Docs / Response Format and the Envelope

Response Format and the Envelope

Every endpoint, one response shape. Parse it once and every future endpoint is already handled.

Maintained by the InScrape API team · Last reviewed 2026-09-19

The success envelope

A 200 always looks like this. The endpoint-specific payload is under data and nowhere else; the metadata beside it is identical across every endpoint, which is what lets you write one HTTP wrapper and reuse it for the whole catalogue.

{
  "success": true,
  "credits_charged": 1,
  "credits_remaining": 843,
  "processing_time_ms": 1842,
  "requested_at": "2026-09-13T14:32:18Z",
  "query": { "username": "natgeo" },
  "data": { "handle": "natgeo", "follower_count": 279000000 }
}

When a collection has no results

A successful lookup with zero items still returns HTTP 200. data contains the endpoint's list as an empty array, credits_charged is the endpoint rate, and credits_remaining reflects that charge. Exception: a successful cursor request returning zero items costs 0 on posts, reels-posts, reposts-posts, tagged-posts, comments, popular and hashtag. Their first requests still cost the endpoint rate even when empty. Paginated endpoints include their cursor as null; endpoints without pagination omit the cursor. Internal no-results flags are not part of the response.

{
  "success": true,
  "credits_charged": 0.6,
  "credits_remaining": 842.4,
  "processing_time_ms": 3201,
  "requested_at": "2026-09-27T11:53:43.584Z",
  "query": { "username": "example_user" },
  "data": { "reposts_posts": [], "next_cursor": null, "has_next_page": false }
}

The error envelope is a different shape

This trips people up more than anything else in the API. Error responses have no data key. They carry success false, an error object with a machine-readable code and a sentence of explanation, and the two credit fields. Reading body.data on a non-200 gives you undefined rather than an exception, which is exactly the kind of bug that surfaces three screens later, so branch on res.ok before you touch the payload.

{
  "success": false,
  "error": {
    "code": "INVALID_PARAMS",
    "message": "Invalid or missing parameters."
  },
  "credits_charged": 0,
  "credits_remaining": 843
}

requested_at and query

requested_at is the UTC timestamp for when we received the request. query repeats the lookup that triggered the response, using customer-facing names: handle is shown as username, and optional include fields appear only when they were sent as true.

{
  "requested_at": "2026-09-13T14:32:18Z",
  "query": {
    "username": "natgeo",
    "include_profile": true,
    "include_highlights": true
  }
}

credits_charged and credits_remaining

credits_charged is what this specific request cost: 1 on most endpoints, 2 on followers and following, and the endpoint rate for private-account (200) and missing-target (200) results. Successful empty first requests are charged at the endpoint rate. Exception: a successful cursor request returning zero items costs 0 on posts, reels-posts, reposts-posts, tagged-posts, comments, popular and hashtag. Their first requests still cost the endpoint rate even when empty. Invalid requests, authentication failures and service errors cost 0. Comments with include_replies add 0.5 per reply request. reply_initial_requests counts initial reply page requests, reply_cursor_requests counts additional reply page requests, reply_total_requests adds both, and reply_total_credits is their cost before the 1-credit base fee. These values apply only to this GET. credits_remaining is your team balance, including fractional credits, reported on every response. On a 401 it is 0 because no team is identified.

A wrapper worth writing once

Roughly twenty lines covers the whole API. Everything after this is a different path and a different set of query parameters.

const BASE = "https://api.socialscrape.dev";

export async function call(path, params = {}) {
  const url = new URL(path, BASE);
  for (const [k, v] of Object.entries(params)) url.searchParams.set(k, String(v));

  const res = await fetch(url, {
    headers: { "x-api-key": process.env.INSCRAPE_KEY },
  });
  const body = await res.json();

  if (!res.ok) {
    const err = new Error(body.error.message);
    err.code = body.error.code;
    err.status = res.status;
    throw err;
  }

  return {
    data: body.data,
    cost: body.credits_charged,
    requestedAt: body.requested_at,
    processingTimeMs: body.processing_time_ms,
    query: body.query,
  };
}

const profile = await call("/v1/instagram/profile", { handle: "natgeo" });

Envelope keys

KeyTypePresent onMeaning
successbooleanevery responsetrue only on HTTP 200
credits_chargednumberevery responseEndpoint rate on successful lookups, including empty first requests, and private-account/not-found lookups; 0 on eligible empty cursor pages, invalid requests and service errors
credits_remainingnumberevery responseTeam balance after this request. 0 on a 401, where no team is identified
processing_time_msinteger200 onlyHow long this request took to process, in milliseconds
requested_atstring200 onlyUTC ISO 8601 timestamp for when the request started
reply_initial_requestsintegercomments 200 onlyInitial reply page requests in this GET
reply_cursor_requestsintegercomments 200 onlyAdditional reply page requests in this GET
reply_total_requestsintegercomments 200 onlyInitial plus cursor reply requests
reply_total_creditsnumbercomments 200 onlyReply request cost at 0.5 each, waived when this GET returns no replies; excludes the 1-credit base fee
queryobject200 onlyThe submitted lookup, normalized for customer-facing field names
dataobject200 onlyThe endpoint payload, including its pagination cursor on list endpoints
error.codestringerrors onlyOne of the six documented codes, plus UPSTREAM_TIMEOUT
error.messagestringerrors onlyA sentence that always states whether anything was charged

FAQ

Is the response shape the same on every endpoint?

Yes for the envelope. The contents of data differ per endpoint and are documented on each endpoint reference page.

Why is there no data key on errors?

Because there is no data. An error body carries the code, the message and the two credit fields, and nothing else.

Can I get the raw Instagram response?

You get what the endpoint's reference page documents. There is no separate passthrough mode. What we never do is invent fields: if a value is not in the public response, it is not in ours.

Related