API reference

Generated from the OpenAPI document the API serves, so it is always the deployed truth. Download it as JSON or YAML for code generators, or start with the quickstart.

Every request authenticates with Authorization: Bearer and an org API key or OAuth token. POSTs need an Idempotency-Key. Errors share one envelope; see requests and errors.

Claims

List claims

get/api/v1/claims

Newest first. Page with cursor = the previous page's nextCursor. To sync, note the time before you start, page through with updatedSince = your last sync time, then use the noted time next run.

Scope: claims:read

Parameters

  • limitinteger · query

    Page size, 1-100.

  • cursorstring · query

    nextCursor from the previous page.

  • externalRefstring · query

    Only claims with this externalRef.

  • updatedSincestring (date-time) · query

    Only claims whose record changed at or after this instant.

›Response 200Success
  • object"list"required

  • dataobject[]required

  • data[].idstringrequired

    clm_ id.

  • data[].object"claim"required

  • data[].mode"live" | "test"required

    test = created with a ctk_test_ key: only test keys see it, and it is never billed.

  • data[].externalRefstring | nullrequired

    Your own id for this claim.

  • data[].claimNumberstring | nullrequired

  • data[].insuredNamestring | nullrequired

  • data[].lossAddressstring | nullrequired

  • data[].lossType"Fire" | "Water" | "Wind" | "Other" | nullrequired

  • data[].dateOfLossstring | nullrequired

    YYYY-MM-DD.

  • data[].status"open" | "in_progress" | "review" | "closed"required

  • data[].insuranceCompanystring | nullrequired

  • data[].policyNumberstring | nullrequired

  • data[].valuationMode"rcv" | "acv" | "acv_only"required

  • data[].salesTaxBpinteger | nullrequired

  • data[].itemCountintegerrequired

    Inventory lines, excluding auto-detected duplicates.

  • data[].approvedRcvCentsintegerrequired

    Replacement cost of approved items, tax included.

  • data[].createdAtstring (date-time)required

  • data[].updatedAtstring (date-time)required

    Last change to the claim record itself (not its items).

  • hasMorebooleanrequired

  • nextCursorstring | nullrequired

    Pass as cursor for the next page; null on the last page.

curl
curl -X GET 'https://contents.team/api/v1/claims' \
  -H "Authorization: Bearer $CONTENTS_API_KEY"

Create a claim

post/api/v1/claims

Creates a claim in the key's organization. Every field is optional; set externalRef to your own id so you can find the claim again with GET /api/v1/claims?externalRef=. A test key (ctk_test_) creates a test claim: only test keys see it and it is never billed. Live keys need an organization with an active subscription (402 billing_inactive otherwise).

Scope: claims:write

Parameters

  • Idempotency-Keystring · headerrequired

    Required on every POST (a UUID works). Retrying with the same key and body returns the first successful response (with Idempotent-Replayed: true) instead of acting twice; the same key with a different body is a 409 idempotency_key_reused. Errors are not stored, so a failed request can be retried with the same key. Keys are per organization and expire after 24 hours.

Request body

  • externalRefstring | null

    Your own id for this claim; filter on it with GET /api/v1/claims?externalRef=. Not unique.

  • claimNumberstring | null

    The carrier claim number, shown as the claim's label in the app.

  • insuredNamestring | null

    The policyholder (your customer).

  • lossAddressstring | null

  • lossType"Fire" | "Water" | "Wind" | "Other" | null

  • dateOfLossstring (date) | null

    YYYY-MM-DD.

  • insuranceCompanystring | null

  • policyNumberstring | null

  • salesTaxBpinteger | null

    Sales tax in basis points (825 = 8.25%).

›Response 201Success
  • idstringrequired

    clm_ id.

  • object"claim"required

  • mode"live" | "test"required

    test = created with a ctk_test_ key: only test keys see it, and it is never billed.

  • externalRefstring | nullrequired

    Your own id for this claim.

  • claimNumberstring | nullrequired

  • insuredNamestring | nullrequired

  • lossAddressstring | nullrequired

  • lossType"Fire" | "Water" | "Wind" | "Other" | nullrequired

  • dateOfLossstring | nullrequired

    YYYY-MM-DD.

  • status"open" | "in_progress" | "review" | "closed"required

  • insuranceCompanystring | nullrequired

  • policyNumberstring | nullrequired

  • valuationMode"rcv" | "acv" | "acv_only"required

  • salesTaxBpinteger | nullrequired

  • itemCountintegerrequired

    Inventory lines, excluding auto-detected duplicates.

  • approvedRcvCentsintegerrequired

    Replacement cost of approved items, tax included.

  • createdAtstring (date-time)required

  • updatedAtstring (date-time)required

    Last change to the claim record itself (not its items).

curl
curl -X POST 'https://contents.team/api/v1/claims' \
  -H "Authorization: Bearer $CONTENTS_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"externalRef":"string","claimNumber":"string","insuredName":"string"}'

Get a claim

get/api/v1/claims/{claimId}

