Send one Edit JSON document. Get back a zip with a project for CapCut, Premiere Pro, DaVinci Resolve and Final Cut Pro, each ready to open in that editor. Captions and titles stay editable text.
The document format is Edit JSON, version 1. This page covers the hosted API: the call, the answer, the errors and the pricing.
The call
POST https://cutconvert.com/api/edit/export
Content-Type: application/json
Authorization: Bearer cck_… (optional)The body:
{
"edit": { "cutconvert": "edit/1", "…": "…" },
"targets": ["capcut", "premiere", "resolve", "finalcut"],
"media": [{ "id": "silent", "base64": "AAAAIGZ0eXBpc29t…" }]
}| Field | Type | Rules |
|---|---|---|
edit | object or string | Required. The Edit JSON document, as an object or as a string that holds it. At most 8 MB. |
targets | array | Required. 1 to 4 of capcut, premiere, resolve and finalcut, each once. |
media | array | Optional. Files to put in the zip, at most 100. Each is { "id", "base64" }: id is the id of one of the document's media, once each; base64 is the file's bytes in standard base64 (no data: prefix; line breaks are fine). |
Any other field is an error. The whole body, media included, is at most 25 MB.
premiere is a Premiere Pro XML (.xml), resolve a DaVinci Resolve project (.drp), finalcut an FCPXML (.fcpxml) and capcut a CapCut desktop project folder.
Keys and guests
With a key. Create keys on your account page, under API keys. A key is cck_ and 32 letters and digits. It is shown once, when you create it; we keep only a hash. Send it on each call:
Authorization: Bearer cck_…An account can hold 10 live keys. Revoke a key on the same page; calls with it then get a 401. All of an account's keys share its exports. A Premiere plugin key (ccp_…) is not an API key and gets a 401.
Without a key. Leave out the Authorization header. Calls without a key get 3 exports a calendar month (UTC) per network: your IP address, or for IPv6 its /64. The address is kept only as a hash keyed with a secret, which can't be turned back into the address.
Try it
request.json: one 10-second clip of a 25 fps file that is known only by name.
{
"edit": {
"cutconvert": "edit/1",
"name": "Edit 09 minimal",
"timeline": { "fps": 25, "width": 1280, "height": 720 },
"media": [
{ "id": "silent", "name": "silent-25.mp4", "duration": 30.0, "fps": 25, "width": 1280, "height": 720 }
],
"video": [
{ "clips": [{ "media": "silent", "start": 0, "in": 2, "out": 12 }] }
]
},
"targets": ["capcut", "premiere", "resolve", "finalcut"]
}Without a key (the guest allowance):
curl -sS https://cutconvert.com/api/edit/export \
-H "Content-Type: application/json" \
--data @request.json \
-o edit.zipWith a key:
curl -sS https://cutconvert.com/api/edit/export \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $CUTCONVERT_API_KEY" \
--data @request.json \
-o edit.zipOn an error the answer is JSON, and -o writes it to edit.zip. Add --fail-with-body to make curl exit with an error code too.
To send the media file with the document, save the edit object above as edit.json and put the file's bytes in media. With jq (1.6 or later):
base64 < silent-25.mp4 > silent.b64
jq -n --slurpfile edit edit.json --rawfile b64 silent.b64 \
'{edit: $edit[0], targets: ["capcut", "premiere"], media: [{id: "silent", base64: $b64}]}' \
> request.jsonThe answer
A 200 is a zip:
HTTP/1.1 200 OK
Content-Type: application/zip
Content-Disposition: attachment; filename="Edit 09 minimal.zip"; filename*=UTF-8''Edit%2009%20minimal.zip
X-Request-Id: 3f0c6d1e-8a4b-4c2e-9d7f-0b1a2c3d4e5fThe zip and its files are named after the document's name, with characters a file name can't hold replaced. Inside, one folder for each target you asked for:
capcut/
Open in CapCut.txt
Edit 09 minimal/ the CapCut project folder
draft_content.json
draft_info.json
draft_meta_info.json
timeline_layout.json
Timelines/…
silent-25.mp4 media you sent, beside Open in CapCut.txt
premiere/
Edit 09 minimal.xml
resolve/
Edit 09 minimal.drp
finalcut/
Edit 09 minimal.fcpxml
report.jsonMedia you send goes in capcut/, beside Open in CapCut.txt, when CapCut is a target. Otherwise it goes in media/ at the top of the zip. Each file keeps the name the document gives it (media[].name), with characters a file name can't hold replaced, because the editors relink by name. When two files have the same name, or a file has the name of the CapCut project folder or of Open in CapCut.txt, the later one gets a number: name (2).mp4. The CapCut project asks for it by that name, and the other projects' warnings in report.json (edit-api-media-renamed) say which copy is which.
In the CapCut project, media you send has no path. CapCut lists it in its Link media window when the project opens, and you pick it in capcut/, the folder Open in CapCut.txt is in. Leave the media there when you move the project folder. The other three projects keep the document's path for every file.
To open the CapCut project, follow Open in CapCut.txt: quit CapCut, move the project folder into CapCut's projects folder, then open CapCut.
report.json
{
"cutconvert": "edit-export/1",
"requestId": "3f0c6d1e-8a4b-4c2e-9d7f-0b1a2c3d4e5f",
"name": "Edit 09 minimal",
"targets": {
"capcut": {
"files": ["capcut/Open in CapCut.txt", "capcut/Edit 09 minimal/draft_content.json", "…", "capcut/silent-25.mp4"],
"warnings": []
},
"premiere": { "files": ["premiere/Edit 09 minimal.xml"], "warnings": [] },
"resolve": { "files": ["resolve/Edit 09 minimal.drp"], "warnings": [] },
"finalcut": {
"files": ["finalcut/Edit 09 minimal.fcpxml"],
"warnings": [
{
"code": "fcpxml-relink-by-name",
"message": "1 media item is known only by name, without a file path (silent-25.mp4). It points into a \"CutConvert Offline Media\" folder that does not exist, so Final Cut opens the clips offline with their edits intact; relink them with File > Relink Files."
}
]
}
},
"media": [{ "id": "silent", "name": "silent-25.mp4", "file": "capcut/silent-25.mp4" }],
"export": {
"charged": "guest",
"allowance": { "kind": "guest", "month": "2026-10", "limit": 3, "used": 1, "remaining": 2, "resetAt": 1793491200000 }
}
}| Field | What it holds |
|---|---|
requestId | This call's id, also in the X-Request-Id header. Quote it to support. |
targets.<t>.files | Every file in the zip for that target, as paths in the zip. Media you sent is listed under capcut when CapCut is a target. |
targets.<t>.warnings | What that editor's project couldn't keep exactly as the document says, as { code, message }. Notes that every document gets, whatever it holds, are left out. edit-api-media-renamed says a file you sent is in the zip under another name than the project looks for. |
media | The files you sent: the document's id and name, and where each is in the zip. |
export.charged | guest, free (one of the month's free 10) or paid (one from your packs). |
export.allowance | What is left after this call. For a guest: { kind: "guest", month, limit, used, remaining, resetAt }. With a key: { kind: "key", month, free: { limit, used, remaining }, paid: { balance }, resetAt }, where paid.balance is the paid exports you can still spend (your balance less the calls still running or not yet charged), or null when the free 10 covered this call. resetAt is when the month's count starts again, in milliseconds since 1970 (UTC). |
Relinking media sent without a path
A document can name a file with no path. If you also send the file, CapCut finds it in the zip. The other editors open the clips offline, with the edit intact, until you link them to the file. What each editor shows:
| Editor | On opening | To relink |
|---|---|---|
| CapCut | A "Link media" window: "Couldn't find some of the imported media files. Link the media files then edit." It lists each file by name, with "Link media when selecting a folder" ticked. | Click Link media and pick one of the files. With that box ticked, CapCut links the others from the same folder. |
| Premiere Pro | Nothing. The XML imports with the clips offline. | Select the offline clips and use Link Media. |
| DaVinci Resolve | Nothing. The clips are offline in the media pool. | Select them and use Relink Selected Clips. |
| Final Cut Pro | Nothing. The clips show "Missing File". | File > Relink Files > Original Media… opens "Relink Clips to Original Media Files". Click Locate All…, pick the file ("Click Choose to verify match of file …"), and Final Cut reports "1 of 1 files matched". Click Relink Files. |
Final Cut links a file only when it matches what the project expects: a picture or sound as declared, the frame rate, the timecode range and the number of sound channels. So the facts in media should be the file's own.
When the document's path is where the file is on the editor's computer, nothing needs relinking.
Errors
Every error is JSON with an error sentence. Some add the fields below.
| Status | When | Example body |
|---|---|---|
| 400 | The body isn't JSON. | {"error":"The body must be JSON."} |
| 400 | The body's shape is wrong. Every problem is listed. | {"error":"This request has 2 problems.","problems":["\"extra\": unknown field; the body holds edit, targets and media","targets[0]: \"avid\" is not one of capcut, premiere, resolve, finalcut"]} |
| 400 | edit is an object nested too deeply to read. | {"error":"This request has 1 problem.","problems":["edit: nested too deeply"]} |
| 400 | A media id isn't one of the document's. | {"error":"This request has 1 problem.","problems":["media[0].id: \"camera\" is not the id of one of the document's media"]} |
| 400 | edit is a string that isn't JSON. | {"error":"This Edit JSON document isn't valid JSON: unexpected \"n\" at line 1, column 2.","problems":["This Edit JSON document isn't valid JSON: unexpected \"n\" at line 1, column 2."]} |
| 400 | The document isn't valid Edit JSON. Every problem is listed, with its path (format rules). | {"error":"This Edit JSON document has 3 problems.","problems":["video[0].clips[0].fadein: unknown field","timeline.fps: 31 is not one of 23.976, 24, 25, 29.97, 30, 50, 59.94 or 60","video[0].clips[0].out: 130 s is past the end of \"wide\", which lasts 120.02 s"]} |
| 400 | One editor's project couldn't be written from this document. | {"error":"The capcut export couldn't be written: …","requestId":"…","target":"capcut"} |
| 401 | The key doesn't exist, most often because it was mistyped or cut short. | {"error":"This API key isn't valid. Check it, or create one at https://cutconvert.com/account."} |
| 401 | The key was revoked, or its account is closed. | {"error":"This API key isn't valid: it was revoked, or its account is closed. Create a new key at https://cutconvert.com/account."} |
| 401 | A Premiere plugin key was sent. | {"error":"That's a Premiere panel key. The Edit API takes an API key (cck_…), which you can create at https://cutconvert.com/account."} |
| 401 | The Authorization header isn't Bearer cck_…. | {"error":"Send an API key as \"Authorization: Bearer cck_…\", or no Authorization header for the guest allowance."} |
| 401 | A call without a key that can't be counted by network. | {"error":"Guest exports aren't available from here. Send an API key: create one at https://cutconvert.com/account."} |
| 402 | A guest network has used its 3 exports this month. | {"error":"This network has used its 3 guest exports for October. Create a free API key for 10 exports a month.","requestId":"…","upgradeUrl":"https://cutconvert.com/developers#pricing","allowance":{"kind":"guest","month":"2026-10","limit":3,"used":3,"remaining":0,"resetAt":1793491200000}} |
| 402 | An account has used its free 10 this month and has no paid exports left. | {"error":"This account has used its 10 free exports for October and has no paid exports left. Buy a pack of 300, 1,500 or 10,000 exports, or wait for the free 10 on November 1.","requestId":"…","upgradeUrl":"https://cutconvert.com/developers#pricing","allowance":{"kind":"key","month":"2026-10","free":{"limit":10,"used":10,"remaining":0},"paid":{"balance":0},"resetAt":1793491200000}} |
| 402 | An account's paid exports left are all held by its calls still running, or by exports whose charge hasn't gone through yet. Try again shortly. | {"error":"This account has used its 10 free exports for October, and its paid exports left are taken by calls still running or not yet charged. Try again in a minute.","requestId":"…","upgradeUrl":"https://cutconvert.com/developers#pricing","allowance":{"kind":"key","month":"2026-10","free":{"limit":10,"used":10,"remaining":0},"paid":{"balance":0},"resetAt":1793491200000}} |
| 408 | The body took over 2 minutes to arrive. | {"error":"The request took over 120 s to arrive. Send it again, or send less media in each call."} |
| 413 | The body is over 25 MB, media included. | {"error":"The request is over 25 MB, media included. Leave the media out (the projects then point to each file's path), or send fewer files in each call."} |
| 413 | The document is over 8 MB. | {"error":"edit: the document is over 8 MB"} |
| 413 | The projects are too large to send back in one answer. | {"error":"This export is too large to send in one answer. Ask for fewer targets in each call, or split the edit.","requestId":"…"} |
| 429 | Too many calls this minute: 30 a minute per account (all its keys), 6 a minute per guest network. | {"error":"Too many calls this minute. Try again in 12 s.","retryAfter":12}, with a Retry-After: 12 header. |
| 503 | Too many large uploads are arriving at once. Checked before the body is read. | {"error":"The export service is busy. Try again in 10 s.","retryAfter":10}, with a Retry-After: 10 header. |
| 500 | The billing service didn't answer while checking paid exports. | {"error":"The billing service could not confirm your paid exports. Nothing was charged. Try again in a minute.","requestId":"…"} |
| 500 | The export failed on our side. | {"error":"The export failed on our side. Nothing was charged. Email [email protected] with the request id.","requestId":"…"} |
| 500 | The call failed on this server before the export ran. | {"error":"The export failed on this server. Nothing was charged. Try again in a minute."} |
| 500 | The export service didn't answer. | {"error":"The export service didn't answer. Try again in a minute."} |
| 500 | The export service's answer couldn't be read, so no zip was made. | {"error":"The export service sent an answer this server could not read. Nothing was zipped; try again."} |
| 500 | The export service failed in a way it couldn't describe. | {"error":"The export failed on our side. Try again, or email [email protected] with the time of the call."}, or {"error":"The export failed. Try again in a minute, or email [email protected] with the time of the call."} |
| 500 | The export was made, but the zip couldn't be built. | {"error":"The export was made, but this server couldn't put it in a zip. Email [email protected] with the request id.","requestId":"…"} |
A 402 has upgradeUrl, the pricing page, and allowance, as in report.json. A 429 and a 503 have retryAfter, in seconds. Calls that end in a 400, a 401, a 402, a 408, a 413, a 429 or a 503 are never charged, nor are the 500s that say "Nothing was charged". After any other 500 the export may have been counted: write to [email protected] with the request id if there is one, or the time of the call.
Metering and pricing
One export is one call, whatever the number of targets. Asking for all four editors costs the same as asking for one. Every tier includes all four, CapCut and Resolve too.
| Who | Exports |
|---|---|
| No key | 3 a calendar month (UTC) per network. |
| A key | 10 free a calendar month (UTC) per account, shared by all its keys. Then the account's paid exports. |
Paid exports come in packs, bought on the pricing page or your account page. A pack is a one-off payment, not a subscription.
| Pack | Price | Per export |
|---|---|---|
| 300 exports | $49 | about 16¢ |
| 1,500 exports | $149 | about 10¢ |
| 10,000 exports | $499 | about 5¢ |
How a call is counted:
- The month's free exports are used first. A paid export is used only after the free 10.
- A paid export is checked before the projects are written and taken once they are, one per call.
- A call that fails is not charged: an invalid document, a project that couldn't be written, a 413, or a 500 that says "Nothing was charged".
- While a paid call runs, and until its charge goes through, it holds one of your paid exports: other calls can't spend it.
- Your account page shows this month's free exports and your paid balance. Every answer has the same in
export.allowance(inreport.json, or in the body of a 402).
What we keep
Media you send stays in memory for the call and is dropped with the answer. It is never stored, logged or sent anywhere else. The document is not stored. For each call except a 401 or a 429 we keep a record, as for any conversion on the site: when it ran, which targets, the outcome, the document's name, the output files' names and sizes, the warnings, and for a call that failed, its first error message. With a key, the record names the account; without one, a keyed hash of the network's address.