Skip to content

Uploads

For local files, you can’t pass a url to POST /v1/clips — the pipeline needs an http-reachable source. The uploads endpoint mints a one-time presigned S3 PUT URL you can stream the file to. The resulting upload_id flows into POST /v1/clips.

There are two ways to upload:

  • Single PUT — omit size_bytes. One URL, files up to 5 GB.
  • Multipart — send size_bytes. You get one URL per part (at most 100 parts), PUT each part, then call POST /v1/uploads/:id/complete. Files up to 10 GB. Use this for long recordings and anything over 5 GB.

Auth: Bearer JWT or API key

FieldTypeRequiredDescription
content_typestringyesA video MIME type, e.g. video/mp4, video/quicktime. application/octet-stream is also accepted.
file_namestringnoThe file’s name, e.g. team offsite.mp4. The clip job’s folder is named after it.
size_bytesnumbernoTotal file size in bytes. Makes the upload multipart. Max 10,000,000,000.
Terminal window
curl -X POST https://api2.choppity.com/v1/uploads \
-H "Authorization: Key $CHOPPITY_KEY" \
-H "Content-Type: application/json" \
-d '{ "content_type": "video/mp4" }'
FieldTypeDescription
upload_idstringPass this as source.upload_id to POST /v1/clips
upload_urlstringSingle PUT only: presigned S3 PUT URL
expires_atnumberUnix-ms timestamp when the URL(s) stop working
part_sizenumberMultipart only: bytes per part (the last part may be smaller)
partsarrayMultipart only: { part_number, upload_url } for every part
{
"upload_id": "9f8e7d6c-5b4a-3c2b-1a09-0f8e7d6c5b4a",
"upload_url": "https://choppity-assets-dev.s3.amazonaws.com/tmp/api-uploads/...",
"expires_at": 1745803600000
}

The signed PUT URL is valid for 1 hour. If your upload may take longer, mint the URL right before you start streaming.

The upload_id itself stays valid as long as the underlying S3 object exists — typically until the next overnight tmp cleanup.

Part N is bytes (N-1) × part_size to N × part_size − 1 of the file. PUT each part to its upload_url and keep the ETag response header S3 returns — you need every one to finish.

Terminal window
# part 1 of a file with part_size 104857600
dd if=./recording.mp4 bs=104857600 skip=0 count=1 2>/dev/null \
| curl -sS -X PUT --data-binary @- -D - "$PART_1_URL" | grep -i etag

Part URLs are valid for 4 hours. If some expire before you’re done, re-sign just those:

FieldTypeDescription
part_numbersnumber[]Up to 100 part numbers to re-sign

Returns { upload_id, expires_at, parts: [{ part_number, upload_url }] }.

FieldTypeDescription
partsarrayEvery part exactly once: { part_number, etag }

Returns { upload_id, status: "complete" }. Calling it again is harmless. Until it succeeds, POST /v1/clips refuses the upload with 409 CONFLICT.

If a part is missing or an etag doesn’t match what S3 received, it answers 400 BAD_REQUEST: upload that part again (re-sign it first if its URL has expired) and call complete with its new etag. An upload that has expired or was cancelled answers 409 CONFLICT.

Cancels an unfinished multipart upload and discards its parts. Returns { upload_id, status: "aborted" }.

  1. Mint the upload URL.

    Terminal window
    curl -X POST https://api2.choppity.com/v1/uploads \
    -H "Authorization: Key $CHOPPITY_KEY" \
    -H "Content-Type: application/json" \
    -d '{ "content_type": "video/mp4" }' \
    | tee upload.json
  2. PUT the file directly to S3.

    Terminal window
    curl -X PUT --upload-file ./podcast.mp4 \
    -H "Content-Type: video/mp4" \
    "$(jq -r .upload_url upload.json)"

    The Content-Type header must match what you sent in step 1 — S3 rejects PUTs that disagree.

  3. Start a clip job using the upload.

    Terminal window
    curl -X POST https://api2.choppity.com/v1/clips \
    -H "Authorization: Key $CHOPPITY_KEY" \
    -H "Content-Type: application/json" \
    -d "{
    \"source\": { \"upload_id\": \"$(jq -r .upload_id upload.json)\" },
    \"criteria\": \"funny moments\"
    }"

    The pipeline streams the uploaded file in via a short-lived presigned GET URL — no public hosting required on your side.

    Each upload feeds one clip job. Starting a second job with the same upload_id answers 409 CONFLICT; upload the file again instead.

CodeWhen
BAD_REQUESTcontent_type missing or not a video type; a part missing or its etag not matching on complete; size_bytes not a positive integer or over 10 GB; part_numbers or parts incomplete or out of range
CONFLICTCompleting a cancelled or expired upload, cancelling a completed one, starting a clip job before complete, or starting a second clip job on the same upload
NOT_FOUNDUnknown upload_id
UNAUTHORIZEDMissing or invalid credential