Fix Video
curl --request POST \
--url https://api.fastdrop.io/api/v1/fix \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"video_url": "<string>",
"context": "<string>",
"reason_id": "<string>",
"overrides": {}
}
'import requests
url = "https://api.fastdrop.io/api/v1/fix"
payload = {
"video_url": "<string>",
"context": "<string>",
"reason_id": "<string>",
"overrides": {}
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
video_url: '<string>',
context: '<string>',
reason_id: '<string>',
overrides: {}
})
};
fetch('https://api.fastdrop.io/api/v1/fix', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.fastdrop.io/api/v1/fix",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'video_url' => '<string>',
'context' => '<string>',
'reason_id' => '<string>',
'overrides' => [
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.fastdrop.io/api/v1/fix"
payload := strings.NewReader("{\n \"video_url\": \"<string>\",\n \"context\": \"<string>\",\n \"reason_id\": \"<string>\",\n \"overrides\": {}\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.fastdrop.io/api/v1/fix")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"video_url\": \"<string>\",\n \"context\": \"<string>\",\n \"reason_id\": \"<string>\",\n \"overrides\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.fastdrop.io/api/v1/fix")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"video_url\": \"<string>\",\n \"context\": \"<string>\",\n \"reason_id\": \"<string>\",\n \"overrides\": {}\n}"
response = http.request(request)
puts response.read_bodyFix
Fix Video
Execute the fix the readiness check prescribed, and verify it worked
POST
/
fix
Fix Video
curl --request POST \
--url https://api.fastdrop.io/api/v1/fix \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"video_url": "<string>",
"context": "<string>",
"reason_id": "<string>",
"overrides": {}
}
'import requests
url = "https://api.fastdrop.io/api/v1/fix"
payload = {
"video_url": "<string>",
"context": "<string>",
"reason_id": "<string>",
"overrides": {}
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
video_url: '<string>',
context: '<string>',
reason_id: '<string>',
overrides: {}
})
};
fetch('https://api.fastdrop.io/api/v1/fix', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.fastdrop.io/api/v1/fix",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'video_url' => '<string>',
'context' => '<string>',
'reason_id' => '<string>',
'overrides' => [
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.fastdrop.io/api/v1/fix"
payload := strings.NewReader("{\n \"video_url\": \"<string>\",\n \"context\": \"<string>\",\n \"reason_id\": \"<string>\",\n \"overrides\": {}\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.fastdrop.io/api/v1/fix")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"video_url\": \"<string>\",\n \"context\": \"<string>\",\n \"reason_id\": \"<string>\",\n \"overrides\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.fastdrop.io/api/v1/fix")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"video_url\": \"<string>\",\n \"context\": \"<string>\",\n \"reason_id\": \"<string>\",\n \"overrides\": {}\n}"
response = http.request(request)
puts response.read_bodyTakes a video, runs the readiness check, executes the fix that check prescribes, and then re-runs the check on the result before handing it back.
A file is only returned as fixed if the original failure is confirmed gone and the output still matches the source in duration, audio and orientation. If it does not pass, you get the reason and are charged nothing.
You do not need to call Check Readiness first. This runs the same check itself and does not bill for it separately. Call readiness when you want the verdict without committing to the fix.
Anything else is refused explicitly rather than attempted. A verdict can prescribe fixes we do not execute yet —
The quote carries its working, not just the total, so you can see which band and how many minutes produced the number without reimplementing the rate table.
Poll
Two of these are worth understanding.
Try it on our footage first.
GET /api/v1/samples lists two videos that are
broken on purpose, and checking or fixing them costs no credits. No API key
needed to list them.What it can fix today
| Check | What it does |
|---|---|
vfr | Conforms variable frame rate footage to a constant rate |
rotation_metadata | Bakes a rotation flag into the pixels, so every tool agrees which way up the video is |
422 fix_not_executable names which.
string
required
Public HTTP/HTTPS URL of the video.
string
required
What the footage is for. The fix executed is the one this context’s verdict prescribes, and the same clip can need different work for different destinations — a rotation flag is a defect for
motion_analysis and a non-event for nle_editing.Accepts the same values as Check Readiness: motion_analysis, nle_editing, social_playback, archival.string
Which failing check to fix, e.g.
vfr. Optional — with one executable failure, which is the common case, it is inferred.When a clip fails several checks, the fix that runs is the one for the most severe of them. Name a reason_id to choose differently.object
Threshold overrides, exactly as on Check Readiness. They shape the verdict, and therefore what gets fixed.
string
Send one to make retries safe. Replaying a request with the same key returns the original job instead of charging and transcoding again — it does not even re-read the video.Scoped to your API key, so you are free to choose any value.
Response
{
"fix_job_id": "9c2f1e40-5f6d-4a1e-9a2b-3f8c7d1e0b42",
"status": "queued",
"reason_id": "vfr",
"context": "motion_analysis@v1",
"quote": {
"credits": 2,
"resolution_tier": "hd",
"billed_minutes": 1,
"credits_per_minute": 2,
"output": { "width": 1280, "height": 720 }
},
"credits_charged": 2
}
GET /api/v1/fix/{fix_job_id} for the result.
Statuses
status | Meaning |
|---|---|
queued | Accepted, not yet handed to the transcoder |
transcoding | In progress |
verifying | The file came back; we are checking it |
succeeded | Verified. result_url carries the file |
failed_provider | The transcode could not be completed for this file |
failed_timeout | It did not finish in the time we allow, or we lost track of it |
verification_failed | A file came back and it was not what was prescribed |
terminal tells you whether the status is final, so you do not need to keep the list above in your client to know when to stop polling.
All three failures charge nothing. That is not a refund you have to ask for — it is the state of the job.
Why three failure states and not one
They mean different things to a decision you are about to make.failed_provider says this file could not be transcoded; retrying is unlikely to help. failed_timeout says nothing is known about the outcome; retrying is reasonable. verification_failed says a file came back and we would not stand behind it — the verification report says which check failed and what was measured.
The verification report
Present onsucceeded and on verification_failed alike, because the checks that passed are as informative as the one that did not:
{
"passed": false,
"failed_checks": ["duration_preserved"],
"checks": [
{ "name": "measurement_ran", "passed": true, "detail": "Sampled via spread_3x50." },
{ "name": "original_failure_cleared", "passed": true, "detail": "'vfr' no longer fails for motion_analysis@v1." },
{ "name": "duration_preserved", "passed": false, "detail": "20.000s -> 12.004s (delta 7.996s, tolerance 0.033s = one frame)" },
{ "name": "audio_preserved", "passed": true, "detail": "Audio present (aac)." },
{ "name": "dimensions_as_prescribed", "passed": true, "detail": "asked 1280x720, got 1280x720" },
{ "name": "rotation_resolved", "passed": true, "detail": "No rotation metadata on the output." }
]
}
measurement_ran is checked separately, and first. Our frame-rate detector reports “no variable frame rate” when it could not read the file — which is honest about the file and useless as evidence. Without asking this question on its own, a failed measurement would look exactly like a clean result, and we would hand you a file stamped verified having proved nothing. If this check fails, it is not a claim that the fix failed; it is a statement that we could not confirm it, and you were not charged either way.
duration_preserved tolerates one frame, not a fixed number of seconds. Conforming to a constant rate moves the final frame onto a new boundary, which costs up to 1/fps — 0.033s at 30fps, 0.042s at 24. A flat tolerance would either pass a truncated encode or fail a correct one, depending on the rate.
Frame count is deliberately not checked. Forcing a constant rate changes it — that is the fix working.
Result URLs
result_url is signed when you read the job, with a fresh one-hour expiry. Re-poll to get a new one; there is no stale-link failure mode.
Cost
Credits by output duration and resolution. See Pricing for the table. The quote is returned before anything runs, and computed from measurements we have already taken — so it is the price, not an estimate.A fix is one video in, one video out. There is no batch form and no output ladder. If you need several renditions, that is a transcoding service, and this is not one — it executes a prescription and proves it worked.
Errors
| Status | Code | Meaning |
|---|---|---|
| 402 | insufficient_credits | The error carries the price and your balance |
| 422 | nothing_to_fix | This file has no recoverable failure matching the request. The verdict is attached |
| 422 | fix_not_executable | The verdict prescribes a fix we do not execute yet |
| 422 | not_measurable | The video could not be inspected closely enough to fix safely |
| 422 | unreadable_source | The URL could not be read |
| 503 | fix_unavailable | Fix execution is not enabled on this deployment |
nothing_to_fix is worth expecting rather than treating as an error: it is what you get when the file is already fine for the context you named. Nothing is charged, and the attached verdict tells you why we thought so.