API reference
Errors
Every error code the API returns, what causes it, and whether retrying is worth your time.
Failures come back with the same shape every time.
{
"ok": false,
"code": "PRIVATE",
"message": "That post is private, deleted, or age restricted. Public posts only."
}
Branch on code. The message is written for a person to read and the wording may change.
The codes
| Code | Status | Cause | Retry |
|---|---|---|---|
INVALID_URL |
400 | Not an Instagram post link | No |
UNSUPPORTED |
400 | A link type we do not read, such as a Story | No |
PRIVATE |
422 | The post needs a login, is deleted, or is age restricted | No |
TOO_LONG |
422 | Video is over the duration cap | No |
TOO_LARGE |
422 | Audio is over the size the provider accepts | No |
RATE_LIMITED |
429 | You are over the requests-per-hour limit on that key | After retry-after |
UNAUTHORIZED |
401 | No key, or a key that has been revoked | No |
INSUFFICIENT_CREDITS |
402 | Not enough credits for this video | After a top up |
CHALLENGE_FAILED |
403 | A bot check did not pass. Browser visitors only; API keys skip it | No |
FETCH_FAILED |
502 | Instagram would not serve us the post | Yes, with backoff |
TRANSCRIBE_FAILED |
500 | Something broke on our side | Yes, with backoff |
NOT_CONFIGURED |
503 | Transcription is off for this deployment | No |
The three you will actually see
PRIVATE
The most common failure, and it is permanent. Mark the link as unreadable and move on. A retry an hour later gives you the same answer.
Watch for it in bulk jobs. A run over a large list will hit plenty of these, and treating them as transient will waste the whole budget on retries that cannot succeed.
INSUFFICIENT_CREDITS
The balance will not cover the video. Nothing was transcribed and nothing was charged.
The response says how many credits are left and how many the call needed. Top up on your account page, or read the balance ahead of a large run with a GET to the same endpoint.
curl https://instagramtotranscript.com/v1/transcripts \
-H "Authorization: Bearer $ITT_API_KEY"
{
"ok": true,
"key": { "id": "...", "name": "Batch runner" },
"credits": { "remaining": 184 },
"limits": { "requestsPerHour": 600, "maxDurationSec": 600 }
}
Every successful transcript also carries the numbers, in the body under credits and in the x-credits-remaining header, so a long job can watch its own balance without a second request.
RATE_LIMITED
Read retry-after and sleep for that many seconds.
if (!body.ok && body.code === "RATE_LIMITED") {
const wait = Number(response.headers.get("retry-after") ?? 60);
await new Promise((resolve) => setTimeout(resolve, wait * 1000));
}
Retrying properly
Only FETCH_FAILED and TRANSCRIBE_FAILED deserve a retry. Use exponential backoff and stop after three attempts.
async function withRetry<T>(run: () => Promise<T>, attempts = 3): Promise<T> {
for (let attempt = 1; ; attempt++) {
try {
return await run();
} catch (error) {
if (attempt >= attempts) throw error;
await new Promise((resolve) => setTimeout(resolve, 2 ** attempt * 1000));
}
}
}
FETCH_FAILED usually means Instagram answered us with a login shell rather than the post. It often clears on its own within a minute or two.