# Contents.team API

> Photos in, an itemized, priced contents inventory out. A REST API, webhooks and an MCP server for claims, restoration and job-management software.

Contents.team turns property-claim photos into a reviewable contents inventory: each photo is analyzed, the visible items are drafted as lines with room, category, quantity and condition, every line gets a sourced replacement price, and depreciation gives RCV and ACV. The API lets other software create claims, send photos, read the priced inventory and export reports.

## Conventions

- Base URL: https://contents.team. Every endpoint is under /api/v1.
- Auth: `Authorization: Bearer ctk_test_...` or `ctk_live_...`, an org API key from Settings -> Connections -> API keys (owners and admins).
- Ids are prefixed opaque strings: clm_ (claim), rom_ (room), itm_ (item), rpl_ (replacement offer), pho_ (photo), exp_ (export), whe_ (webhook endpoint), evt_ (event), org_ (organization).
- Money is integer cents (`...Cents`); rates are basis points (`...Bp`, 10000 = 100%). Timestamps are ISO 8601 UTC.
- Lists are cursor-paginated: `limit` (1-100, default 25) and `cursor` = the previous page's `nextCursor`; `hasMore` says whether there is another page.
- POST requests accept `Idempotency-Key`; retrying with the same key returns the first result.
- Errors: `{ "error": { "type", "code", "message", "details"?, "requestId" } }`. Branch on `type` and `code`; every response has an `X-Request-Id` header.
- Rate limits: 300 requests per minute per key and 600 per minute per IP; a 429 carries `Retry-After`.
- Test mode (ctk_test_ keys) runs a canned pipeline and is never billed.
- Review: with items:write, edit items (PATCH /api/v1/items/{itemId}), accept replacement offers and approve items (POST /api/v1/claims/{claimId}/items/approve). Only approved items count toward totals and exports; API approvals are metered exactly like approvals in the app.

## Scopes

- `claims:read`: List and read claims
- `claims:write`: Create and update claims
- `photos:read`: List and read photos
- `photos:write`: Add photos to claims
- `items:read`: Read inventory items and prices
- `exports:write`: Generate report exports
- `webhooks:manage`: Manage webhook endpoints
- `items:write`: Edit, add, delete, approve and reject items

## Endpoints

- `DELETE /api/v1/items/{itemId}`: Delete an item
- `GET /api/v1/items/{itemId}`: Get an item
- `PATCH /api/v1/items/{itemId}`: Update an item
- `DELETE /api/v1/webhook-endpoints/{endpointId}`: Delete a webhook endpoint
- `GET /api/v1/webhook-endpoints/{endpointId}`: Get a webhook endpoint
- `GET /api/v1/claims`: List claims
- `POST /api/v1/claims`: Create a claim
- `GET /api/v1/claims/{claimId}`: Get a claim
- `PATCH /api/v1/claims/{claimId}`: Update a claim
- `GET /api/v1/claims/{claimId}/categories`: List the categories items on a claim can take
- `GET /api/v1/claims/{claimId}/items`: List a claim's items
- `POST /api/v1/claims/{claimId}/items`: Add an item
- `GET /api/v1/claims/{claimId}/photos`: List a claim's photos
- `POST /api/v1/claims/{claimId}/photos`: Add photos by URL
- `GET /api/v1/claims/{claimId}/rooms`: List a claim's rooms
- `GET /api/v1/exports/{exportId}`: Get an export
- `GET /api/v1/items/{itemId}/replacements`: List an item's replacement offers
- `GET /api/v1/me`: The calling credential and its organization
- `GET /api/v1/openapi.json`: This OpenAPI document
- `GET /api/v1/photos/{photoId}`: Get a photo
- `GET /api/v1/webhook-endpoints`: List webhook endpoints
- `POST /api/v1/webhook-endpoints`: Register a webhook endpoint
- `GET /api/v1/webhook-endpoints/{endpointId}/deliveries`: List an endpoint's deliveries
- `POST /api/v1/claims/{claimId}/exports`: Start a report export
- `POST /api/v1/claims/{claimId}/items/approve`: Approve items
- `POST /api/v1/claims/{claimId}/items/reject`: Reject items
- `POST /api/v1/claims/{claimId}/items/unapprove`: Unapprove items
- `POST /api/v1/claims/{claimId}/photo-uploads`: Start a direct photo upload
- `POST /api/v1/claims/{claimId}/photo-uploads/complete`: Finish a direct photo upload
- `POST /api/v1/items/{itemId}/price-from-link`: Price an item from a product link
- `POST /api/v1/items/{itemId}/replacements/{replacementId}/accept`: Accept a replacement offer
- `POST /api/v1/webhook-endpoints/{endpointId}/deliveries/{deliveryId}/replay`: Replay a delivery
- `POST /api/v1/webhook-endpoints/{endpointId}/test`: Send a test event

## Machine-readable

