ThingzSafe

Household and Pro

Developer API reference

Connect your own application to your ThingzSafe inventory.

The developer API is included with Household and Pro. It is for developers and technically confident users who want their own application or script to search inventory, calculate valuations, read supported attachments and make approved changes. If you only want to ask questions in everyday words, connect ChatGPT instead.

You can try everything in the web app first: open How to use API, run a search or valuation report, inspect the request and response and copy a working example.

API quick start and access tokens

Base URL and authentication

Use HTTPS at https://app.thingzsafe.com. Send a developer token in the request header:

Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

Use a developer token created in the web app, not an Android/device sign-in token or an AI assistant OAuth token. Each token is tied to one household and its verified owner. Active Household or Pro cloud access is required.

Create a token

  1. Sign in to the web app as the household owner.
  2. Open How to use API.
  3. Enter a connection name and an expiry of 1–90 days. The default is 30 days.
  4. Select the required permissions and choose Create token.
  5. Copy the token while it is shown. It cannot be displayed again later. Store it in your application's secret configuration.

The initial selections are inventory reading and valuation reports. Inventory changes and file reading are separate optional selections. A household can have up to 20 active, unexpired developer tokens. Use Revoke to stop an unused token; creating a replacement does not automatically revoke the previous one.

Scope code Developer API ChatGPT
inventory.read Inventory and location/category lookup Same
reports.read Valuation reports Same
inventory.write Supported create/edit/move batches Same
files.read Selected attachment listing/reading; also requires inventory.read Same
feedback.write Not issued by the developer-token screen in this release Support report drafts/submission; also requires inventory.read

Developer write routes require inventory.write; include inventory.read as well if your application looks up records before proposing changes. AI assistant write tools require both.

Try a read in PowerShell

The following prompts for the token locally without displaying it. It uses the existing Sites in your household and changes no inventory.

$token = [System.Net.NetworkCredential]::new('', (Read-Host 'Developer token' -AsSecureString)).Password
$headers = @{ Authorization = "Bearer $token" }
$body = @{ kind = 'Site'; offset = 0; limit = 100 } | ConvertTo-Json
Invoke-RestMethod -Method Post `
  -Uri 'https://app.thingzsafe.com/v1/developer/query' `
  -Headers $headers -ContentType 'application/json' -Body $body

Try a read with curl

Put your token in a local secret/environment variable named THINGZSAFE_TOKEN. Save the JSON below as sites.json:

{"kind":"Site","offset":0,"limit":100}
curl 'https://app.thingzsafe.com/v1/developer/query' \
  -H "Authorization: Bearer $THINGZSAFE_TOKEN" \
  -H 'Content-Type: application/json' \
  --data-binary @sites.json

On Windows PowerShell, use the PowerShell example above, or invoke curl.exe with Windows shell syntax. Never publish a real token in an example, screenshot or support conversation.

Shared request format, IDs and pagination

Endpoint map

Method Path Purpose
POST /v1/developer/query Sites, Areas, Categories, Things and valuation queries
POST /v1/developer/attachments/list List attachments for selected Thing IDs
POST /v1/developer/attachments/read Read one selected attachment or PDF page batch
POST /v1/developer/batches/preview Preview create/edit/move changes
POST /v1/developer/batches/execute Execute exactly one approved preview
GET /v1/developer/batches/{batchId} Recover a preview or execution result using the same credential

These are the developer-facing routes documented here. The web app's own authenticated management routes are not interchangeable with developer-token routes. In particular, a developer token cannot create or enlarge its own permissions.

Query fields