The claim with its item counts, approved totals (valued with the claim's tax, depreciation and payment caps) and photo-analysis progress. Poll it to know when analysis has finished. An id from another organization is answered exactly like an unknown id: 404.

Scope: claims:read

Parameters

  • claimIdstring · pathrequired

    clm_ id.

›Response 200Success
  • idstringrequired

    clm_ id.

  • object"claim"required

  • mode"live" | "test"required

    test = created with a ctk_test_ key: only test keys see it, and it is never billed.

  • externalRefstring | nullrequired

    Your own id for this claim.

  • claimNumberstring | nullrequired

  • insuredNamestring | nullrequired

  • lossAddressstring | nullrequired

  • lossType"Fire" | "Water" | "Wind" | "Other" | nullrequired

  • dateOfLossstring | nullrequired

    YYYY-MM-DD.

  • status"open" | "in_progress" | "review" | "closed"required

  • insuranceCompanystring | nullrequired

  • policyNumberstring | nullrequired

  • valuationMode"rcv" | "acv" | "acv_only"required

  • salesTaxBpinteger | nullrequired

  • itemCountintegerrequired

    Inventory lines, excluding auto-detected duplicates.

  • approvedRcvCentsintegerrequired

    Replacement cost of approved items, tax included.

  • createdAtstring (date-time)required

  • updatedAtstring (date-time)required

    Last change to the claim record itself (not its items).

  • countsobjectrequired

  • counts.itemsintegerrequired

    Inventory lines, excluding auto-detected duplicates.

  • counts.itemsAwaitingReviewintegerrequired

    pending and low_confidence items.

  • counts.itemsApprovedintegerrequired

  • counts.itemsRejectedintegerrequired

  • counts.roomsintegerrequired

  • counts.photosintegerrequired

  • totalsobjectrequired

    Approved items only, valued with the claim's tax, depreciation and payment caps.

  • totals.approvedSubtotalCentsintegerrequired

  • totals.approvedTaxCentsintegerrequired

  • totals.approvedRcvCentsintegerrequired

  • totals.approvedAcvCentsintegerrequired

  • totals.approvedPaymentCentsintegerrequired

    What the approved lines pay after payment caps.

  • analysisobjectrequired

  • analysis.status"no_photos" | "in_progress" | "awaiting_start" | "complete"required

    in_progress while any inventory photo is queued or analyzing; awaiting_start when the only unanalyzed photos are staged (held until someone starts analysis in the app); complete otherwise.

  • analysis.photosTotalintegerrequired

    Inventory photos (reference photos are never analyzed).

  • analysis.photosQueuedintegerrequired

  • analysis.photosStagedintegerrequired

  • analysis.photosAnalyzingintegerrequired

  • analysis.photosCompletedintegerrequired

  • analysis.photosFailedintegerrequired

curl
curl -X GET 'https://contents.team/api/v1/claims/{claimId}' \
  -H "Authorization: Bearer $CONTENTS_API_KEY"

Photos

List a claim's photos

get/api/v1/claims/{claimId}/photos

Photos oldest first, with analysis status and signed download URLs that expire after 15 minutes. Page with cursor = the previous page's nextCursor. An id from another organization is answered exactly like an unknown id: 404.

Scope: photos:read

Parameters

  • claimIdstring · pathrequired

    clm_ id.

  • limitinteger · query

    Page size, 1-100.

  • cursorstring · query

    nextCursor from the previous page.

  • roomIdstring · query

    Only photos filed in this room (rom_ id).

  • updatedSincestring (date-time) · query

    Only photos changed (including analysis status) at or after this instant.

›Response 200Success
  • object"list"required

  • dataobject[]required

  • data[].idstringrequired

    pho_ id.

  • data[].object"photo"required

  • data[].claimIdstringrequired

    clm_ id.

  • data[].roomIdstring | nullrequired

    rom_ id, or null when the photo is not filed in a room.

  • data[].filenamestringrequired

  • data[].contentTypestringrequired

  • data[].sizeBytesinteger | nullrequired

  • data[].kind"inventory" | "reference"required

    reference: a room overview shot, never analyzed for items.

  • data[].analysisStatus"queued" | "staged" | "analyzing" | "completed" | "failed" | "not_analyzed"required

    queued: uploaded, waiting for analysis; staged: held until someone starts analysis in the app; analyzing; completed: items identified (possibly none); failed: analysis failed; not_analyzed: a reference photo.

  • data[].stackIdstring | nullrequired

    pho_ id of the stack lead when several photos show one item; null otherwise.

  • data[].urlsobjectrequired

    Signed GET URLs. They expire at urlsExpireAt: fetch the photo again for fresh ones, never store them.

  • data[].urls.originalstringrequired

    The uploaded file (may be HEIC).

  • data[].urls.compressedstringrequired

    A WebP copy for display. Generated shortly after upload: until then this URL returns 404, so fall back to original.

  • data[].urls.thumbnailstringrequired

  • data[].urlsExpireAtstring (date-time)required

  • data[].createdAtstring (date-time)required

  • data[].updatedAtstring (date-time)required

  • hasMorebooleanrequired

  • nextCursorstring | nullrequired

    Pass as cursor for the next page; null on the last page.

curl
curl -X GET 'https://contents.team/api/v1/claims/{claimId}/photos' \
  -H "Authorization: Bearer $CONTENTS_API_KEY"

Add photos by URL

post/api/v1/claims/{claimId}/photos

Up to 10 public https image URLs per call. We fetch each once (JPEG, PNG, WebP or HEIC, at most 20 MB; private and internal addresses are refused) and send the photos into the same AI pipeline as photos uploaded in the app. URLs that fail are listed in failed; if none can be fetched the call is a 400. With a test key, photos are analyzed by the fixture pipeline: canned, deterministic items appear at once and nothing is billed. Send X-Contents-Test-Mode: live-ai to run the real pipeline instead, capped at 50 photos per organization per UTC day (429 test_live_ai_cap_reached beyond that).

Scope: photos:write

Parameters

  • claimIdstring · pathrequired

    clm_ id.

  • Idempotency-Keystring · headerrequired

    Required on every POST (a UUID works). Retrying with the same key and body returns the first successful response (with Idempotent-Replayed: true) instead of acting twice; the same key with a different body is a 409 idempotency_key_reused. Errors are not stored, so a failed request can be retried with the same key. Keys are per organization and expire after 24 hours.

Request body

  • photosobject[]required

  • photos[].urlstring (uri)required

    Public https URL of a JPEG, PNG, WebP or HEIC image, at most 20 MB. Fetched once, server-side.

  • photos[].roomNamestring

    Room to file the photo under; created on the claim if it does not exist yet.

  • photos[].externalIdstring

    Your own id for the photo, echoed back; also picks the test-mode fixture set.

›Response 201Success
  • object"photo_batch"required

  • claimIdstringrequired

  • pipeline"ai" | "test_fixtures" | "test_live_ai"required

    ai = the real pipeline (live keys); test_fixtures = canned items at once; test_live_ai = real pipeline, test claim.

  • photosobject[]required

  • photos[].idstringrequired

    pho_ id.

  • photos[].object"photo"required

  • photos[].externalIdstring | nullrequired

  • photos[].filenamestringrequired

  • photos[].roomIdstring | nullrequired

    rom_ id, or null when unfiled.

  • photos[].status"uploading" | "processing" | "analyzed" | "analysis_failed" | "ready" | "failed"required

    processing = queued for or in AI analysis; analyzed = items extracted; ready = stored, not analyzed; failed = the upload never arrived or did not match.

  • failedobject[]required

  • failed[].indexinteger | nullrequired

    Position in the request, when the failure is about one entry.

  • failed[].photoIdstring | nullrequired

  • failed[].externalIdstring | nullrequired

  • failed[].codestringrequired

  • failed[].messagestringrequired

curl
curl -X POST 'https://contents.team/api/v1/claims/{claimId}/photos' \
  -H "Authorization: Bearer $CONTENTS_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"photos":[{"url":"https://example.com/photo.jpg"}]}'

Get a photo

get/api/v1/photos/{photoId}

One photo with its analysis status and fresh signed URLs. An id from another organization is answered exactly like an unknown id: 404.

Scope: photos:read

Parameters

  • photoIdstring · pathrequired

    pho_ id.

›Response 200Success
  • idstringrequired

    pho_ id.

  • object"photo"required

  • claimIdstringrequired

    clm_ id.

  • roomIdstring | nullrequired

    rom_ id, or null when the photo is not filed in a room.

  • filenamestringrequired

  • contentTypestringrequired

  • sizeBytesinteger | nullrequired

  • kind"inventory" | "reference"required

    reference: a room overview shot, never analyzed for items.

  • analysisStatus"queued" | "staged" | "analyzing" | "completed" | "failed" | "not_analyzed"required

    queued: uploaded, waiting for analysis; staged: held until someone starts analysis in the app; analyzing; completed: items identified (possibly none); failed: analysis failed; not_analyzed: a reference photo.

  • stackIdstring | nullrequired

    pho_ id of the stack lead when several photos show one item; null otherwise.

  • urlsobjectrequired

    Signed GET URLs. They expire at urlsExpireAt: fetch the photo again for fresh ones, never store them.

  • urls.originalstringrequired

    The uploaded file (may be HEIC).

  • urls.compressedstringrequired

    A WebP copy for display. Generated shortly after upload: until then this URL returns 404, so fall back to original.

  • urls.thumbnailstringrequired

  • urlsExpireAtstring (date-time)required

  • createdAtstring (date-time)required

  • updatedAtstring (date-time)required

curl
curl -X GET 'https://contents.team/api/v1/photos/{photoId}' \
  -H "Authorization: Bearer $CONTENTS_API_KEY"

Start a direct photo upload

post/api/v1/claims/{claimId}/photo-uploads

Register up to 24 files and get one presigned PUT URL per file (valid 5 minutes). PUT each file's exact bytes with the returned headers, then call .../photo-uploads/complete. Nothing is analyzed until then.

Scope: photos:write

Parameters

  • claimIdstring · pathrequired

    clm_ id.

  • Idempotency-Keystring · headerrequired

    Required on every POST (a UUID works). Retrying with the same key and body returns the first successful response (with Idempotent-Replayed: true) instead of acting twice; the same key with a different body is a 409 idempotency_key_reused. Errors are not stored, so a failed request can be retried with the same key. Keys are per organization and expire after 24 hours.

Request body

  • filesobject[]required

  • files[].filenamestringrequired

  • files[].contentType"image/jpeg" | "image/png" | "image/webp" | "image/heic" | "image/heif"required

  • files[].sizeBytesintegerrequired

    Exact byte size you will PUT.

  • files[].roomNamestring

    Room to file the photo under; created on the claim if it does not exist yet.

  • files[].externalIdstring

    Your own id for the photo, echoed back; also picks the test-mode fixture set.

›Response 201Success
  • idstringrequired

    upl_ id; pass it to the complete call.

  • object"photo_upload"required

  • claimIdstringrequired

  • uploadsobject[]required

  • uploads[].photoIdstringrequired

  • uploads[].externalIdstring | nullrequired

  • uploads[].filenamestringrequired

  • uploads[].method"PUT"required

  • uploads[].urlstringrequired

    Presigned storage URL. PUT the exact bytes with the headers below.

  • uploads[].headersobjectrequired

  • uploads[].expiresAtstring (date-time)required

curl
curl -X POST 'https://contents.team/api/v1/claims/{claimId}/photo-uploads' \
  -H "Authorization: Bearer $CONTENTS_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"files":[{"filename":"string","contentType":"image/jpeg","sizeBytes":1}]}'

Finish a direct photo upload

post/api/v1/claims/{claimId}/photo-uploads/complete

Checks each uploaded file arrived with the declared size and type, then sends the photos into the AI pipeline. Files that did not arrive are listed in failed. With a test key, photos are analyzed by the fixture pipeline: canned, deterministic items appear at once and nothing is billed. Send X-Contents-Test-Mode: live-ai to run the real pipeline instead, capped at 50 photos per organization per UTC day (429 test_live_ai_cap_reached beyond that).

Scope: photos:write

Parameters

  • claimIdstring · pathrequired

    clm_ id.

  • Idempotency-Keystring · headerrequired

    Required on every POST (a UUID works). Retrying with the same key and body returns the first successful response (with Idempotent-Replayed: true) instead of acting twice; the same key with a different body is a 409 idempotency_key_reused. Errors are not stored, so a failed request can be retried with the same key. Keys are per organization and expire after 24 hours.

Request body

  • uploadIdstringrequired

    upl_ id from the create call.

  • photoIdsstring[]required

    pho_ ids you finished uploading.

›Response 200Success
  • object"photo_batch"required

  • claimIdstringrequired

  • pipeline"ai" | "test_fixtures" | "test_live_ai"required

    ai = the real pipeline (live keys); test_fixtures = canned items at once; test_live_ai = real pipeline, test claim.

  • photosobject[]required

  • photos[].idstringrequired

    pho_ id.

  • photos[].object"photo"required

  • photos[].externalIdstring | nullrequired

  • photos[].filenamestringrequired

  • photos[].roomIdstring | nullrequired

    rom_ id, or null when unfiled.

  • photos[].status"uploading" | "processing" | "analyzed" | "analysis_failed" | "ready" | "failed"required

    processing = queued for or in AI analysis; analyzed = items extracted; ready = stored, not analyzed; failed = the upload never arrived or did not match.

  • failedobject[]required

  • failed[].indexinteger | nullrequired

    Position in the request, when the failure is about one entry.

  • failed[].photoIdstring | nullrequired

  • failed[].externalIdstring | nullrequired

  • failed[].codestringrequired

  • failed[].messagestringrequired

curl
curl -X POST 'https://contents.team/api/v1/claims/{claimId}/photo-uploads/complete' \
  -H "Authorization: Bearer $CONTENTS_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"uploadId":"string","photoIds":["string"]}'

Rooms

List a claim's rooms

get/api/v1/claims/{claimId}/rooms

Every room in display order, in one page. Floors hold rooms, rooms hold subrooms (parentId). An id from another organization is answered exactly like an unknown id: 404.

Scope: claims:read

Parameters

  • claimIdstring · pathrequired

    clm_ id.

›Response 200Success
  • object"list"required

  • dataobject[]required

  • data[].idstringrequired

    rom_ id.

  • data[].object"room"required

  • data[].claimIdstringrequired

    clm_ id of the claim the room belongs to.

  • data[].namestringrequired

  • data[].type"floor" | "room" | "subroom"required

  • data[].parentIdstring | nullrequired

    rom_ id of the containing floor or room; null at the top level.

  • data[].displayOrderintegerrequired

    Position among its siblings, as the app orders them.

  • data[].itemCountintegerrequired

    Inventory lines filed in this room (not its subrooms), excluding auto-detected duplicates.

  • data[].createdAtstring (date-time)required

  • data[].updatedAtstring (date-time)required

  • hasMorebooleanrequired

  • nextCursorstring | nullrequired

    Pass as cursor for the next page; null on the last page.

curl
curl -X GET 'https://contents.team/api/v1/claims/{claimId}/rooms' \
  -H "Authorization: Bearer $CONTENTS_API_KEY"

Items

Get an item

get/api/v1/items/{itemId}

One inventory item, valued within its claim. An id from another organization is answered exactly like an unknown id: 404.

Scope: items:read

Parameters

  • itemIdstring · pathrequired

    itm_ id.

›Response 200Success
  • idstringrequired

    itm_ id.

  • object"item"required

  • claimIdstringrequired

    clm_ id.

  • roomIdstring | nullrequired

    rom_ id, or null when the item is not filed in a room.

  • roomNamestring | nullrequired

  • namestringrequired

    The line as the app shows it: the retailer title when chosen, else the description.

  • descriptionstringrequired

  • retailerDescriptionstring | nullrequired

  • modelstring | nullrequired

  • categorystring | nullrequired

  • quantityintegerrequired

  • unitCostCentsinteger | nullrequired

    Null until the item is priced.

  • pricedbooleanrequired

    True once the item has a unit cost. An unpriced item cannot be approved: set unitCostCents or accept a replacement first.

  • status"pending" | "low_confidence" | "reviewing" | "approved" | "rejected"required

    pending and low_confidence await review; reviewing is being priced (the item is locked: edits and approval are refused until it finishes); approved items count toward totals and exports; rejected items are left out of both.

  • needsReviewbooleanrequired

    True while the item waits on a person: status pending or low_confidence.

  • condition"New" | "Above Average" | "Average" | "Below Average" | nullrequired

  • estimatedAgeYearsnumber | nullrequired

  • source"ai" | "manual"required

    ai: identified from a photo; manual: entered or imported.

  • priceSourcestring | nullrequired

    Where the price came from (retailer name).

  • priceSourceUrlstring | nullrequired

  • notesstring | nullrequired

  • photoIdsstring[]required

    pho_ ids the item appears in, primary photo first.

  • valuationobjectrequired

  • valuation.subtotalCentsintegerrequired

    Quantity x unit cost, before tax.

  • valuation.taxCentsintegerrequired

    Sales tax at the claim's salesTaxBp.

  • valuation.rcvCentsintegerrequired

    Replacement cost value: subtotal plus tax.

  • valuation.depreciationBpintegerrequired

    Depreciation rate applied, in basis points (2500 = 25%).

  • valuation.depreciationCentsintegerrequired

  • valuation.acvCentsintegerrequired

    Actual cash value: RCV less depreciation.

  • valuation.paymentCentsintegerrequired

    What the claim pays for the line after any payment cap.

  • createdAtstring (date-time)required

  • updatedAtstring (date-time)required

curl
curl -X GET 'https://contents.team/api/v1/items/{itemId}' \
  -H "Authorization: Bearer $CONTENTS_API_KEY"

Update an item

patch/api/v1/items/{itemId}

Change any of the listed fields; leave out the ones to keep. The same validation and price caps as the app apply, and the change is recorded in the item's history like an edit in the app. Editing an approved item keeps it approved. An item being priced (status reviewing) is a 409 item_locked: retry once it finishes. Live keys need an organization with an active subscription (402 billing_inactive otherwise). An id from another organization is answered exactly like an unknown id: 404.

Scope: items:write

Parameters

  • itemIdstring · pathrequired

    itm_ id.

Request body

  • namestring

    The line's text as the claim shows it. Setting it replaces a retailer title chosen earlier.

  • modelstring | null

    Make and model, if known.

  • quantityinteger

    1-100000.

  • unitCostCentsinteger | null

    Replacement price for one, in integer cents; null removes the price (the item is then unpriced and cannot be approved).

  • priceSourcestring | null

    Where the price came from (a retailer name).

  • priceSourceUrlstring (uri) | null

    The listing the price came from. Google search links are dropped, as in the app.

  • roomIdstring | null

    rom_ id of a room on the same claim (GET /api/v1/claims/{claimId}/rooms); null takes the item out of its room.

  • condition"New" | "Above Average" | "Average" | "Below Average" | null

  • estimatedAgeYearsnumber | null

    Age in years, to a tenth (2.5). Drives depreciation.

  • notesstring | null

›Response 200Success
  • idstringrequired

    itm_ id.

  • object"item"required

  • claimIdstringrequired

    clm_ id.

  • roomIdstring | nullrequired

    rom_ id, or null when the item is not filed in a room.

  • roomNamestring | nullrequired

  • namestringrequired

    The line as the app shows it: the retailer title when chosen, else the description.

  • descriptionstringrequired

  • retailerDescriptionstring | nullrequired

  • modelstring | nullrequired

  • categorystring | nullrequired

  • quantityintegerrequired

  • unitCostCentsinteger | nullrequired

    Null until the item is priced.

  • pricedbooleanrequired

    True once the item has a unit cost. An unpriced item cannot be approved: set unitCostCents or accept a replacement first.

  • status"pending" | "low_confidence" | "reviewing" | "approved" | "rejected"required

    pending and low_confidence await review; reviewing is being priced (the item is locked: edits and approval are refused until it finishes); approved items count toward totals and exports; rejected items are left out of both.

  • needsReviewbooleanrequired

    True while the item waits on a person: status pending or low_confidence.

  • condition"New" | "Above Average" | "Average" | "Below Average" | nullrequired

  • estimatedAgeYearsnumber | nullrequired

  • source"ai" | "manual"required

    ai: identified from a photo; manual: entered or imported.

  • priceSourcestring | nullrequired

    Where the price came from (retailer name).

  • priceSourceUrlstring | nullrequired

  • notesstring | nullrequired

  • photoIdsstring[]required

    pho_ ids the item appears in, primary photo first.

  • valuationobjectrequired

  • valuation.subtotalCentsintegerrequired

    Quantity x unit cost, before tax.

  • valuation.taxCentsintegerrequired

    Sales tax at the claim's salesTaxBp.

  • valuation.rcvCentsintegerrequired

    Replacement cost value: subtotal plus tax.

  • valuation.depreciationBpintegerrequired

    Depreciation rate applied, in basis points (2500 = 25%).

  • valuation.depreciationCentsintegerrequired

  • valuation.acvCentsintegerrequired

    Actual cash value: RCV less depreciation.

  • valuation.paymentCentsintegerrequired

    What the claim pays for the line after any payment cap.

  • createdAtstring (date-time)required

  • updatedAtstring (date-time)required

curl
curl -X PATCH 'https://contents.team/api/v1/items/{itemId}' \
  -H "Authorization: Bearer $CONTENTS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"string","model":"string","quantity":1}'

Delete an item

DEL/api/v1/items/{itemId}

Removes the item from the claim, its totals and its exports, as deleting it in the app does (the line's history is kept). 409 item_locked while it is being priced. Live keys need an organization with an active subscription (402 billing_inactive otherwise). An id from another organization is answered exactly like an unknown id: 404.

Scope: items:write

Parameters

  • itemIdstring · pathrequired

    itm_ id.

Response 204

No content

curl
curl -X DELETE 'https://contents.team/api/v1/items/{itemId}' \
  -H "Authorization: Bearer $CONTENTS_API_KEY"

List a claim's items

get/api/v1/claims/{claimId}/items

The inventory, oldest first. Each item carries its RCV/ACV valuation in cents and its depreciation in basis points. Page with cursor = the previous page's nextCursor. An id from another organization is answered exactly like an unknown id: 404.

Scope: items:read

Parameters

  • claimIdstring · pathrequired

    clm_ id.

  • limitinteger · query

    Page size, 1-100.

  • cursorstring · query

    nextCursor from the previous page.

  • status"pending" | "low_confidence" | "reviewing" | "approved" | "rejected" · query

    Only items with this status.

  • needsReview"true" | "false" · query

    true: only items waiting on a person (pending or low_confidence); false: only the others.

  • priced"true" | "false" · query

    true: only items with a unit cost; false: only unpriced items.

  • roomIdstring · query

    Only items filed in this room (rom_ id).

  • updatedSincestring (date-time) · query

    Only items changed at or after this instant.

›Response 200Success
  • object"list"required

  • dataobject[]required

  • data[].idstringrequired

    itm_ id.

  • data[].object"item"required

  • data[].claimIdstringrequired

    clm_ id.

  • data[].roomIdstring | nullrequired

    rom_ id, or null when the item is not filed in a room.

  • data[].roomNamestring | nullrequired

  • data[].namestringrequired

    The line as the app shows it: the retailer title when chosen, else the description.

  • data[].descriptionstringrequired

  • data[].retailerDescriptionstring | nullrequired

  • data[].modelstring | nullrequired

  • data[].categorystring | nullrequired

  • data[].quantityintegerrequired

  • data[].unitCostCentsinteger | nullrequired

    Null until the item is priced.

  • data[].pricedbooleanrequired

    True once the item has a unit cost. An unpriced item cannot be approved: set unitCostCents or accept a replacement first.

  • data[].status"pending" | "low_confidence" | "reviewing" | "approved" | "rejected"required

    pending and low_confidence await review; reviewing is being priced (the item is locked: edits and approval are refused until it finishes); approved items count toward totals and exports; rejected items are left out of both.

  • data[].needsReviewbooleanrequired

    True while the item waits on a person: status pending or low_confidence.

  • data[].condition"New" | "Above Average" | "Average" | "Below Average" | nullrequired

  • data[].estimatedAgeYearsnumber | nullrequired

  • data[].source"ai" | "manual"required

    ai: identified from a photo; manual: entered or imported.

  • data[].priceSourcestring | nullrequired

    Where the price came from (retailer name).

  • data[].priceSourceUrlstring | nullrequired

  • data[].notesstring | nullrequired

  • data[].photoIdsstring[]required

    pho_ ids the item appears in, primary photo first.

  • data[].valuationobjectrequired

  • data[].valuation.subtotalCentsintegerrequired

    Quantity x unit cost, before tax.

  • data[].valuation.taxCentsintegerrequired

    Sales tax at the claim's salesTaxBp.

  • data[].valuation.rcvCentsintegerrequired

    Replacement cost value: subtotal plus tax.

  • data[].valuation.depreciationBpintegerrequired

    Depreciation rate applied, in basis points (2500 = 25%).

  • data[].valuation.depreciationCentsintegerrequired

  • data[].valuation.acvCentsintegerrequired

    Actual cash value: RCV less depreciation.

  • data[].valuation.paymentCentsintegerrequired

    What the claim pays for the line after any payment cap.

  • data[].createdAtstring (date-time)required

  • data[].updatedAtstring (date-time)required

  • hasMorebooleanrequired

  • nextCursorstring | nullrequired

    Pass as cursor for the next page; null on the last page.

curl
curl -X GET 'https://contents.team/api/v1/claims/{claimId}/items' \
  -H "Authorization: Bearer $CONTENTS_API_KEY"

Add an item

post/api/v1/claims/{claimId}/items

Adds a line by hand, as the app's Add item does: source manual, and approved as soon as it is added. It counts toward totals and exports at once; an unpriced one counts at $0 until you set unitCostCents. Adding an item does not send inventory.approved. Approval through the API is the same status change as in the app: the daily settle job, the one place usage is metered, treats it exactly like an approval made in the app. Items on a test claim are never billed. Live keys need an organization with an active subscription (402 billing_inactive otherwise). An id from another organization is answered exactly like an unknown id: 404.

Scope: items:write

Parameters

  • claimIdstring · pathrequired

    clm_ id.

  • Idempotency-Keystring · headerrequired

    Required on every POST (a UUID works). Retrying with the same key and body returns the first successful response (with Idempotent-Replayed: true) instead of acting twice; the same key with a different body is a 409 idempotency_key_reused. Errors are not stored, so a failed request can be retried with the same key. Keys are per organization and expire after 24 hours.

Request body

  • namestringrequired

    The line's text as the claim shows it. Setting it replaces a retailer title chosen earlier.

  • modelstring | null

    Make and model, if known.

  • quantityinteger

    1-100000, default 1.

  • unitCostCentsinteger | null

    Replacement price for one, in integer cents. Omit to add the item unpriced.

  • roomIdstring | null

    rom_ id of a room on the same claim (GET /api/v1/claims/{claimId}/rooms); null takes the item out of its room.

  • condition"New" | "Above Average" | "Average" | "Below Average" | null

  • estimatedAgeYearsnumber | null

    Age in years, to a tenth (2.5). Drives depreciation.

  • notesstring | null

›Response 201Success
  • idstringrequired

    itm_ id.

  • object"item"required

  • claimIdstringrequired

    clm_ id.

  • roomIdstring | nullrequired

    rom_ id, or null when the item is not filed in a room.

  • roomNamestring | nullrequired

  • namestringrequired

    The line as the app shows it: the retailer title when chosen, else the description.

  • descriptionstringrequired

  • retailerDescriptionstring | nullrequired

  • modelstring | nullrequired

  • categorystring | nullrequired

  • quantityintegerrequired

  • unitCostCentsinteger | nullrequired

    Null until the item is priced.

  • pricedbooleanrequired

    True once the item has a unit cost. An unpriced item cannot be approved: set unitCostCents or accept a replacement first.

  • status"pending" | "low_confidence" | "reviewing" | "approved" | "rejected"required

    pending and low_confidence await review; reviewing is being priced (the item is locked: edits and approval are refused until it finishes); approved items count toward totals and exports; rejected items are left out of both.

  • needsReviewbooleanrequired

    True while the item waits on a person: status pending or low_confidence.

  • condition"New" | "Above Average" | "Average" | "Below Average" | nullrequired

  • estimatedAgeYearsnumber | nullrequired

  • source"ai" | "manual"required

    ai: identified from a photo; manual: entered or imported.

  • priceSourcestring | nullrequired

    Where the price came from (retailer name).

  • priceSourceUrlstring | nullrequired

  • notesstring | nullrequired

  • photoIdsstring[]required

    pho_ ids the item appears in, primary photo first.

  • valuationobjectrequired

  • valuation.subtotalCentsintegerrequired

    Quantity x unit cost, before tax.

  • valuation.taxCentsintegerrequired

    Sales tax at the claim's salesTaxBp.

  • valuation.rcvCentsintegerrequired

    Replacement cost value: subtotal plus tax.

  • valuation.depreciationBpintegerrequired

    Depreciation rate applied, in basis points (2500 = 25%).

  • valuation.depreciationCentsintegerrequired

  • valuation.acvCentsintegerrequired

    Actual cash value: RCV less depreciation.

  • valuation.paymentCentsintegerrequired

    What the claim pays for the line after any payment cap.

  • createdAtstring (date-time)required

  • updatedAtstring (date-time)required

curl
curl -X POST 'https://contents.team/api/v1/claims/{claimId}/items' \
  -H "Authorization: Bearer $CONTENTS_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"name":"string"}'

List an item's replacement offers

get/api/v1/items/{itemId}/replacements

The offers the replacement finder found for the item in its most recent search, best first, as the app shows them. proposed marks the finder's pick. An item never searched has an empty list. An id from another organization is answered exactly like an unknown id: 404.

Scope: items:read

Parameters

  • itemIdstring · pathrequired

    itm_ id.

›Response 200Success
  • object"list"required

  • itemIdstringrequired

  • searchedAtstring (date-time) | nullrequired

    When the finder last searched for this item; null if it never has.

  • dataobject[]required

    The offers from the most recent search, best first.

  • data[].idstringrequired

    rpl_ id; pass it to .../replacements/{replacementId}/accept.

  • data[].object"replacement"required

  • data[].titlestringrequired

    The listing's product title.

  • data[].modelstring | nullrequired

  • data[].retailerstring | nullrequired

  • data[].priceCentsinteger | nullrequired

    The listing's price for one, in integer cents; null when the listing had no price.

  • data[].urlstring | nullrequired

    The retailer's product page, when one was found.

  • data[].imageUrlstring | nullrequired

  • data[].proposedbooleanrequired

    True for the offer the finder picked for this item.

  • data[].outsidePreferredRetailersbooleanrequired

    True when the retailer is not on the organization's preferred list.

  • data[].acceptedAtstring (date-time) | nullrequired

    When this offer was applied to the item, if it was.

curl
curl -X GET 'https://contents.team/api/v1/items/{itemId}/replacements' \
  -H "Authorization: Bearer $CONTENTS_API_KEY"

Approve items

post/api/v1/claims/{claimId}/items/approve

Approves up to 1000 items of the claim at once, exactly as bulk approval in the app does. Each id lands in one list: approved, alreadyApproved, locked (still being priced; retry later), skipped (with a reason: unpriced items cannot be approved) or notFound. Only approved items count toward claim totals and exports. Newly approved items send one inventory.approved webhook. Approval through the API is the same status change as in the app: the daily settle job, the one place usage is metered, treats it exactly like an approval made in the app. Items on a test claim are never billed. Live keys need an organization with an active subscription (402 billing_inactive otherwise). An id from another organization is answered exactly like an unknown id: 404.

Scope: items:write

Parameters

  • claimIdstring · pathrequired

    clm_ id.

  • Idempotency-Keystring · headerrequired

    Required on every POST (a UUID works). Retrying with the same key and body returns the first successful response (with Idempotent-Replayed: true) instead of acting twice; the same key with a different body is a 409 idempotency_key_reused. Errors are not stored, so a failed request can be retried with the same key. Keys are per organization and expire after 24 hours.

Request body

  • itemIdsstring[]required

    itm_ ids of items on this claim, 1-1000.

›Response 200Success
  • object"item_approval"required

  • claimIdstringrequired

  • approvedstring[]required

    itm_ ids approved by this request.

  • alreadyApprovedstring[]required

    itm_ ids that were approved already; nothing changed.

  • lockedstring[]required

    itm_ ids still being priced (status reviewing). Retry them once pricing finishes.

  • skippedobject[]required

  • skipped[].itemIdstringrequired

  • skipped[].reason"unpriced" | "not_approvable"required

    unpriced: the item has no unit cost; set unitCostCents or accept a replacement, then approve again. not_approvable: the item cannot be approved in its current state.

  • notFoundstring[]required

    Ids that are not items on this claim (or not visible to this key).

curl
curl -X POST 'https://contents.team/api/v1/claims/{claimId}/items/approve' \
  -H "Authorization: Bearer $CONTENTS_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"itemIds":["string"]}'

Reject items

post/api/v1/claims/{claimId}/items/reject

Rejects up to 100 items, as rejecting in the app does: they stay on the claim but are left out of its totals and exports. Approve one later to bring it back. Live keys need an organization with an active subscription (402 billing_inactive otherwise). An id from another organization is answered exactly like an unknown id: 404.

Scope: items:write

Parameters

  • claimIdstring · pathrequired

    clm_ id.

  • Idempotency-Keystring · headerrequired

    Required on every POST (a UUID works). Retrying with the same key and body returns the first successful response (with Idempotent-Replayed: true) instead of acting twice; the same key with a different body is a 409 idempotency_key_reused. Errors are not stored, so a failed request can be retried with the same key. Keys are per organization and expire after 24 hours.

Request body

  • itemIdsstring[]required

    itm_ ids of items on this claim, 1-100.

›Response 200Success
  • object"item_rejection"required

  • claimIdstringrequired

  • rejectedstring[]required

    itm_ ids rejected by this request.

  • alreadyRejectedstring[]required

  • lockedstring[]required

    itm_ ids still being priced (status reviewing).

  • notFoundstring[]required

curl
curl -X POST 'https://contents.team/api/v1/claims/{claimId}/items/reject' \
  -H "Authorization: Bearer $CONTENTS_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"itemIds":["string"]}'

Unapprove items

post/api/v1/claims/{claimId}/items/unapprove

Moves approved items back to pending (awaiting review), as undoing an approval in the app does. Items that are not approved are listed in notApproved and left alone. Live keys need an organization with an active subscription (402 billing_inactive otherwise). An id from another organization is answered exactly like an unknown id: 404.

Scope: items:write

Parameters

  • claimIdstring · pathrequired

    clm_ id.

  • Idempotency-Keystring · headerrequired

    Required on every POST (a UUID works). Retrying with the same key and body returns the first successful response (with Idempotent-Replayed: true) instead of acting twice; the same key with a different body is a 409 idempotency_key_reused. Errors are not stored, so a failed request can be retried with the same key. Keys are per organization and expire after 24 hours.

Request body

  • itemIdsstring[]required

    itm_ ids of items on this claim, 1-1000.

›Response 200Success
  • object"item_unapproval"required

  • claimIdstringrequired

  • unapprovedstring[]required

    itm_ ids moved from approved back to pending.

  • notApprovedstring[]required

    itm_ ids that were not approved; nothing changed.

  • notFoundstring[]required

curl
curl -X POST 'https://contents.team/api/v1/claims/{claimId}/items/unapprove' \
  -H "Authorization: Bearer $CONTENTS_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"itemIds":["string"]}'

Accept a replacement offer

post/api/v1/items/{itemId}/replacements/{replacementId}/accept

Prices the item from one of its offers and approves it, in one step, as accepting in the app does. The price and the quantity come from the stored offer, never the request; when the finder could not tell how many units a listing covers, the call is a 400 quantity_confirmation_required and you confirm with quantity and quantityConfirmed. Only an offer from the latest search can be accepted (409 replacement_stale otherwise), and never on an approved item (409 item_already_approved). Sends inventory.approved. The body may be empty. Approval through the API is the same status change as in the app: the daily settle job, the one place usage is metered, treats it exactly like an approval made in the app. Items on a test claim are never billed. Live keys need an organization with an active subscription (402 billing_inactive otherwise). An id from another organization is answered exactly like an unknown id: 404.

Scope: items:write

Parameters

  • itemIdstring · pathrequired

    itm_ id.

  • replacementIdstring · pathrequired

    rpl_ id from GET /api/v1/items/{itemId}/replacements.

  • Idempotency-Keystring · headerrequired

    Required on every POST (a UUID works). Retrying with the same key and body returns the first successful response (with Idempotent-Replayed: true) instead of acting twice; the same key with a different body is a 409 idempotency_key_reused. Errors are not stored, so a failed request can be retried with the same key. Keys are per organization and expire after 24 hours.

Request body

  • quantityinteger

    Only when the finder could not tell how many units the listing covers: the quantity you are confirming. Send it with quantityConfirmed: true.

  • quantityConfirmedtrue

  • keepItemTitletrue

    Adopt the listing's price and source but keep the item's own title.

›Response 200Success
  • idstringrequired

    itm_ id.

  • object"item"required

  • claimIdstringrequired

    clm_ id.

  • roomIdstring | nullrequired

    rom_ id, or null when the item is not filed in a room.

  • roomNamestring | nullrequired

  • namestringrequired

    The line as the app shows it: the retailer title when chosen, else the description.

  • descriptionstringrequired

  • retailerDescriptionstring | nullrequired

  • modelstring | nullrequired

  • categorystring | nullrequired

  • quantityintegerrequired

  • unitCostCentsinteger | nullrequired

    Null until the item is priced.

  • pricedbooleanrequired

    True once the item has a unit cost. An unpriced item cannot be approved: set unitCostCents or accept a replacement first.

  • status"pending" | "low_confidence" | "reviewing" | "approved" | "rejected"required

    pending and low_confidence await review; reviewing is being priced (the item is locked: edits and approval are refused until it finishes); approved items count toward totals and exports; rejected items are left out of both.

  • needsReviewbooleanrequired

    True while the item waits on a person: status pending or low_confidence.

  • condition"New" | "Above Average" | "Average" | "Below Average" | nullrequired

  • estimatedAgeYearsnumber | nullrequired

  • source"ai" | "manual"required

    ai: identified from a photo; manual: entered or imported.

  • priceSourcestring | nullrequired

    Where the price came from (retailer name).

  • priceSourceUrlstring | nullrequired

  • notesstring | nullrequired

  • photoIdsstring[]required

    pho_ ids the item appears in, primary photo first.

  • valuationobjectrequired

  • valuation.subtotalCentsintegerrequired

    Quantity x unit cost, before tax.

  • valuation.taxCentsintegerrequired

    Sales tax at the claim's salesTaxBp.

  • valuation.rcvCentsintegerrequired

    Replacement cost value: subtotal plus tax.

  • valuation.depreciationBpintegerrequired

    Depreciation rate applied, in basis points (2500 = 25%).

  • valuation.depreciationCentsintegerrequired

  • valuation.acvCentsintegerrequired

    Actual cash value: RCV less depreciation.

  • valuation.paymentCentsintegerrequired

    What the claim pays for the line after any payment cap.

  • createdAtstring (date-time)required

  • updatedAtstring (date-time)required

curl
curl -X POST 'https://contents.team/api/v1/items/{itemId}/replacements/{replacementId}/accept' \
  -H "Authorization: Bearer $CONTENTS_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"quantity":1,"quantityConfirmed":true,"keepItemTitle":true}'

Exports

Get an export

get/api/v1/exports/{exportId}

Status and progress. Once completed, downloads holds presigned URLs valid for 15 minutes; call again for fresh ones. Needs exports:write (whoever can start an export can collect it).

Scope: exports:write

Parameters

  • exportIdstring · pathrequired

    exp_ id.

›Response 200Success
  • idstringrequired

    exp_ id.

  • object"export"required

  • claimIdstringrequired

  • format"pdf" | "xlsx" | "photo_zip"required

  • status"queued" | "processing" | "completed" | "failed" | "expired" | "cancelled"required

  • progressintegerrequired

    0-100.

  • filenamestring | nullrequired

  • fileSizeBytesinteger | nullrequired

  • errorobject | nullrequired

  • error.codestringrequired

  • error.messagestringrequired

  • downloadsobject[]required

    Empty until the export completes. A large photo ZIP comes in several parts.

  • downloads[].urlstringrequired

    Presigned storage URL; GET it before expiresAt.

  • downloads[].filenamestringrequired

  • downloads[].bytesinteger | nullrequired

  • downloads[].expiresAtstring (date-time)required

  • createdAtstring (date-time)required

  • completedAtstring (date-time) | nullrequired

  • expiresAtstring (date-time) | nullrequired

    When the finished file is deleted; export again after that.

curl
curl -X GET 'https://contents.team/api/v1/exports/{exportId}' \
  -H "Authorization: Bearer $CONTENTS_API_KEY"

Start a report export

post/api/v1/claims/{claimId}/exports

Queues a PDF inventory report, an Excel inventory or a ZIP of the claim's photos, with the app's default options. Returns 202 with the export; poll GET /api/v1/exports/{exportId} until status is completed. At most 3 exports per organization run at once (429 export_concurrency_limit).

Scope: exports:write

Parameters

  • claimIdstring · pathrequired

    clm_ id.

  • Idempotency-Keystring · headerrequired

    Required on every POST (a UUID works). Retrying with the same key and body returns the first successful response (with Idempotent-Replayed: true) instead of acting twice; the same key with a different body is a 409 idempotency_key_reused. Errors are not stored, so a failed request can be retried with the same key. Keys are per organization and expire after 24 hours.

Request body

  • format"pdf" | "xlsx" | "photo_zip"required

    pdf = the inventory report; xlsx = the inventory spreadsheet; photo_zip = every photo.

›Response 202Success
  • idstringrequired

    exp_ id.

  • object"export"required

  • claimIdstringrequired

  • format"pdf" | "xlsx" | "photo_zip"required

  • status"queued" | "processing" | "completed" | "failed" | "expired" | "cancelled"required

  • progressintegerrequired

    0-100.

  • filenamestring | nullrequired

  • fileSizeBytesinteger | nullrequired

  • errorobject | nullrequired

  • error.codestringrequired

  • error.messagestringrequired

  • downloadsobject[]required

    Empty until the export completes. A large photo ZIP comes in several parts.

  • downloads[].urlstringrequired

    Presigned storage URL; GET it before expiresAt.

  • downloads[].filenamestringrequired

  • downloads[].bytesinteger | nullrequired

  • downloads[].expiresAtstring (date-time)required

  • createdAtstring (date-time)required

  • completedAtstring (date-time) | nullrequired

  • expiresAtstring (date-time) | nullrequired

    When the finished file is deleted; export again after that.

curl
curl -X POST 'https://contents.team/api/v1/claims/{claimId}/exports' \
  -H "Authorization: Bearer $CONTENTS_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"format":"pdf"}'

Webhooks

Get a webhook endpoint

get/api/v1/webhook-endpoints/{endpointId}

Scope: webhooks:manage

Parameters

  • endpointIdstring · pathrequired

    whe_ id.

›Response 200Success
  • idstringrequired

    whe_ id.

  • object"webhook_endpoint"required

  • urlstringrequired

  • descriptionstring | nullrequired

  • events"claim.created" | "photos.analysis_completed" | "items.priced" | "inventory.approved" | "export.ready"[]required

  • mode"live" | "test"required

    Receives only events of this mode (the mode of the key that registered it).

  • status"enabled" | "disabled"required

  • disabledReason"failing" | nullrequired

    failing: every delivery failed for three days. Register the URL again to resume.

  • lastSuccessAtstring (date-time) | nullrequired

  • createdAtstring (date-time)required

curl
curl -X GET 'https://contents.team/api/v1/webhook-endpoints/{endpointId}' \
  -H "Authorization: Bearer $CONTENTS_API_KEY"

Delete a webhook endpoint

DEL/api/v1/webhook-endpoints/{endpointId}

Stops deliveries immediately, including pending retries.

Scope: webhooks:manage

Parameters

  • endpointIdstring · pathrequired

    whe_ id.

Response 204

No content

curl
curl -X DELETE 'https://contents.team/api/v1/webhook-endpoints/{endpointId}' \
  -H "Authorization: Bearer $CONTENTS_API_KEY"

List webhook endpoints

get/api/v1/webhook-endpoints

The endpoints the calling key or OAuth app registered, in its mode, newest first (at most 20, one page). Endpoints registered in Settings or by another credential are not listed.

Scope: webhooks:manage

›Response 200Success
  • object"list"required

  • dataobject[]required

  • data[].idstringrequired

    whe_ id.

  • data[].object"webhook_endpoint"required

  • data[].urlstringrequired

  • data[].descriptionstring | nullrequired

  • data[].events"claim.created" | "photos.analysis_completed" | "items.priced" | "inventory.approved" | "export.ready"[]required

  • data[].mode"live" | "test"required

    Receives only events of this mode (the mode of the key that registered it).

  • data[].status"enabled" | "disabled"required

  • data[].disabledReason"failing" | nullrequired

    failing: every delivery failed for three days. Register the URL again to resume.

  • data[].lastSuccessAtstring (date-time) | nullrequired

  • data[].createdAtstring (date-time)required

  • hasMorebooleanrequired

  • nextCursorstring | nullrequired

    Pass as cursor for the next page; null on the last page.

curl
curl -X GET 'https://contents.team/api/v1/webhook-endpoints' \
  -H "Authorization: Bearer $CONTENTS_API_KEY"

Register a webhook endpoint

post/api/v1/webhook-endpoints

Events are POSTed as JSON with a Contents-Signature: t=<unix>,v1=<hex> header, HMAC-SHA256 of <t>.<raw body> keyed by the secret returned here (once). Failed deliveries retry with backoff for about three days; an endpoint that fails for three days is disabled. The endpoint receives events of the calling key's mode only. It belongs to the calling credential: only the same API key (or the same OAuth app connection) can list, read, test, replay or delete it through the API; others get a 404. Owners and admins see every endpoint in Settings.

Scope: webhooks:manage

Parameters

  • Idempotency-Keystring · headerrequired

    Required on every POST (a UUID works). Retrying with the same key and body returns the first successful response (with Idempotent-Replayed: true) instead of acting twice; the same key with a different body is a 409 idempotency_key_reused. Errors are not stored, so a failed request can be retried with the same key. Keys are per organization and expire after 24 hours.

Request body

  • urlstringrequired

    HTTPS URL on a public hostname. Private, loopback and link-local addresses are refused.

  • descriptionstring

  • events"claim.created" | "photos.analysis_completed" | "items.priced" | "inventory.approved" | "export.ready"[]required

    Event types to receive.

›Response 201Success
  • idstringrequired

    whe_ id.

  • object"webhook_endpoint"required

  • urlstringrequired

  • descriptionstring | nullrequired

  • events"claim.created" | "photos.analysis_completed" | "items.priced" | "inventory.approved" | "export.ready"[]required

  • mode"live" | "test"required

    Receives only events of this mode (the mode of the key that registered it).

  • status"enabled" | "disabled"required

  • disabledReason"failing" | nullrequired

    failing: every delivery failed for three days. Register the URL again to resume.

  • lastSuccessAtstring (date-time) | nullrequired

  • createdAtstring (date-time)required

  • secretstringrequired

    whsec_ signing secret. Shown only in this response: store it now.

curl
curl -X POST 'https://contents.team/api/v1/webhook-endpoints' \
  -H "Authorization: Bearer $CONTENTS_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"url":"string","events":["claim.created"]}'

List an endpoint's deliveries

get/api/v1/webhook-endpoints/{endpointId}/deliveries

Newest first, with the status, attempt count and the start of your endpoint's last response.

Scope: webhooks:manage

Parameters

  • endpointIdstring · pathrequired

    whe_ id.

  • limitinteger · query

    Page size, 1-100.

  • cursorstring · query

    nextCursor from the previous page.

›Response 200Success
  • object"list"required

  • dataobject[]required

  • data[].idstringrequired

    whd_ id.

  • data[].object"webhook_delivery"required

  • data[].endpointIdstringrequired

    whe_ id.

  • data[].eventIdstringrequired

    evt_ id; also the id in the event body.

  • data[].eventTypestringrequired

  • data[].status"pending" | "sending" | "succeeded" | "failed"required

  • data[].attemptsintegerrequired

  • data[].responseCodeinteger | nullrequired

  • data[].responseSnippetstring | nullrequired

    First 1 KB of your endpoint's last response.

  • data[].errorstring | nullrequired

  • data[].nextAttemptAtstring (date-time) | nullrequired

  • data[].lastAttemptAtstring (date-time) | nullrequired

  • data[].deliveredAtstring (date-time) | nullrequired

  • data[].createdAtstring (date-time)required

  • hasMorebooleanrequired

  • nextCursorstring | nullrequired

    Pass as cursor for the next page; null on the last page.

curl
curl -X GET 'https://contents.team/api/v1/webhook-endpoints/{endpointId}/deliveries' \
  -H "Authorization: Bearer $CONTENTS_API_KEY"

Replay a delivery

post/api/v1/webhook-endpoints/{endpointId}/deliveries/{deliveryId}/replay

Sends the same event (same id, same body) again now, with a fresh retry budget. Deduplicate on the event id if you may have processed it already.

Scope: webhooks:manage

Parameters

  • endpointIdstring · pathrequired

    whe_ id.

  • deliveryIdstring · pathrequired

    whd_ id.

  • Idempotency-Keystring · headerrequired

    Required on every POST (a UUID works). Retrying with the same key and body returns the first successful response (with Idempotent-Replayed: true) instead of acting twice; the same key with a different body is a 409 idempotency_key_reused. Errors are not stored, so a failed request can be retried with the same key. Keys are per organization and expire after 24 hours.

›Response 202Success
  • idstringrequired

    whd_ id.

  • object"webhook_delivery"required

  • endpointIdstringrequired

    whe_ id.

  • eventIdstringrequired

    evt_ id; also the id in the event body.

  • eventTypestringrequired

  • status"pending" | "sending" | "succeeded" | "failed"required

  • attemptsintegerrequired

  • responseCodeinteger | nullrequired

  • responseSnippetstring | nullrequired

    First 1 KB of your endpoint's last response.

  • errorstring | nullrequired

  • nextAttemptAtstring (date-time) | nullrequired

  • lastAttemptAtstring (date-time) | nullrequired

  • deliveredAtstring (date-time) | nullrequired

  • createdAtstring (date-time)required

curl
curl -X POST 'https://contents.team/api/v1/webhook-endpoints/{endpointId}/deliveries/{deliveryId}/replay' \
  -H "Authorization: Bearer $CONTENTS_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"

Send a test event

post/api/v1/webhook-endpoints/{endpointId}/test

Queues a webhook.ping event to this endpoint only and returns its delivery; poll the delivery log to see your endpoint's response.

Scope: webhooks:manage

Parameters

  • endpointIdstring · pathrequired

    whe_ id.

  • Idempotency-Keystring · headerrequired

    Required on every POST (a UUID works). Retrying with the same key and body returns the first successful response (with Idempotent-Replayed: true) instead of acting twice; the same key with a different body is a 409 idempotency_key_reused. Errors are not stored, so a failed request can be retried with the same key. Keys are per organization and expire after 24 hours.

›Response 202Success
  • idstringrequired

    whd_ id.

  • object"webhook_delivery"required

  • endpointIdstringrequired

    whe_ id.

  • eventIdstringrequired

    evt_ id; also the id in the event body.

  • eventTypestringrequired

  • status"pending" | "sending" | "succeeded" | "failed"required

  • attemptsintegerrequired

  • responseCodeinteger | nullrequired

  • responseSnippetstring | nullrequired

    First 1 KB of your endpoint's last response.

  • errorstring | nullrequired

  • nextAttemptAtstring (date-time) | nullrequired

  • lastAttemptAtstring (date-time) | nullrequired

  • deliveredAtstring (date-time) | nullrequired

  • createdAtstring (date-time)required

curl
curl -X POST 'https://contents.team/api/v1/webhook-endpoints/{endpointId}/test' \
  -H "Authorization: Bearer $CONTENTS_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"

Meta

The calling credential and its organization

get/api/v1/me

Use it to check a key works: returns the org, live/test mode and the key's scopes. Needs no scope.

›Response 200Success
  • object"me"required

  • organizationobjectrequired

  • organization.idstringrequired

    org_ id.

  • organization.namestringrequired

  • mode"live" | "test"required

  • credentialobjectrequired

  • credential.type"api_key" | "oauth"required

  • credential.namestringrequired

    The API key name, or the OAuth app name.

  • credential.prefixstringrequired

    First characters of the key, as Settings shows it; "cto_" for an OAuth token.

  • credential.scopes"claims:read" | "claims:write" | "photos:read" | "photos:write" | "items:read" | "exports:write" | "webhooks:manage" | "items:write"[]required

  • credential.createdAtstring (date-time)required

  • credential.lastUsedAtstring (date-time) | nullrequired

  • credential.expiresAtstring (date-time) | nullrequired

curl
curl -X GET 'https://contents.team/api/v1/me' \
  -H "Authorization: Bearer $CONTENTS_API_KEY"

This OpenAPI document

get/api/v1/openapi.json

Also served as YAML at /openapi/v1.yaml. No authentication.

›Response 200Success
  • openapistringrequired

curl
curl -X GET 'https://contents.team/api/v1/openapi.json' \
  -H "Authorization: Bearer $CONTENTS_API_KEY"