- [OpenAPI 3.1 (JSON)](https://contents.team/api/v1/openapi.json)
- [OpenAPI 3.1 (YAML)](https://contents.team/openapi/v1.yaml)
- [Changelog (JSON)](https://developers.contents.team/changelog.json)
- [MCP manifest](https://developers.contents.team/mcp.json)

## MCP server

Streamable HTTP at https://contents.team/api/mcp, stateless, authenticated with an org API key or OAuth access token as a bearer token (a 401 points OAuth clients at /.well-known/oauth-protected-resource/api/mcp). Tools:

- `list_claims` (claims:read): List the organization's claims, newest first, with item counts and approved RCV in cents. Filter by claimNumber (the number people call a claim by, such as EVAL-2026-0042), externalRef (your own id) or updatedSince; page with cursor.
- `create_claim` (claims:write): Open a claim with the insured, loss address, loss type and date of loss, and your own externalRef. Returns the claim and its clm_ id. Pass a fresh idempotencyKey for each new claim and reuse it when retrying, so a retry never opens a second claim.
- `add_photos` (photos:write): Add up to 10 photos to a claim by public https URL, optionally filed by room name. Each photo is analyzed and its items drafted and priced; poll get_claim_status for progress. Pass a fresh idempotencyKey for each batch and reuse it when retrying, so a retry never adds the photos twice.
- `get_claim_status` (claims:read): How far a claim has got: photos received, queued, analyzing, analyzed and failed; items drafted, awaiting review, approved and rejected; approved RCV, ACV and payment totals in cents. While photos are being analyzed, pass waitSeconds (up to 20) to wait for analysis.status to leave in_progress instead of calling again in quick succession.
- `get_inventory` (items:read): The claim's line items: room, description, quantity, condition, status, unit cost in cents, and each line's RCV, depreciation (basis points and cents) and ACV. priced and needsReview say what still needs a person. Filter by status, needsReview, priced or room; 50 items a page by default (limit up to 100), page with cursor.
- `export_report` (exports:write): Generate the XactContents-format XLSX (default), the PDF report or a photo ZIP for a claim. Waits up to 20 seconds and returns the download link when ready; otherwise call again with the returned exportId. Pass a fresh idempotencyKey for each new export and reuse it when retrying, so a retry never starts a second export.
- `get_replacements` (items:read): The replacement finder's offers for one item from its latest search, best first: title, retailer, price in cents, link and image, with proposed marking the finder's pick. Each has an rpl_ id for accept_replacement.
- `update_item` (items:write): Edit one item the way the app does: name, model, quantity, unitCostCents (integer cents; null removes the price), priceSource, priceSourceUrl, roomId (from list_rooms), category (a name or code from list_categories), condition, estimatedAgeYears, notes. Returns the item, revalued. An item still being priced is refused (item_locked). Pass a fresh idempotencyKey for each edit and reuse it when retrying.
- `approve_items` (items:write): Approve up to 1000 items of a claim, as bulk approval in the app does; only approved items count toward totals and exports. Reports each id as approved, alreadyApproved, locked (still being priced), skipped (with a reason, e.g. unpriced) or notFound. Pass a fresh idempotencyKey for each approval and reuse it when retrying.
- `accept_replacement` (items:write): Price an item from one of its offers (an rpl_ id from get_replacements) and approve it in one step, as accepting in the app does. The price and quantity come from the offer; if the listing unit count is unclear the call asks you to confirm a quantity (quantity plus quantityConfirmed: true). Pass a fresh idempotencyKey and reuse it when retrying.
- `add_item` (items:write): Add an item the AI missed, as manual add in the app does: name, optional model, quantity (default 1), unitCostCents (integer cents; omit to add it unpriced), roomId (from list_rooms), condition, estimatedAgeYears, notes. Returns the new item. idempotencyKey is required: send a fresh one per item and reuse it when retrying, so a retry never adds (and bills) a second item.
- `delete_item` (items:write): Remove one item from the inventory, as delete in the app does. To keep a record that an item was ruled out, use reject_items instead. Pass a fresh idempotencyKey and reuse it when retrying.
- `unapprove_items` (items:write): Send up to 1000 approved items of a claim back to review, as bulk unapprove in the app does. Pass a fresh idempotencyKey and reuse it when retrying.
- `reject_items` (items:write): Rule out up to 100 items of a claim (not part of the loss, a duplicate), as rejecting in the app does; rejected items stay on the claim but never count toward totals or exports. Pass a fresh idempotencyKey and reuse it when retrying.
- `list_rooms` (claims:read): The claim's rooms in display order, in one page: floors, rooms and subrooms (parentId), each with its rom_ id and direct item count. Use the rom_ ids as roomId in update_item and add_item, or to filter get_inventory.
- `get_item` (items:read): One item by its itm_ id, as get_inventory shows it: room, category, quantity, unit cost in cents, status, and its RCV, depreciation and ACV valued within the claim.
- `list_categories` (items:read): The categories an item on this claim can take, from the claim's own category system (taxonomy jmidg, or xactimate with 3-letter codes), each with its default depreciation. Pass a name (or, on xactimate claims, a code) as category in update_item.
- `update_claim` (claims:write): Correct the claim record: externalRef, claimNumber, insuredName, lossAddress, lossType, dateOfLoss, insuranceCompany, policyNumber. Send only the fields to change (null clears one); status and valuation settings are not editable here. Returns the claim. Pass a fresh idempotencyKey for each edit and reuse it when retrying.
- `get_me` (no scope): The organization, live or test mode, and scopes of the credential this connection uses. Needs no scope: use it to check what the other tools will allow.
- `get_upload_link` (claims:read): A link that opens the claim in the Contents.team app with its Add photos dialog, for a person to upload photos from their own device. Use it when you cannot send the files yourself (in a chat, say): give the link to the user, who signs in as themselves. The photos are analyzed as usual; follow them with get_claim_status. Not available for test claims, which the app does not show.
- `create_photo_upload` (photos:write): For clients that can make HTTP PUT requests (Claude Code, scripts): register up to 24 photo files (filename, contentType, exact sizeBytes, optional roomName) and get one presigned PUT URL each. PUT each file's exact bytes to its url with the given Content-Type header within 5 minutes, then call complete_photo_upload with the upl_ id and the pho_ ids you uploaded. If the URLs expired, call this again with a new idempotencyKey. In a chat, use add_photos (public URLs) or get_upload_link instead.
- `complete_photo_upload` (photos:write): After PUTting the files from create_photo_upload: checks each one arrived with the declared size and type and sends them to analysis, which drafts and prices their items; follow it with get_claim_status. Photos that never arrived are listed in failed. Pass a fresh idempotencyKey and reuse it when retrying.
- `list_photos` (photos:read): The claim's photos, oldest first: room, kind and analysis status (no download links: to see a photo, use get_photo). Filter by roomId or updatedSince; 25 a page by default (limit up to 100), page with cursor. Use get_photo to look at one.
- `get_photo` (photos:read): One photo's details plus the image itself, so you can see it: medium (default, up to 1920 px) or thumbnail (400 px). A photo uploaded moments ago may have no image yet; the result then says so. Set includeImage false for the details only.
- `create_item_photo_upload` (photos:write, items:write): For clients that can make HTTP PUT requests: add up to 10 more photos of an item that already exists (a close-up, a receipt, a label) without analyzing them, so no duplicate item is drafted and nothing is billed. Returns one presigned PUT URL per file: PUT the exact bytes with the given Content-Type within 5 minutes, then call attach_item_photos with the pho_ ids. If the URLs expired, call this again with a new idempotencyKey.
- `attach_item_photos` (photos:write, items:write): Attach up to 20 photos to an existing item as extra evidence, without analysis: photos uploaded with create_item_photo_upload (checked in storage first), or photos already on the same claim. Returns the item. An item still being priced is refused (item_locked). Pass a fresh idempotencyKey and reuse it when retrying.
- `price_from_link` (items:write): Price an item from a retailer's product page, as pasting a link in the app does: the page is read and its product name, model, store, link and price become the item's (a page with no price leaves an item that already has a price exactly as it was: its price, store and link stay unchanged). priceMethod search means the page could not be read and the price came from the same store's shopping listing: check it. When priced is false and the item has no price, ask the user for the price and set it with update_item; an item that already had a price keeps it. Set approve true to approve the item too, only when the page itself priced it (a search price is never approved: approveSkipped price_via_search). An item still being priced is refused (item_locked). Pass a fresh idempotencyKey and reuse it when retrying.
- `search_replacements` (items:write): Run the replacement finder again for one item: a fresh search across retailers, checked against the item's photo. Waits up to about 20 seconds and returns the new offers (status completed); if it is still running (status searching), poll get_replacements until searching is false. Choose an offer with accept_replacement. Approved items are refused (item_already_approved). Searches cost real money, so they are limited to a few a minute per organization: search only items that need it, one at a time. Pass a fresh idempotencyKey and reuse it when retrying.

## Docs

- [Overview](https://developers.contents.team/): What the API does and where to start.
- [Quickstart](https://developers.contents.team/quickstart): Create a key, list claims, create a claim, add photos and get the priced inventory back.
- [Authentication](https://developers.contents.team/authentication): API keys, scopes, test and live mode, and OAuth apps for partners.
- [Review and approve](https://developers.contents.team/review): Edit the priced items, accept replacement offers and approve them from your own product.
- [Webhooks](https://developers.contents.team/webhooks): Event types, delivery and retries, and verifying the signature.
- [Client libraries](https://developers.contents.team/client-libraries): Generate a typed client from the OpenAPI document in TypeScript, C# or Python, and verify webhook signatures in each.
- [Test mode](https://developers.contents.team/test-mode): Build against canned results with test keys; nothing is billed.
- [Requests and errors](https://developers.contents.team/conventions): Ids, money, pagination, idempotency, errors and rate limits.
- [CompanyCam](https://developers.contents.team/companycam): Sync CompanyCam projects and photos into claims, and write item details back.
- [MCP server](https://developers.contents.team/mcp): Connect Claude, ChatGPT, Cursor or Claude Code to Contents.team with an API key.
- [API reference](https://developers.contents.team/api-reference): Every endpoint, parameter and response, generated from the OpenAPI document.
- [Changelog](https://developers.contents.team/changelog): Every change a client can see, newest first.

## Contact

- Integrations and API support: integrations@contents.team
