InsightSocial API

Response schema

The InsightSocial API envelope under schema 2, what sits inside data, pagination, response headers, idempotent replays and fresh fetches.

Every data endpoint answers in one success envelope, and every failure in one error envelope, so a single parser handles every platform. The two share only a few fields: check success first, then read the body as one or the other. The two account endpoints are the exception: GET /v1/credits returns a balance body of its own (it still carries success, credits_used and credits_remaining), and GET /v1/endpoints returns the catalogue in its own shape.

This page describes schema 2, the shape every key gets from October 4, 2026. What changed from the previous shape is on Schema 2.

Successful response

Response
{
  "success": true,
  "platform": "instagram",
  "endpoint": "/v1/instagram/profile",
  "schema_version": "2",
  "data": {
    "author": {
      "id": "528817151",
      "username": "nasa",
      "display_name": "NASA",
      "followers": 104294818,
      "followers_approximate": null,
      "url": "https://www.instagram.com/nasa/",
      "external_url": "https://www.nasa.gov",
      "last_post_at": "2026-10-03T14:10:18.000Z"
    }
  },
  "unavailable": [],
  "credits_used": 10,
  "credits_remaining": 9990,
  "request_id": "req_d6ad52ab1f9f",
  "cached": false,
  "idempotent_replay": false,
  "charge_reason": "miss",
  "free_call": false
}

The profile is trimmed here. Every field of each entity is on its unified schema page.

Envelope fields

These are the fields of a successful response. An error carries far fewer; see Error response.

FieldTypeOn every successDescription
successbooleanyestrue. Also on every error, as false: branch on it first.
platformstringyesThe platform you called, such as instagram.
endpointstringyesThe path you called, such as /v1/instagram/profile.
schema_versionstringyes"2". The contract data follows. An error never carries it.
dataobjectyesThe result. Its shape depends on the endpoint; see Inside data.
paginationobjectnoList endpoints only. See List endpoints.
unavailablestring[]yesFields this platform normally fills that this response could not, as paths into data. Empty when nothing is missing. See Data availability.
credits_usedintegeryesCredits charged for this call. 0 for replays, free calls, empty results, dry_run=1 and failures.
credits_remainingintegeryesYour balance after this call.
request_idstringyesOur identifier for this call, in the form req_ plus 12 hex characters.
cachedbooleanyestrue when the result came from the shared cache rather than a live fetch.
idempotent_replaybooleanyestrue when this is a replay of an earlier call with the same Idempotency-Key.
charge_reasonstringyesWhy credits_used has the value it has. See below.
free_callbooleanyestrue when the call used one of your free calls, so credits_used is 0.

charge_reason

ValueMeaningCredits
missA live fetchThe endpoint's price, or for a metered endpoint what the call used
shared_cacheServed from the shared cache5
replayAn idempotent replay of a call you already made0
no_resultNothing to charge for, such as an empty result0
dry_runA dry_run=1 price quote. No data was fetched0

The pricing rules are on Credits.

Inside data

data holds one entity, or a page of them, under a key named after the entity:

Endpoint returnsdata holdsSchema
One postdata.postPost
A list of postsdata.items[].postPost
One profiledata.authorAuthor and profile
A list of profilesdata.items[].authorAuthor and profile
One commentdata.commentComment
A list of commentsdata.items[].commentComment
A transcriptdata.transcriptTranscript

A list also carries these fields next to items:

  • data.total: the platform's own total, when it states one (a comment count, for example). It is not the size of this page, and it is null when the platform does not say.
  • data.truncated: true when the list is known to be cut short by the platform, false when the platform says it is complete, null when we cannot tell. On a Reddit comment tree it is true once the tree reaches Reddit's ten-level depth limit and otherwise null: Reddit leaves some branches behind "load more" links that the source does not count. To judge how complete a Reddit thread is, compare with the post's engagement.comments from /v1/reddit/post, which also counts deleted and removed comments. On /v1/instagram/post/likers it is true when fewer likers came back than the post has likes: Instagram serves one slice of the list, with no next page.
  • data.ext: what the page says about itself beyond its rows, under the same conventions as an entity's ext (snake_case keys, string ids, ISO times). On /v1/instagram/audio/reels, for example, it carries the sound the reels use (ext.sound, with its title, artist and duration) and ext.is_trending. Present only when the platform sends something; absent otherwise, so treat it as optional.

The same rules hold everywhere inside data:

  • Ids are strings. Several platforms use ids above 2^53, which a JSON number would corrupt. Never parse an id as a number.
  • Times are ISO 8601 in UTC, ending in Z.
  • null means not known. A count the platform hides is null, never 0. 0 means zero.
  • content.media_urls is always an array (or null), never a bare string.
  • ext keys are snake_case. ext holds what one platform carries that the others cannot mean the same way. Treat every ext key as optional.

Some long-tail endpoints (metrics, and lists with no matching entity) are marked beta on their reference page. They return the source's own structure under our naming rules: snake_case keys, string ids, and ISO times where the source gives a timestamp. Some pass on figures the source itself computes, such as engagement_rate_percentages and likes_per_hour on /v1/instagram/engagement or a confidence on /v1/youtube/video/sponsors, and a source's own date text (a local datetime, a time_ago) stays as the source wrote it. Their fields may change.

