smartmedia
The API

Using the API

Everything is one HTTPS API with JSON bodies and bearer keys. There are no SDKs to install: four calls with curl are the whole product.

Authentication

Keys look like sm_live_… and are made on the dashboard, shown once, stored hashed. Send one on every call to /v1:

Authorization: Bearer sm_live_…

A key has scopes (analyses:read, analyses:write), a rate limit per minute (60 by default; the X-RateLimit-* headers say where you stand) and an optional monthly spend cap. The base URL is https://api.smartmediaprotocol.com.

1 · Upload

Ask for an upload URL, then PUT the bytes to it. The URL is good for an hour.

curl -X POST https://api.smartmediaprotocol.com/v1/uploads -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
     -d '{"filename":"IMG_1731.MOV","bytes":61230411,"content_type":"video/quicktime"}'
# → { "upload_id": "up_…", "upload": { "url": "…", "method": "PUT", "headers": { … } }, "max_bytes": 314572800, "max_seconds": 600 }
curl -X PUT "$UPLOAD_URL" -H "Content-Type: video/quicktime" --data-binary @IMG_1731.MOV

Accepted: videos (mp4, mov, m4v, webm, mkv), audio (mp3, m4a, wav, flac, aac, ogg, opus, aif, caf) and images (jpg, png, heic, webp, tif, gif, bmp, avif). The response states the two limits the server enforces. Instead of an upload you may pass a public https URL as url when creating the analysis.

2 · Analyse

Create the job with the upload and the sidecar type you declare. The API never guesses the type from the content; without one, a video is video/edit, an audio file audio/speech, an image image/scene. The answer is 202 with the job, and the estimate is held on your balance.

curl -X POST https://api.smartmediaprotocol.com/v1/analyses -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
     -d '{"upload_id":"up_…","type":"video/footage","webhook_url":"https://example.com/hook","idempotency_key":"IMG_1731-v1"}'
# → { "id": "an_…", "status": "queued", "type": "video/footage", "estimate_cents": 20, … }
curl https://api.smartmediaprotocol.com/v1/analyses/an_… -H "Authorization: Bearer $KEY"     # queued → running → done | failed
fieldmeaning
upload_id | urlOne of the two. A URL must be https and public; the worker fetches it.
typevideo/footage, video/edit, audio/speech, audio/music, image/scene, image/graphic.
options.consolidateVideo only: one readable paragraph per shot from a second model pass. 5¢ per minute more.
options.placeLook up the recording place from the file's GPS (default true).
options.languageA spoken-language hint as a BCP 47 tag.
webhook_urlWhere to POST analysis.completed or analysis.failed.
idempotency_keyRepeating a request with the same key returns the same job instead of a second charge; a different request with the same key is a 409.
sourceThe original, when the upload is a proxy of it. See below.

A job can be cancelled while it is queued (DELETE /v1/analyses/:id, the hold is released); once done, the same call deletes the stored result.

Proxies and the original

The analysis only ever needs a small copy of a video: 1280 px on the long side, 30 fps, a few megabits per second. Uploads are capped at 300 MB and 1280 px, so for anything larger you send a proxy and describe the original in source. Everything given there replaces what the API reads from the upload, so the sidecar's source block, the recording time and place and the camera facts are the original's; the API never relies on metadata embedded in the proxy. generated.proxy records what was actually analysed, and the result cache is keyed on the declared sha256.

"source": { "name": "IMG_4115.MOV", "bytes": 358077119, "sha256": "…64 hex…", "quick_hash": "…64 hex…", "container": "mov",
            "created_at": "2026-08-25T14:05:42+08:00",
            "location": { "lat": -8.6423, "lon": 115.154, "altitude": 36 },
            "camera": { "make": "Apple", "model": "iPhone 17 Pro Max" },
            "video": { "codec": "hevc", "width": 3840, "height": 2160, "fps": 59.97, "bit_rate": 54734192, "rotation": 90, "hdr": true, "color_transfer": "arib-std-b67" },
            "audio": { "codec": "aac", "channels": 2, "sample_rate": 48000, "bit_rate": 189321 } }

Keep the camera's zone offset in created_at: it gives the local time and the time of day. The quick hash is the SHA-256 of the size in bytes as decimal ASCII, then the first MiB, then the last MiB of the original.

3 · The result

curl "https://api.smartmediaprotocol.com/v1/analyses/an_…/result?format=smartmedia" -H "Authorization: Bearer $KEY" -o IMG_1731.MOV.smartmedia
curl "https://api.smartmediaprotocol.com/v1/analyses/an_…/result?format=text" -H "Authorization: Bearer $KEY"      # the readable twin
curl "https://api.smartmediaprotocol.com/v1/analyses/an_…/result?format=json" -H "Authorization: Bearer $KEY"      # the full internal analysis

Write the smartmedia document next to the original as <name>.smartmedia. The text format is a plain-text rendering of the same document for a language model or a person. Results are kept for 90 days. Listing: GET /v1/analyses?limit=&status=.

Webhooks

With a webhook_url, the API POSTs analysis.completed or analysis.failed with the job, signed with the key's webhook secret in the Smartmedia-Signature header (HMAC-SHA256 of the timestamp and the body). Deliveries retry with backoff; the dashboard shows each attempt and lets you send a test event. The secret is on the key's page and can be rotated.

Estimates, prices and caps

curl "https://api.smartmediaprotocol.com/v1/estimate?type=video/footage&duration_s=125" -H "Authorization: Bearer $KEY"
curl "https://api.smartmediaprotocol.com/v1/estimate?type=image/scene" -H "Authorization: Bearer $KEY"
curl https://api.smartmediaprotocol.com/v1/me -H "Authorization: Bearer $KEY"     # balance, the key, every price
typepriceminimum
video/footage, video/edit20¢ per minute20¢
audio/speech6¢ per minute
audio/music2¢ per minute
image/scene, image/graphic3¢ per image

Money is integer cents on a prepaid balance. The estimate is held when a job is accepted and settled to the actual charge, never more than the estimate, when it ends; a failed job releases its hold. A file already analysed by the same account as the same type is served from the cache for nothing. Monthly spend caps exist per key and per account; a job that would cross one is refused with 402.

Errors

One shape, always:

{ "error": { "code": "input_too_large", "message": "Files may be at most 314572800 bytes. Downscale or trim first.", "details": { "max_bytes": 314572800 }, "request_id": "req_…" } }
codestatuswhen
unauthorized401No key, or a revoked one.
forbidden403A missing scope, or a suspended account.
validation_failed400The body is wrong; details.problems names each problem.
upload_missing404No such upload, or nothing was PUT to it yet.
input_too_large413 / 422Over the byte limit, or over 1280 px for a video. Send a proxy.
input_too_long422Over ten minutes, or under half a second.
input_unsupported422Not a media file, or a type the file cannot be (audio as video).
insufficient_balance, spend_cap_reached402Buy credit on the dashboard, or raise the cap.
idempotency_conflict409The key was used for a different request.
rate_limited429Wait Retry-After seconds.

Limits

  • Ten minutes per file; split longer footage.
  • 300 MB per upload, and 1280 px on the long side for a video: send a proxy and declare the original.
  • Results are kept for 90 days. Uploads are deleted once the job has run.
  • The result cache is per account: nothing is shared across accounts, even for identical files.

Reference

Using the API · Smart Media Protocol