Requests and errors

The rules every endpoint follows, so a client written for one resource works for the next.

Ids

Every id is an opaque string with a type prefix, such as clm_0a6NnX3I9. Store them as strings and never parse them; the prefix only tells you what kind of object it names.

PrefixObject
clm_Claim
rom_Room
itm_Item (one inventory line)
rpl_Replacement offer for an item
pho_Photo
exp_Export (XLSX or PDF)
whe_Webhook endpoint
evt_Webhook event
org_Organization

Claims also take your own id as externalRef. Filter by it with ?externalRef= to find the claim for one of your records without storing our id.

Money, rates and time

  • Money is an integer number of US cents, in fields ending Cents: 129999 is $1,299.99.
  • Rates are integer basis points, in fields ending Bp: 881 is 8.81%, 10000 is 100%.
  • Timestamps are ISO 8601 in UTC (2026-10-24T18:40:52.000Z); calendar dates are YYYY-MM-DD.

Integers on both sides mean no floating-point rounding between your system and ours.

Pagination

Lists return { "object": "list", "data": [...], "hasMore", "nextCursor" }. Ask for up to 100 at a time with limit (default 25), and pass nextCursor back as cursor until hasMore is false.

JavaScript
let cursor;
do {
  const url = new URL('https://contents.team/api/v1/claims');
  url.searchParams.set('limit', '100');
  if (cursor) url.searchParams.set('cursor', cursor);
  const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.CONTENTS_API_KEY}` } });
  const page = await res.json();
  for (const claim of page.data) handle(claim);
  cursor = page.hasMore ? page.nextCursor : undefined;
} while (cursor);

Syncing changes

To keep a copy in step, note the time before you start, page through with updatedSince set to your last sync time, and use the noted time next run. Overlap is harmless; a gap is not.

Idempotency

Every POST accepts an Idempotency-Key header: 1 to 255 visible ASCII characters you choose, unique per operation (a UUID, or your own record id plus the action). Retrying with the same key and the same body returns the first response instead of acting twice, so a timeout or a dropped connection is always safe to retry. Reusing a key with a different body is a 409. The MCP server's write tools take the same key as an idempotencyKey argument.

Header
Idempotency-Key: job-1042-create

Errors

Every error has the same envelope. Branch on type and code, which are stable; message is for people and may change. Quote requestId (also the X-Request-Id header on every response) when you write to us.

Error body
{
  "error": {
    "type": "permission_error",
    "code": "insufficient_scope",
    "message": "This API key is missing a required scope: claims:read.",
    "details": {
      "required": [
        "claims:read"
      ]
    },
    "requestId": "req_5e0c1f7a2b9d44c08a61e3f2"
  }
}
StatustypeWhat to do
400invalid_request_errorFix the request. details lists each bad parameter.
401authentication_errorThe key is missing, wrong, revoked or expired.
403permission_errorThe key lacks a scope (code insufficient_scope), is a live key or OAuth app waiting for approval (code live_key_pending_approval or oauth_app_pending_approval), or the action is not allowed.
404not_found_errorNo such object in this organization.
409conflict_errorThe request conflicts with the current state; read it again, then retry.
429rate_limit_errorSlow down; retry after the Retry-After seconds.
500api_errorOur fault. Retry with backoff; it is safe with an Idempotency-Key.

Rate limits

  • 300 requests a minute per API key.
  • 600 requests a minute per IP address, across keys.
  • 60 review writes a minute per API key (approve, unapprove, reject and accepting a replacement, REST and MCP together), inside the 300.

Over either limit you get a 429 with a Retry-After header in seconds. Back off for at least that long, then retry. If an integration needs more, tell us what it does and we will raise it.

NextCompanyCamSync CompanyCam projects and photos into claims, and write item details back.