profile/full is beta too, but its profile and posts are typed: data.profile is a profile and each data.posts[].post is a post. It pages its posts with cursor like a list. Any other key under its data follows the beta rules above.

The typed entities above carry no analysis of ours: no engagement rate, labels or relevance scores. Compute what you need from the raw counts; see Schema 2 for what was removed and the creator engagement recipe for an engagement rate built from engagement and the author's followers.

One schema

Every key gets schema 2, and it is the only shape served. An error has no schema_version field. See Schema 2 for what changed from the previous shape.

Error response

An error is a different, smaller body. It has no platform, endpoint, schema_version, data, unavailable, cached or charge_reason, so read success before anything else:

Response
{
  "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
}
FieldDescription
error.typeA stable string to branch on.
error.messageA readable explanation, often naming what to fix. Do not parse it.
error.paramThe query parameter at fault, when there is one.
request_idQuote it to support.
credits_usedAlways 0: failed calls are never charged.
credits_remainingYour balance when we know it, otherwise null (for example on authentication failures, or on a request refused before we read your balance).

That is the whole body: success, error (type, message, and param when one parameter is at fault), request_id, credits_used and credits_remaining.

Node.js
const body = await res.json();
if (!body.success) {
  // { error: { type, message, param? }, request_id, credits_used, credits_remaining }
  throw new Error(`${body.error.type}: ${body.error.message} (${body.request_id})`);
}
// Only now: body.schema_version, body.data, body.unavailable, body.pagination

Not every error is in your Activity Log. A request refused before it reaches a platform writes no log row: 400 UNSUPPORTED_PARAMETER, 400 CURSOR_INVALID and CURSOR_EXPIRED, authentication and rate-limit errors, idempotency conflicts and 402 INSUFFICIENT_CREDITS. dry_run=1 quotes are not logged either. Other errors, including the 400 parameter errors a platform returns, are logged with their status.

The full list of types is on Errors.

Response headers

HeaderValue
X-Request-IdSame as request_id in the body.
X-Credits-UsedCredits charged for this call.
X-Credits-RemainingYour balance after the call. Omitted when it is unknown, never sent as a placeholder 0.
Retry-AfterSeconds to wait. Sent on 429, on 409 IDEMPOTENCY_IN_PROGRESS and on some 503 responses.
Cache-Controlno-store. Do not cache API responses in a shared proxy.

CORS headers are also set, and X-Request-Id, X-Credits-Used, X-Credits-Remaining and Retry-After are exposed to browser code. Keys still belong on your server; see Authentication.

List endpoints

List responses put their rows in data.items and carry a pagination object at the root of the envelope:

Response
{
  "success": true,
  "platform": "twitter",
  "endpoint": "/v1/twitter/user/tweets",
  "schema_version": "2",
  "data": {
    "items": [
      { "post": { "id": "2106502020849127865", "kind": null, "author": { "id": null, "username": "NASA" } } }
    ],
    "total": null,
    "truncated": null
  },
  "pagination": {
    "next_cursor": "v2c.M-GEP84JXNZPqhQ0NlEOubhbuFGGiCK9…",
    "has_more": true,
    "page_size": 19
  },
  "unavailable": ["items[].post.author.id"],
  "credits_used": 10,
  "credits_remaining": 9980,
  "request_id": "req_161a29ec13ca",
  "cached": false,
  "idempotent_replay": false,
  "charge_reason": "miss",
  "free_call": false
}

Here unavailable says that no row on this page carries an author id, though the platform has one. The rows are still valid; plan for author.id being null on this endpoint.

FieldDescription
next_cursorSend it back as the cursor query parameter to get the next page. Starts with v2c.; pass it unchanged. null on the last page.
has_morefalse on the last page. Stop there.
page_sizeRows on this page.
stopped_atOnly on creator post lists called with since or stop_at_id. Says which boundary ended the walk: since, known_id or end.

The cursor is only valid as cursor, with the same endpoint and parameters, for 24 hours. The full loop is on Pagination.

Idempotent requests

Send an Idempotency-Key header to make a call safe to retry. Use a fresh UUID for each logical operation.

cURL
curl "https://api.insightsocial.app/v1/tiktok/profile?handle=khaby.lame" \
  -H "x-api-key: $INSIGHTSOCIAL_API_KEY" \
  -H "Idempotency-Key: c3f9d27e-81b4-4e06-a5d2-94be0f6c1a78"
  • A replay returns idempotent_replay: true, charge_reason: "replay" and credits_used: 0.
  • If the original call with the same key is still running, you get 409 IDEMPOTENCY_IN_PROGRESS with Retry-After. Wait, then send the same key again.
  • Keys are scoped to your account. Reusing a key for a different request, or with a different schema, answers 409 IDEMPOTENCY_KEY_REUSED.
  • A key must be at most 255 characters of letters, digits, ., _, : or -. A key that does not fit is ignored, and the call runs as an ordinary request.

Force a fresh fetch

Repeat calls can be served from the shared cache (cached: true) at 2 credits. To skip it and fetch live, send Cache-Control: no-cache, or add fresh=1 to the query:

cURL
curl "https://api.insightsocial.app/v1/tiktok/profile?handle=khaby.lame" \
  -H "x-api-key: $INSIGHTSOCIAL_API_KEY" \
  -H "Cache-Control: no-cache"

A fresh fetch is always charged at the normal price. See Credits for the shared cache.

Next steps

On this page