vlayervlayer docs
Using the API

Errors

Every error code a data lookup can return, whether it is worth retrying, how long to wait, and what to send us when you need help.

Every error returns the same envelope: a stable error code and a human-readable message. Branch on error, never on message, which may be reworded.

{
  "error": "PROFILE_UNAVAILABLE",
  "message": "This profile does not exist or has been deactivated."
}

A request answered with an error is not billed.

Permanent errors

These fail the same way again. Do not retry the same request unchanged.

errorHTTPMeaning and what to do
UNAUTHORIZED401The token is missing, unknown, expired or revoked, or belongs to the other environment. Check the Authorization header
CUSTOMER_SUSPENDED403Your account is suspended. Contact us
INSUFFICIENT_SCOPE403The route needs a token with more rights than yours, for example creating a token. Ask us to do it
PROFILE_RESTRICTED403/v1/profile only: the platform withholds this profile from public lookups, including a private account that could not be read
POST_NOT_FOUND404The post no longer exists at that URL
PROFILE_UNAVAILABLE404/v1/profile only: the account does not exist or was deactivated
VALIDATION_ERROR422A query parameter or the request body is missing or malformed, or the URL is not a supported post URL. Nothing was read. Correct the request
POST_NOT_ACCESSIBLE422The post is private or otherwise not public
POST_AGE_RESTRICTED422The post is age-restricted and could not be read, even with reduced detail. Only its author can fix this for good, by reposting without the restriction

POST_* and PROFILE_* codes are permanent for that post or profile, not forever: a post made public again, or a profile reactivated, reads normally, though not always straight away.

Retryable errors

These are temporary. Retry the same request after the wait shown.

errorHTTPRetry-AfterWhen to retry
RATE_LIMITED429AlwaysYou went over a rate limit, or refreshed one challenge's metrics again within 5 minutes. Wait for Retry-After
SCRAPING_RATE_LIMITED429AlwaysReads are temporarily rate limited, whatever your own usage. Wait for Retry-After
INTERNAL_ERROR500NoAn unexpected error on our side. Back off from a few seconds; contact us if it persists
SCRAPING_ERROR502SometimesThe post or profile could not be read, for an unclassified reason. Back off from a few seconds
SCRAPING_UNAVAILABLE503SometimesReads are temporarily unavailable. Back off from about 30 seconds
UPSTREAM_TIMEOUT504NoThe request timed out. Retry after a few seconds, then back off
BATCH_TIMEOUT——Batch only, in a result's error.code: the URL was not finished within the batch's 100 seconds. Send it again in a smaller batch or with GET /v1/stats

Whenever a response carries Retry-After, wait at least that many seconds, even on a code the table marks "Sometimes". Otherwise use exponential backoff with jitter: double the wait after each failure, add a random fraction of it, cap it at about five minutes, and give up after five or six attempts.

In a batch, each failed URL carries one of these codes in its result's error.code, an object rather than the plain error string of a single call. The batch itself still answers 200.

Routes that return these codes

On the engagement verification routes:

Liker fetching reports failures on the run's status and errorMessage instead; see Instagram likers.

Handling pattern

This function sorts a response into a result, a retry or a permanent failure. Not every error body is the API's JSON envelope: a 502 from a load balancer in between, for example, is not, so the status code decides when there is no error to read.

const RETRYABLE = new Set([
  "RATE_LIMITED",
  "SCRAPING_RATE_LIMITED",
  "INTERNAL_ERROR",
  "SCRAPING_ERROR",
  "SCRAPING_UNAVAILABLE",
  "UPSTREAM_TIMEOUT",
]);

type Outcome =
  | { kind: "ok"; body: unknown }
  | { kind: "retry"; retryAfterSeconds?: number }
  | { kind: "permanent"; error: string };

async function classify(res: Response): Promise<Outcome> {
  if (res.ok) return { kind: "ok", body: await res.json() };

  const retryAfter = Number(res.headers.get("Retry-After"));
  const { error } = await res.json().catch(() => ({ error: undefined }));
  const retryable = error ? RETRYABLE.has(error) : res.status === 429 || res.status >= 500;
  if (retryable) return { kind: "retry", retryAfterSeconds: retryAfter > 0 ? retryAfter : undefined };

  // POST_*, PROFILE_*, VALIDATION_ERROR and auth errors: permanent
  return { kind: "permanent", error: error ?? `HTTP ${res.status}` };
}

For the waiting and retrying around it, use the tested read() function in High-volume integration.

Get help with a request

Every response carries an X-Request-Id header. Log it next to the error, and quote it when you contact us about a request: it is how we find that request on our side. You can also send your own X-Request-Id, up to 128 letters, digits, _ or -, and the API uses it instead of generating one, so the id in your logs and ours is the same.

Last updated on