{
  "text": null,
  "siteId": null,
  "areaId": null,
  "categoryId": null,
  "kind": null,
  "relationship": null,
  "parentThingId": null,
  "includeDescendants": false,
  "missingValueOnly": false,
  "offset": 0,
  "limit": 100,
  "report": false
}
Field Meaning
text Optional case-insensitive substring search; maximum 160 characters
siteId, areaId, categoryId Optional IDs from previous lookups; omit or use null for no filter
kind Site, Area or Category for lookup; omitted/null means Things
relationship Any, Thing, Part or Content; omitted/null includes all relationships
parentThingId Select direct children of that Thing, or descendants when requested
includeDescendants With a parent ID, include nested descendants; the parent itself is excluded
missingValueOnly Only records with neither an entered value nor an available estimate
offset Zero-based start, 0–5,000
limit Page size, 1–200; default 100
report True for valuation totals; requires reports.read

Do not combine a metadata kind lookup with hierarchy fields, or use a metadata kind with report:true. Use IDs, not location names, in the ID fields. Ambiguous names must be resolved before choosing an ID.

IDs may be compact 32-character GUID strings or standard hyphenated GUID strings. Keep returned IDs unchanged. All IDs in the examples below are fictional placeholders: look up and substitute your own.

Example Fictional ID
Site 11111111-1111-4111-8111-111111111111
Area 22222222-2222-4222-8222-222222222222
Category 33333333-3333-4333-8333-333333333333
Thing 44444444-4444-4444-8444-444444444444
Blob 66666666-6666-4666-8666-666666666666

Follow every page

Read totalCount, returnedCount, hasMore and nextOffset. While hasMore is true, repeat the same filters using the returned next offset. Do not report a page's item count as the total count.

Results are live rather than a frozen export. If inventory changes during paging, re-run the query for a consistent fresh assessment.

Sites

Permission: inventory.read

Use the shared query endpoint to discover Site IDs. A Site is a top-level location, such as House or Barn.

{"kind":"Site","text":"House","offset":0,"limit":100}

Read each returned item's id, kind, name and revision. Other generic fields may be present and need not be used for a Site lookup. Follow pagination if there are more results.

Sites are read through this interface. To create, rename or delete a Site, ask your connected AI assistant (it shows a preview for your approval) or use the web or Android app. To retrieve Things under a Site, omit kind and filter by the chosen siteId.

Areas

Permission: inventory.read

An Area belongs to a Site, such as Kitchen in House. Look it up within its Site to avoid mixing identically named Areas from different Sites.

{
  "kind":"Area",
  "siteId":"11111111-1111-4111-8111-111111111111",
  "text":"Kitchen",
  "offset":0,
  "limit":100
}

Read the Area's returned id and siteId. To search Things in the Area, omit kind and supply its areaId; you may also supply its Site ID. If both filters are supplied, both must match. A mismatched pair returns no matching inventory rather than silently selecting another Area.

Areas are read through this interface. To create, rename or delete an Area, ask your connected AI assistant (it shows a preview for your approval) or use the web or Android app. An Area that still holds things cannot be deleted. In the web tester, changing Site refreshes the available Areas; an invalid old Area selection is cleared.

Categories

Permission: inventory.read

Categories classify Things independently of location. Find an existing Category before using its ID as a filter or assigning it in a supported create/edit batch.

{"kind":"Category","text":"Cameras","offset":0,"limit":100}

The lookup returns IDs for Categories already in the household. Resolve an ambiguous name rather than guessing. To search Things in that Category, omit kind and use categoryId.

This is a lookup interface. Category creation, schema/custom-field editing and deletion are not exposed. A Category lookup is not a complete export of all category settings or custom-field definitions.

Things, Parts and Contents

Permission: inventory.read

Search top-level Things

{
  "text":"camera",
  "categoryId":"33333333-3333-4333-8333-333333333333",
  "relationship":"Thing",
  "offset":0,
  "limit":100
}

The text search checks name, description, model, serial number and barcode using a case-insensitive substring match. It is not a semantic search and does not search attachment contents or notes. Combine filters only when every filter should apply.

Direct Contents of a Thing

{
  "parentThingId":"44444444-4444-4444-8444-444444444444",
  "relationship":"Content",
  "includeDescendants":false,
  "offset":0,
  "limit":100
}

