Errors

Every error is { "error": { "code", "message", "requestId" } } with one of 23 stable codes; branch on code, show message to people, and quote requestId to support.

View as MarkdownOpenAPI specAll docs for LLMs

Shape

{
  "error": {
    "code": "not_found",
    "message": "No app `klavyio` in the catalogue. GET /v1/apps lists them.",
    "requestId": "6f1c2a9e-4b7d-4e2a-9c51-0d3b8e7f2a14"
  }
}
  • code is the contract: codes are added, never renamed. message is for people and may change.
  • requestId is also in the X-Request-Id header of every response.
  • Some errors add fields: a 402 carries offers (see Credits), purchase_not_allowed adds settings, a 405 sends an Allow header.
  • Errors never cost credits, except store lookups past the free-miss allowance (404 on GET /v1/companies/{domain}, 1 credit, shown in X-Credits-Charged).

Codes

Status Code Meaning What to do Retry
400 invalid_request A parameter or body field is missing or malformed. Fix the request; message names the field. Do not retry unchanged. No
400 invalid_cursor The cursor is not one this endpoint issued (edited, truncated, or from another list). Pass pagination.nextCursor back unchanged, or start again without a cursor. No
400 invalid_email Sign-up refused the address (malformed or disposable). Use a real work email. No
401 missing_api_key No key was sent. Send Authorization: Bearer sfs_… or X-API-Key: sfs_…. No
401 invalid_api_key The key is not one we issued. Check for truncation or whitespace; keys are sfs_ plus 32 letters and digits. No
401 api_key_revoked The key was revoked. Make a new key at installgraph.com/account. No
402 insufficient_credits The balance cannot cover one row or lookup. Nothing was charged. Buy credits: error.offers.pack.buyUrl, or POST /v1/billing/purchase if agent purchases are on. No
402 usage_cap_reached The account unlocked 40,000 distinct stores in the last 30 days. Wait for the window to roll, or Talk to us: error.offers.talkToUs.url. No
402 spend_cap_reached Purchases would pass $1,000 in 30 days. Talk to us for more: error.offers.talkToUs.url. No
402 paid_plan_required Webhooks need an account that has bought a credit pack. Buy any pack: error.offers.pack.buyUrl. Then create the webhook again. No
402 payment_failed The saved card was declined. Ask the account owner to update the card at installgraph.com/account. No
403 insufficient_scope The key lacks this endpoint's scope. Use a key with the scope the endpoint lists (GET /v1/usage shows yours). No
403 api_key_suspended The key is suspended. Talk to us, quoting requestId. No
403 purchase_not_allowed Agent purchases are off for this account, the pack is over the owner's limits, or the key has no account. Hand error.offers.pack.buyUrl to a person; the owner sets limits at error.settings. No
404 not_found No such endpoint, app, or store (or not scanned yet). Check the slug or domain. Store 404s are free within the lookup-miss allowance. No
405 method_not_allowed Wrong HTTP method for this path. Use the method in the Allow header. No
409 account_exists That email already has an account. Sign in at installgraph.com/login to make more keys. No
409 no_payment_method No card is saved, so nothing can be bought off-session. Buy a pack once at installgraph.com/account to save a card. No
409 webhook_limit_reached The account already has 25 active webhooks. Delete one with DELETE /v1/webhooks/{id}, or Talk to us: error.offers.talkToUs.url. No
429 rate_limited Too many requests this minute. Wait Retry-After seconds, then retry. Yes
500 internal_error Something failed on our side. Nothing was charged. Retry with backoff; quote requestId if it persists. Yes
503 not_configured A feature this request needs (such as billing) is unavailable. Retry later. Yes
503 service_unavailable The API is briefly unavailable. Retry with backoff. Yes

Handling them

async function call(url, init = {}, attempt = 0) {
  const res = await fetch(url, {
    ...init,
    headers: { Authorization: `Bearer ${process.env.INSTALLGRAPH_API_KEY}`, ...init.headers },
  });
  if ((res.status === 429 || res.status >= 500) && attempt < 5) {
    const wait = Number(res.headers.get("Retry-After")) || 2 ** attempt;
    await new Promise((r) => setTimeout(r, wait * 1000));
    return call(url, init, attempt + 1);
  }
  const body = await res.json();
  if (!res.ok) throw Object.assign(new Error(body.error.message), body.error);
  return body;
}

try {
  const { data } = await call("https://api.installgraph.com/v1/companies/example.com");
} catch (err) {
  if (err.code === "not_found") { /* not a store we know: skip it */ }
  else if (err.code === "insufficient_credits") { /* show err.offers.pack.buyUrl */ }
  else throw err;
}