Errors
Every InsightSocial API error type with its status, what to do about it, and copy-paste retry loops.
Every failure returns the same error body, not the success envelope: success: false, an error object with a type to branch on, a readable message and, when one parameter is at fault, param, plus the usual request_id and credit fields. A failed call is never charged.
{
"success": false,
"error": {
"type": "INSUFFICIENT_CREDITS",
"message": "This call needs up to 170 credits and 120 remain. Top up at https://www.insightsocial.app/pricing"
},
"request_id": "req_1a2b3c4d5e6f",
"credits_used": 0,
"credits_remaining": 120
}error.typeis what your code should branch on.error.paramnames the query parameter at fault, when there is one (for examplecursoron a cursor error). It is absent otherwise.error.messageexplains the failure in a sentence and often names what to change. Show it or log it, but do not parse it.credits_usedis0on every error.credits_remainingis your balance when we know it, andnullwhen we do not (authentication failures, for example).request_idmatches theX-Request-Idheader. Include it when you contact support.
That is the whole error body. It carries no schema_version, data or unavailable, so check success before reading anything else.
Refusals made before a call reaches a platform (UNSUPPORTED_PARAMETER, CURSOR_INVALID, CURSOR_EXPIRED, authentication, rate-limit and idempotency errors, INSUFFICIENT_CREDITS) do not appear in the dashboard's Activity Log. Other errors, including the 400 parameter errors a platform returns, do.
Error types
error.type | Status | Retry? | What it means |
|---|---|---|---|
MISSING_API_KEY | 401 | No | No x-api-key header on the request |
INVALID_API_KEY | 401 | No | The value is not an InsightSocial key, or no key matches it |
API_KEY_REVOKED | 401 | No | The key has been revoked |
INSUFFICIENT_CREDITS | 402 | No | Your balance is below what this call needs to hold |
UNKNOWN_PLATFORM | 404 | No | The first path segment is not one of the platforms we serve |
UNKNOWN_ENDPOINT | 404 | No | The platform exists but that path does not |
RESOURCE_NOT_FOUND | 404 | No | Nothing exists at the identifier you sent |
METHOD_NOT_SUPPORTED | 405 | No | Every endpoint is a GET: any other method, and the three POST endpoints in the catalogue, answer 405 |
UNSUPPORTED_PARAMETER | 400 | No | A parameter this endpoint does not have, or one that asks for analysis schema 2 does not carry. error.param names it |
CURSOR_INVALID | 400 | No | The cursor was not issued by this API, was edited, or belongs to a different request |
CURSOR_EXPIRED | 400 | No | The cursor is more than 24 hours old |
IDEMPOTENCY_IN_PROGRESS | 409 | Yes, after Retry-After | A call with the same Idempotency-Key is still running |
IDEMPOTENCY_KEY_REUSED | 409 | No | The Idempotency-Key was already used for a different request |
IDEMPOTENCY_REPLAY_UNAVAILABLE | 409 | No | The original call succeeded, but its response is too large to replay |
RATE_LIMITED | 429 | Yes, after Retry-After | More than 60 requests in 60 seconds on this key |
CONCURRENCY_LIMIT | 429 | Yes, after Retry-After | More than 10 requests in flight on this key |
CLIENT_CLOSED_REQUEST | 499 | Your call | Your client disconnected before the response |
INTERNAL_ERROR | 500 | Yes, with backoff | Something went wrong on our side |
SERVICE_UNAVAILABLE | 503 | Yes, after Retry-After or backoff | We could not complete the call right now |
UPSTREAM_ERROR (or the platform's own type) | 503 | Yes, after Retry-After | The platform did not answer in time |
UPSTREAM_INVALID | 503 | Yes, after Retry-After | The platform answered with something we could not read |
ENDPOINT_UNAVAILABLE | 503 | Not soon: Retry-After is an hour | This endpoint is switched off while its data source is broken |
Parameter errors, such as INVALID_REQUEST or BAD_REQUEST | 400 (or another 4xx) | No | A parameter is missing, invalid, or not accepted by this endpoint |
Parameter errors can carry different types
When a request is rejected because of its parameters, the error.type is not drawn from a fixed list: you will usually see INVALID_REQUEST or BAD_REQUEST, but other values occur. Treat any 4xx you do not recognise as "fix the request", and read error.message, which says what was wrong.
Which errors to retry
Retry only RATE_LIMITED, CONCURRENCY_LIMIT, IDEMPOTENCY_IN_PROGRESS, SERVICE_UNAVAILABLE, UPSTREAM_ERROR, UPSTREAM_INVALID and INTERNAL_ERROR, plus any 429, 500 or 503 whatever its error.type: when a platform fails, the 503 can carry that platform's own error type. A 409 is retryable only when its type is IDEMPOTENCY_IN_PROGRESS. Everything else is deterministic: the same request fails the same way until you change the request, the key or your balance.
When a response carries Retry-After, wait at least that many seconds. Otherwise back off exponentially with jitter, and give up after a few attempts.
Error reference
MISSING_API_KEY
401. No x-api-key header reached the API. Send the header, and if you already do, make sure nothing between you and the API, such as a proxy or gateway, removes it. A key sent in Authorization is not read and also produces this error. See Authentication.
INVALID_API_KEY
401. Either the value does not look like an InsightSocial key (keys start isk_live_ or isk_test_), or it looks right but matches no key. Copy it again from API keys.
API_KEY_REVOKED
401. The key was revoked in the dashboard. Create a new one in the dashboard.
INSUFFICIENT_CREDITS
402. Before a call runs, we hold the most it can cost. If your balance is below that amount, the call is refused and nothing is charged. credits_remaining shows your balance, and the message states both numbers.
For a metered endpoint, the amount held is the most the call can cost with the parameters you sent, and on endpoints that accept max_pages it is multiplied by the number of pages. You can therefore get a 402 on a call that would have cost less in the end. A smaller limit, fewer include joins or fewer pages hold less; dry_run=1 shows the hold for free. Top up on the pricing page; retrying without a top-up fails the same way.
UNKNOWN_PLATFORM
404. The platform segment of the path is not one we serve. The 25 platforms are instagram, tiktok, facebook, linkedin, twitter, threads, youtube, reddit, pinterest, bluesky, truthsocial, snapchat, telegram, twitch, kick, rumble, kwai, douyin, xiaohongshu, quora, tiktokshop, weibo, zhihu, substack and nextdoor.
UNKNOWN_ENDPOINT
404. The path is not in the catalogue. Check it against GET /v1/endpoints or the API reference.
RESOURCE_NOT_FOUND
404. The endpoint exists, but nothing is there for the identifier you sent: a handle that does not exist, a deleted post, and so on. You are not charged. Retrying the same identifier returns the same answer, so record it and move on.
INVALID_REQUEST for a missing parameter
A required parameter left out is refused before anything is fetched: 400 INVALID_REQUEST, with error.param naming it and a message such as "url is required on this endpoint". Nothing is charged.
METHOD_NOT_SUPPORTED
405. This path is in the catalogue but cannot be called through the API. Pick another endpoint.
UNSUPPORTED_PARAMETER
400. You sent a parameter this endpoint does not have (a typo, or one that belongs to another endpoint), or one that asks for analysis: labels, relevance scoring, fit or brand judgments. Both are refused instead of being ignored, which would return unfiltered rows and still charge for them. error.param names the parameter; the endpoint's parameters are in GET /v1/endpoints. Nothing is charged.
{
"success": false,
"error": {
"type": "UNSUPPORTED_PARAMETER",
"message": "`relevance` is not supported: it asks for analysis, which is not part of schema 2. Remove it and retry. Nothing was charged.",
"param": "relevance"
},
"request_id": "req_80d197121a1e",
"credits_used": 0,
"credits_remaining": null
}The refused parameters are label, label_evidence, relevance, relevance_threshold, relevant_to, judgments, fit, goal, fit_tokens, seen, brand, brand_description, offer, exclude (except on /v1/pinterest/trends, where it filters terms), brief, use, moments, and the value format=stationery.
Two more are refused on specific endpoints only, where they ask for analysis; elsewhere they are ordinary parameters:
| Parameter | Refused on | What it asked for |
|---|---|---|
q | /v1/tiktok/post/transcript, /v1/youtube/video/transcript | A question answered about the video's transcript. On search endpoints q is the search query and works as before. |
switch_from | /v1/twitter/search/tweets, /v1/reddit/search/comments | Posts about switching away from a named brand. |
Only the first refused parameter in your query is reported, so remove it and check the next. To filter rows by topic or language, do it on your side after the call; see Schema 2.
CURSOR_INVALID
400. The cursor cannot be used for this request. Either it was not issued by this API (a cursor from the previous schema starts is. or is2.), it was edited or truncated, it was sent under a name other than cursor, or it belongs to a different request: another endpoint, or the same endpoint with different parameters. error.param names the parameter that carried it. Nothing is charged.
Send pagination.next_cursor back unchanged, as cursor, with the same parameters as the first page. Only limit, max_pages, dry_run and fresh may change between pages. Otherwise start again without a cursor. See Pagination.
CURSOR_EXPIRED
400. The cursor is more than 24 hours old. Start the walk again without a cursor. Nothing is charged.
IDEMPOTENCY_IN_PROGRESS
409. An earlier call with the same Idempotency-Key has not finished. Wait for Retry-After, then send the same request with the same key. Once the original has finished, you get its result as a free replay, unless the original never completed (see below).
IDEMPOTENCY_KEY_REUSED
409. The Idempotency-Key was already used for a different request: another endpoint or other parameters. Use a new key. Nothing is charged.
IDEMPOTENCY_REPLAY_UNAVAILABLE
409. The original call with this Idempotency-Key succeeded, but its response is too large for us to replay. Send the request again with a new key to fetch it. Nothing is charged.
RATE_LIMITED
429. This key sent more than 60 requests in a sliding 60-second window. Retry-After is the number of seconds until the oldest request leaves the window. Nothing is charged. See Rate limits.
CONCURRENCY_LIMIT
429. This key already had 10 requests in flight. Retry-After is 1, since a slot frees as soon as any open request returns. Shrink your worker pool.
CLIENT_CLOSED_REQUEST
499. Your client closed the connection before we answered. You only see this in logs, since nobody was listening for the response. Give slow endpoints a generous client timeout.
INTERNAL_ERROR
500. An unexpected error on our side. Nothing was charged. Retry with backoff, and if it keeps happening, contact support with the request_id.
SERVICE_UNAVAILABLE
503. We could not complete the call right now. Causes include being unable to verify your key or read your balance for a moment, or temporarily running out of capacity. Nothing was charged. When Retry-After is present (often 30), wait that long; otherwise retry with backoff.
UPSTREAM_ERROR
503. The platform did not answer, even after our own retries and a second data source where one exists. Nothing was charged. Retry-After is at least 30, longer when the platform asked for more.
UPSTREAM_INVALID
503. The platform answered, but with something that does not fit the response schema, so we did not serve it. A page whose rows all lack an id is also refused this way, rather than charged as an empty page. Nothing was charged. Retry-After is 30. If it repeats on the same input, contact support with the request_id.
ENDPOINT_UNAVAILABLE
503. The endpoint exists but is switched off, because its data source stopped returning data for every input. It is left out of GET /v1/endpoints and the API reference until it works again. Nothing was charged. Retry-After is 3600: retrying sooner gets the same answer, so a generic retry loop should honour it or stop. We check these endpoints daily and put them back as soon as they answer.
Parameter errors
400, or another 4xx. A required parameter is missing, a value is in the wrong format, or the endpoint does not accept a parameter you sent. The message says what was rejected and usually names the parameter to fix. Check the endpoint's parameters on its API reference page or in GET /v1/endpoints. Nothing is charged.
Idempotency
Send an Idempotency-Key header, a fresh UUID per logical operation, to make any call safe to retry after a timeout or a dropped connection.
- A retry with the same key returns the original result with
idempotent_replay: true,charge_reason: "replay"andcredits_used: 0. - A retry that arrives while the original is still running gets
409 IDEMPOTENCY_IN_PROGRESS. Wait forRetry-Afterand try again with the same key. - A key reused for a different request, or across schemas, gets
409 IDEMPOTENCY_KEY_REUSED. Use a new key. - Keys are scoped to your account.
- A key longer than 255 characters, or with characters other than letters, digits,
.,_,:and-, is ignored. The call then runs normally, with no replay protection. - If we never answered the original successfully (for example your client disconnected before it finished), the retry that delivers the data is charged as a normal call (
charge_reason: "miss") at exactly the ceiling the call reserved: the listed price, or the top of the range for a metered endpoint, multiplied bymax_pagesif you sent it. Like any charged call, it can be covered by one of your free calls. It costs 0 if the replay returns nothing.
What is never charged
Every error on this page costs 0 credits, including a request refused for an unsupported parameter or a bad cursor. So do empty results, dry_run=1 calls and replays of a call we already answered. API credits are non-refundable, and a call that fails is simply never charged. See Credits.
Retry loops
Each loop retries only the transient statuses (429, 500, 503, and 409 when the type is IDEMPOTENCY_IN_PROGRESS), honors Retry-After when it is sent, otherwise backs off exponentially with jitter, and stops after five retries.
#!/usr/bin/env bash
url="https://api.insightsocial.app/v1/tiktok/profile?handle=khaby.lame"
max_retries=5
attempt=0
while :; do
body=$(mktemp); headers=$(mktemp)
status=$(curl -s -o "$body" -D "$headers" -w '%{http_code}' \
-H "x-api-key: $INSIGHTSOCIAL_API_KEY" "$url")
if [ "$status" -lt 400 ]; then
cat "$body"; rm -f "$body" "$headers"; break
fi
retry_409=false
if [ "$status" = "409" ] && grep -q '"IDEMPOTENCY_IN_PROGRESS"' "$body"; then retry_409=true; fi
case "$status" in
429|500|503) ;;
409) $retry_409 || { echo "Not retryable: 409"; cat "$body"; rm -f "$body" "$headers"; exit 1; } ;;
*) echo "Not retryable: $status"; cat "$body"; rm -f "$body" "$headers"; exit 1 ;;
esac
attempt=$((attempt + 1))
if [ "$attempt" -gt "$max_retries" ]; then
echo "Giving up after $max_retries retries (last status $status)"
rm -f "$body" "$headers"; exit 1
fi
wait_s=$(grep -i '^retry-after:' "$headers" | tr -d '\r' | awk '{print $2}')
if [ -z "$wait_s" ]; then
wait_s=$(awk "BEGIN{srand(); print (2 ^ $attempt) * (0.5 + rand() / 2)}")
fi
rm -f "$body" "$headers"
sleep "$wait_s"
doneDebugging tips
- Log
request_id(also sent asX-Request-Id) with every failure, so support can trace the exact call. - A
503on an input that worked before is usually temporary. Retry afterRetry-After. - A
400that nameserror.paramtells you exactly which parameter to remove or fix. - Some calls take a while to answer. Set a client timeout of a minute or more on heavy endpoints rather than aborting early.