For direct Parts, change relationship to Part. For all direct children, omit the relationship filter. Use includeDescendants:true to include nested descendants. A relationship filter still applies to each returned descendant. The parent itself is excluded.

relationship:"Thing" means top-level records only and excludes both Parts and Contents. Any, omitted or null includes all relationships. The model uses the singular Content in the request even though the user interface says Contents.

Important returned fields

Field Meaning
id, revision Stable record ID and current revision
name, kind Display name and record kind
relationship Thing, Part or Content
siteId, areaId, parentThingId Location/ownership references
siteName, areaName, parentThingName Available display names
categoryId Resolved Category ID, where available
quantity Quantity represented by that record
currentValue Entered value each, possibly null
effectiveValue Entered value, otherwise the available purchase-price estimate
estimated, valueSource Whether the effective value is estimated; source is entered, estimate or missing
currency Currency used for that record
status Raw stored status representation; no status-filter parameter is exposed
createdAt, purchaseDate Available recorded dates; neither is an installation/fitting date

This response is a defined projection, not the complete underlying Thing JSON. Notes, full custom fields, loan/rental/contact records and structured event histories are not returned by this query. Use the attachment endpoints to inspect supported attached content separately.

Valuations and missing values

Permission for reports: reports.read

Send the same relevant Thing filters with report:true. Omit kind.

{
  "areaId":"22222222-2222-4222-8222-222222222222",
  "relationship":"Thing",
  "report":true
}

A valuation report calculates across all matches on the server; you do not need to page through item lists to obtain its totals. A report's items is null, returnedCount is zero and hasMore is false, while totalCount still gives the matching-record count.

Illustrative response excerpt—not a full response:

{
  "totalCount":12,
  "returnedCount":0,
  "hasMore":false,
  "nextOffset":null,
  "summary":{
    "totalsByCurrency":{"GBP":1250.50},
    "missingValuations":2,
    "estimatedValuations":4,
    "enteredValuations":6
  },
  "items":null
}

Each effective value is multiplied by quantity. Currency totals remain separate. The counts describe records, not a sum of quantities.

An entered current value, including zero, takes precedence. Otherwise ThingzSafe may calculate an estimate from purchase price, purchase date, warranty/category rules and the household's new-for-old preference. Where the existing inventory rules permit it, undated Contents can have a purchase-price estimate; not every undated top-level Thing or Part has one. An estimate is not a fresh AI market valuation. This route does not call the Pro AI valuation service.

To list records with neither an entered nor an estimated value:

{"missingValueOnly":true,"offset":0,"limit":20}

By default, reports include all statuses and Parts/Contents. Specify relationship:"Thing" for top-level-only totals. The API does not apply all the web inventory valuation report's optional risk, exclusion or ownership filters, and it does not convert currencies.

Photos and documents

Permissions: inventory.read + files.read

List selected attachments

POST /v1/developer/attachments/list:

{
  "itemIds":["44444444-4444-4444-8444-444444444444"],
  "offset":0,
  "limit":50
}

Select 1–20 Thing IDs per request. The result is wrapped in isError, code, structuredContent and content. In structuredContent, inspect totalCount, returnedCount, hasMore, nextOffset and items.

Each file entry identifies its owning itemId, blobId, fileName, source, available mimeType and byte length, plus available. A file reference can exist even if its blob is unavailable. The same blob referenced repeatedly on one Thing is listed once; the same blob associated with two Things may appear for both.

Include the IDs of Parts/Contents separately when their attachments matter. A parent Thing alone does not implicitly include its children's files.

Read an attachment

POST /v1/developer/attachments/read:

{
  "itemId":"44444444-4444-4444-8444-444444444444",
  "blobId":"66666666-6666-4666-8666-666666666666",
  "page":1
}

Both IDs must come from the selected Thing and its file listing. The service checks the live relationship and household. It does not accept arbitrary storage URLs.

Illustrative response shape, with image data omitted:

{
  "isError":false,
  "code":null,
  "structuredContent":{
    "itemId":"44444444444444448444444444444444",
    "blobId":"66666666666646668666666666666666",
    "fileName":"Example receipt.pdf",
    "mimeType":"application/pdf",
    "pageCount":3,
    "firstPage":1,
    "returnedPages":2,
    "hasMore":true,
    "nextPage":3
  },
  "content":[
    {"type":"text","text":"Attachment metadata is supplied here."},
    {"type":"image","mimeType":"image/jpeg","data":"BASE64_JPEG_PAGE_1"},
    {"type":"image","mimeType":"image/jpeg","data":"BASE64_JPEG_PAGE_2"}
  ]
}

For this example, call again with page:3. PDF page numbering is one-based. Images and text documents use page 1 only.

Decode image blocks' base64 data as the stated MIME type. Text blocks contain extracted text. The returned images are prepared views, not an original-file download API. PDFs are rendered, so scanned documents can be inspected too. No public/SAS download URL is returned, and no AI analysis or receipt classification occurs on this API route.

Supported formats and omissions are listed in the ChatGPT files guide. In particular, DOCX is main-document text only, and XLSX/RTF are unsupported. Treat unavailable, unreadable, unsupported or partial reads as unexamined. Do not count them as negative evidence.

Create, edit and move Things

Use a two-stage flow: preview the exact proposal, show it to the user for approval, then execute the saved preview. Previewing does not change or lock the inventory while the user decides.

Each proposal needs a fresh requestId of 1–80 characters; a generated UUID is convenient. Use the same request ID and identical payload only for retries of that proposal. A developer batch is bound to the credential that created it. Use the same token for preview, execution and recovery.

Create top-level Things

POST /v1/developer/batches/preview:

{
  "requestId":"example-create-001",
  "action":"create",
  "siteId":"11111111-1111-4111-8111-111111111111",
  "areaId":"22222222-2222-4222-8222-222222222222",
  "categoryId":"33333333-3333-4333-8333-333333333333",
  "items":[{"name":"API test — delete later","quantity":1,"notes":"Created for API testing"}]
}

Use a real destination Site. The Area, if supplied, must belong to it. Omit Area to put the Thing directly in the Site. If Category is omitted, an existing Other category is used; if it is unavailable, choose an existing category explicitly. Creation is for top-level Things, not new Parts/Contents under a parent.

Edit existing Things

{
  "requestId":"example-edit-001",
  "action":"edit",
  "itemIds":["44444444-4444-4444-8444-444444444444"],
  "values":{"notes":"Checked through the API","needsReview":false}
}

The supplied values apply to every selected record. Omitted fields are preserved. An optional top-level categoryId can reassign the Category. Use a move action for location changes.

Supported values/create-item fields:

Field group Supported keys
Text name, notes, make, model, barcode, position, size
Numbers quantity, purchasePrice, currentValue
Dates purchaseDate, warrantyEndDate, currentValueDate
Other currentValueCurrency (three uppercase letters), needsReview (boolean)

Name must be non-empty; ordinary text is limited to 200 characters and notes to 4,000. Quantity must be 0–1,000,000, with whole quantities for top-level Things and Parts; existing Contents may permit fractions. Money values must be non-negative and no greater than 1,000,000,000,000. Use ISO date strings such as 2026-10-25. Optional nullable fields can be cleared with null; quantity and currency cannot be null, and name cannot be empty/null.

This is not a general record-patching interface: do not send status, photo IDs, parent IDs, loan/disposal information or arbitrary custom fields. Existing unrelated data is preserved on supported edits.

Move Things and their owned children

{
  "requestId":"example-move-001",
  "action":"move",
  "itemIds":["44444444-4444-4444-8444-444444444444"],
  "siteId":"11111111-1111-4111-8111-111111111111",
  "areaId":"22222222-2222-4222-8222-222222222222"
}

A selected Thing moves with its owned Parts/Contents, including nested children. Inspect the full expanded preview: the 100-record batch limit includes affected descendants. Selecting both parent and child does not move the child twice. Moving a selected child makes it a top-level Thing at the destination; moving a parent retains the descendants' ownership links beneath it. This operation does not move stock into another Thing or split a quantity.

Review the response

A preview includes batchId, previewHash, status, expiresAt, action, affectedCount and changes. Each change includes an ID, expected revision, before and after details. A create has no before record.

Show all affected records and before/after details to the user. Resolve display names from the location/category lookups where needed. A preview expires after 15 minutes. Relevant changes to records or a moved ownership tree make execution fail; prepare a new preview and obtain approval again.

Execute the approved preview

POST /v1/developer/batches/execute:

{
  "batchId":"BATCH_ID_FROM_PREVIEW",
  "previewHash":"EXACT_HASH_FROM_PREVIEW",
  "confirm":true
}

The two placeholder strings must be replaced with the server's actual values. confirm:true is the calling application's assertion that approval was obtained; it is not independent proof of a human click. Your application must implement the review/approval step.

Execution is all-or-nothing. A successful result has status Executed, the action, affected count and record IDs/revisions. Changes join normal cloud sync and have integration Events. Devices will see them when they next sync.

Recover after a timeout

GET /v1/developer/batches/{batchId} with the same developer credential. If already Executed, use the saved result. Otherwise, retry the exact batch/hash while valid. Do not generate another create request merely because a response was lost. Re-executing a completed batch returns its original result. Old completed records may be cleaned up after 90 days; do not use them as indefinite job storage.

Limits, errors and unsupported operations

Current bounds

Area Limit
Shared household read allowance 30 developer/MCP queries/reads per minute; retry handling can differ by route
Inventory evaluation 5,000 source records and 8 MiB encrypted data
Inventory result page 200 records maximum
Query request body 4,096 bytes
Developer write request body 65,536 bytes
Active inventory previews 10 per household; 15-minute expiry
Inventory batch Up to 100 affected Things including moved descendants; saved proposal up to 512 KiB
Committed write allowance 1,000 Things and 10 MiB compact audit payload per 24-hour usage window, shared across credentials
Attachment request body / MCP request body 16,384 bytes
Attachment selection/page 20 selected Thing IDs; up to 50 file references per page
Attachment metadata processing 10,000 relevant records / 16 MiB encrypted data; 5,000 selected file references
Source attachment 20 MiB per read
Prepared image/PDF page Maximum 1,800-pixel edge; up to 3 MiB encoded JPEG per image
Image source Up to 40 million pixels
PDF Up to 1,000 pages; at most two pages returned per read
TXT/CSV/HTML Up to 200,000 source bytes
DOCX extraction Up to 100,000 text characters; no layout/embedded-image inspection
File processing concurrency One download/render operation at a time per API process; retry busy responses
ChatGPT feedback Ten committed reports per rolling 24 hours, plus shared request allowance; 15-minute draft expiry

The shared minute allowance is used by inventory queries, attachment calls, new inventory previews and feedback preview/submit calls. Inventory execution and saved-batch recovery have their own checks; successful retries do not consume another committed-write allowance. Other HTTP rate limits also apply. Limits are not an entitlement to unlimited CPU, attachment analysis or ChatGPT usage. Treat explicit processing-limit errors as incomplete work rather than inventing totals.

Read failures correctly

Inventory query/write routes generally return a JSON code on application errors. Attachment routes use an isError/code wrapper as well as HTTP status. Inspect both the HTTP status and response body. MCP tools can return HTTP 200 with result.isError:true; inspect the tool result rather than assuming transport success means the operation succeeded.

Status or code Meaning / next step
400 / InvalidQuery, validation codes Correct field names, types, IDs or bounds
401 / InvalidCredential Missing, expired, wrong-type or revoked credential; check/reconnect without exposing it
403 / ScopeRequired, WriteAccessRequired, AttachmentAccessRequired Required permission or active owner/household access is missing
404 / BatchNotFound, ThingNotFound, AttachmentNotLinkedToThing, AttachmentUnavailable Check ownership/IDs and whether the resource still exists; do not guess another blob
409 / PreviewStale, OwnershipTreeChanged Read current state, prepare a new preview and obtain approval again
409 / PreviewHashMismatch, RequestIdReused Use the saved hash/exact original retry payload, or a fresh request ID for a genuinely new proposal
410 / PreviewExpired, FeedbackPreviewExpired Unused preview expired; review a new draft before approval
413 / BatchTooLarge, request-size failure Reduce the request or affected set
422 / InventoryProcessingLimit, IntegrationInventoryLimit, attachment processing errors Work could not be completed within bounds; do not present a partial result as complete
429 / HouseholdReadLimit, WriteAllowance, FeedbackDailyLimit Pause and retry within the relevant allowance; observe Retry-After where supplied
503 / HouseholdBusy, CommitBusy, FeedbackBusyRetrySamePreview Short-lived contention: retry the same operation, not a new create proposal
AttachmentRendererBusy Retry the same attachment/page later; for MCP, this is a tool-level error
AttachmentContentInvalid, AttachmentUnreadable, AttachmentFormatUnsupported The attachment was not successfully inspected; report it as unexamined

Error names are route-specific; an error body need not contain a human-readable message. Log the code and request context, but keep tokens and attachment payloads out of application logs.

Not exposed in this release

Site/Area/Category creation or deletion; Thing deletion; creating new Parts/Contents under a parent; attachment uploads or original-file downloads; contact queries; structured loan/rental/reservation queries or actions; fitted/installation history; Pro AI recognition/valuation actions; arbitrary status/custom-field changes; currency conversion; public access to the support log. Use the ordinary app/web features where available.

MCP tools and advanced connection details

Most developers should use the developer-token routes above. MCP is the separate interface used by the ChatGPT, Claude and Google Gemini connections; it uses OAuth rather than a developer token. All three use the same server address. ChatGPT uses client ID thingzsafe-chatgpt and Claude uses thingzsafe-claude. Gemini connects through the published ThingzSafe app, so it needs no client details.

MCP tool Purpose
get_connection_profile Linked household and granted scopes
list_inventory_locations Site/Area/Category lookup
search_inventory Thing/Part/Content search
inventory_valuation Valuation summary
list_inventory_attachments Selected file metadata
read_inventory_attachment Images, PDF pages or supported text
preview_inventory_changes Saved create/edit/move proposal
execute_inventory_changes Commit the approved inventory proposal
get_inventory_change_batch Recover proposal/execution result
preview_feedback_report Exact bug/change report draft; no submission
submit_feedback_report Submit the approved support report

MCP endpoint: https://app.thingzsafe.com/mcp. It supports stateless Streamable HTTP POST with negotiated protocol versions 2025-03-26 and 2025-06-18. Tool discovery is public; private tool calls require authorisation.

Discovery endpoints are /.well-known/oauth-protected-resource, /.well-known/oauth-protected-resource/mcp and /.well-known/oauth-authorization-server. The static public client ID is thingzsafe-chatgpt; token endpoint authentication is none, with mandatory PKCE S256. The default configured callback is https://chatgpt.com/connector_platform_oauth_redirect. This is not a promise that arbitrary third-party OAuth client IDs or redirect URLs can register themselves.

Read the tool schemas rather than copying developer query bodies unchanged: for example, inventory_valuation selects report behavior through the tool name and does not accept a report field. list_inventory_locations uses kind; search_inventory selects Things without a kind argument. Follow isError, scopes and continuation fields.

Support reports map Bug to a failed supported operation and Change to a feature request. Both report tools require inventory.read plus feedback.write. Show the complete comment, destination and account/household association before submission. Submit the exact previewId/previewToken with confirm:true. The sealed token is connection-bound and expires in 15 minutes if unused. There is no equivalent developer-token feedback endpoint in this release.

Never publish a real token in an example, screenshot or support conversation. If a token is exposed, revoke it in How to use API.

ChatGPT and API help · What it does · Compare plans

Browse all help topics · Compare plans