Neatkept API reference
Base URL https://api.neatkept.com. Every call sends Authorization: Bearer nk_pat_…; read calls work with any token, write calls need a read-and-write one.
A call a token can't make answers 403 problems/token-not-allowed, a write with a read token 403 problems/token-read-only, and too many calls 429 problems/rate-limited with Retry-After. The guide has the limits and the other errors.
Households
GET /v1/fx-rates read
The ECB reference rates, for approximate conversion.
How many units of each currency one euro buys, from the ECB's daily file (the worker refreshes it every few
hours). EUR itself is not listed. date is null and rates empty before the first refresh. Conversions are
for display only.
GET /v1/households read
Households the current user belongs to, with their role.
Requires Authorization: Bearer <access token>; 401 otherwise.
| Response | Type | Description |
|---|---|---|
200 | HouseholdList | The user's households. |
429 | Problem | Rate limited; retry after the given number of seconds. |
error | Problem | Error (RFC 9457 problem details). |
GET /v1/households/{hid}/plan read
The household's plan, its limits and this month's usage.
Any member may read; can_purchase says whether the caller may buy (owners).
| Parameter | Type | Description |
|---|---|---|
hidpath required | string (uuid) | A household the caller belongs to; 404 otherwise. |
| Response | Type | Description |
|---|---|---|
200 | HouseholdPlanStatus | Plan status. |
429 | Problem | Rate limited; retry after the given number of seconds. |
error | Problem | Error (RFC 9457 problem details). |
Locations
GET /v1/households/{hid}/locations read
The household's location tree as a flat list (parent_id links the tree).
Without updated_since: live locations only. With it: every location changed after that
instant, deleted ones included as tombstones (deleted_at set). Ordered by updated_at, then
id; for sync pass the largest updated_at seen (rows from that same millisecond may repeat).
Any member may read.
| Parameter | Type | Description |
|---|---|---|
hidpath required | string (uuid) | A household the caller belongs to; 404 otherwise. |
updated_sincequery | string (date-time) | Only rows changed after this instant, tombstones (deleted_at set) included. Groundwork for sync. |
limitquery | integer 1–500; default 100 | |
cursorquery | string | Opaque value from a previous page's next_cursor. |
| Response | Type | Description |
|---|---|---|
200 | LocationList | A page of locations. |
429 | Problem | Rate limited; retry after the given number of seconds. |
error | Problem | Error (RFC 9457 problem details). |
GET /v1/households/{hid}/locations/{id} read
One location.
For a client without the whole list at hand, e.g. an assistant's tool acting on one place.
A deleted location comes back as its tombstone (deleted_at), so the client can say so and
restore it whole. 404 for an unknown location. Any member may read.
| Parameter | Type | Description |
|---|---|---|
hidpath required | string (uuid) | A household the caller belongs to; 404 otherwise. |
idpath required | string (uuid) | The row's ID, generated by the client (UUID v7). |
PUT /v1/households/{hid}/locations/{id} write
Create or update a location under the client's ID (idempotent upsert).
Creates the location if the ID is new, otherwise replaces its fields. Repeating the same body
changes nothing, version included. A property has no parent_id; every other kind has one
of a shallower kind in the same household (property → room → storage → zone → shelf, levels
may be skipped). Moving a location under its own descendant is refused. Changing kind is
allowed while the parent and the children still fit. A name is unique among the location's
siblings (same parent; for a property, the household), ignoring case and deleted ones: 422
name otherwise. 422 names the offending field; 409 if the
ID belongs to another household; 410 if the location was deleted. Needs editor or owner.
| Parameter | Type | Description |
|---|---|---|
hidpath required | string (uuid) | A household the caller belongs to; 404 otherwise. |
idpath required | string (uuid) | The row's ID, generated by the client (UUID v7). |
If-Matchheader | string pattern ^"[0-9]{1,18}"$ | The version the client last saw, as an entity tag: |
If-None-Matchheader | string: * |
|
restorequery | boolean default false | A deleted row is brought back with this body instead of answering 410: its ID, photos
and moves stay. The usual rules apply (a live parent, a free code). |
Body: LocationInput
| Response | Type | Description |
|---|---|---|
200 | Location | The location as stored. |
412 | LocationConflict | Changed since the |
429 | Problem | Rate limited; retry after the given number of seconds. |
error | Problem | Error (RFC 9457 problem details). |
DELETE /v1/households/{hid}/locations/{id} write
Soft-delete an empty location.
Only an empty location can be deleted: 409 while it still holds live locations, containers or items. Deleting an already deleted location is a 204 too. Needs editor or owner.
| Parameter | Type | Description |
|---|---|---|
hidpath required | string (uuid) | A household the caller belongs to; 404 otherwise. |
idpath required | string (uuid) | The row's ID, generated by the client (UUID v7). |
If-Matchheader | string pattern ^"[0-9]{1,18}"$ | The version the client last saw, as an entity tag: |
| Response | Type | Description |
|---|---|---|
204 | Deleted (a tombstone stays for sync). | |
412 | LocationConflict | Changed since the |
429 | Problem | Rate limited; retry after the given number of seconds. |
error | Problem | Error (RFC 9457 problem details). |
Containers
GET /v1/households/{hid}/containers read
The household's containers (boxes, bags, crates).
Same list rules as locations: live rows only, or every change incl. tombstones with
updated_since; ordered by updated_at, then id. location_id narrows to the containers
directly in that location, carried=true to the ones someone has with them (not with
updated_since: 400). Any member may read.
| Parameter | Type | Description |
|---|---|---|
hidpath required | string (uuid) | A household the caller belongs to; 404 otherwise. |
location_idquery | string (uuid) | |
carriedquery | boolean default false | Only what someone has with them ( |
updated_sincequery | string (date-time) | Only rows changed after this instant, tombstones (deleted_at set) included. Groundwork for sync. |
limitquery | integer 1–500; default 100 | |
cursorquery | string | Opaque value from a previous page's next_cursor. |
| Response | Type | Description |
|---|---|---|
200 | ContainerList | A page of containers. |
429 | Problem | Rate limited; retry after the given number of seconds. |
error | Problem | Error (RFC 9457 problem details). |
GET /v1/households/{hid}/containers/{id} read
One container.
For a client without the whole list at hand, e.g. an assistant's tool acting on one box.
A deleted container comes back as its tombstone (deleted_at), so the client can say so and
restore it whole. 404 for an unknown container. Any member may read.
| Parameter | Type | Description |
|---|---|---|
hidpath required | string (uuid) | A household the caller belongs to; 404 otherwise. |
idpath required | string (uuid) | The row's ID, generated by the client (UUID v7). |
PUT /v1/households/{hid}/containers/{id} write
Create or update a container under the client's ID (idempotent upsert).
Same upsert rules as locations. location_id must be a live location of this household (any
kind); changing it moves the container with everything in it. code is stored normalized
(see resolve) and must not be on another live container or location of this household (422 on
code). Without code, a new container gets a generated one and an existing one keeps its
own. Needs editor or owner.
| Parameter | Type | Description |
|---|---|---|
hidpath required | string (uuid) | A household the caller belongs to; 404 otherwise. |
idpath required | string (uuid) | The row's ID, generated by the client (UUID v7). |
If-Matchheader | string pattern ^"[0-9]{1,18}"$ | The version the client last saw, as an entity tag: |
If-None-Matchheader | string: * |
|
restorequery | boolean default false | A deleted row is brought back with this body instead of answering 410: its ID, photos
and moves stay. The usual rules apply (a live parent, a free code). |
Body: ContainerInput
| Response | Type | Description |
|---|---|---|
200 | Container | The container as stored. |
412 | ContainerConflict | Changed since the |
429 | Problem | Rate limited; retry after the given number of seconds. |
error | Problem | Error (RFC 9457 problem details). |
DELETE /v1/households/{hid}/containers/{id} write
Soft-delete an empty container.
409 while it still holds live items; a repeat is a 204 too. Needs editor or owner.
| Parameter | Type | Description |
|---|---|---|
hidpath required | string (uuid) | A household the caller belongs to; 404 otherwise. |
idpath required | string (uuid) | The row's ID, generated by the client (UUID v7). |
If-Matchheader | string pattern ^"[0-9]{1,18}"$ | The version the client last saw, as an entity tag: |
| Response | Type | Description |
|---|---|---|
204 | Deleted (a tombstone stays for sync). | |
412 | ContainerConflict | Changed since the |
429 | Problem | Rate limited; retry after the given number of seconds. |
error | Problem | Error (RFC 9457 problem details). |
Items
GET /v1/households/{hid}/items read
The household's items, optionally in one container or location.
Same list rules as locations. container_id narrows to the items in that container;
location_id to the items lying directly in that location, or, with subtree=true, to
everything under it: its child locations and the containers in any of them. At most one of
container_id and location_id. carried=true narrows to the items someone has with them
(not with updated_since: 400). Any member may read.
| Parameter | Type | Description |
|---|---|---|
hidpath required | string (uuid) | A household the caller belongs to; 404 otherwise. |
container_idquery | string (uuid) | |
location_idquery | string (uuid) | |
subtreequery | boolean default false | With location_id, include everything below it. |
carriedquery | boolean default false | Only what someone has with them ( |
updated_sincequery | string (date-time) | Only rows changed after this instant, tombstones (deleted_at set) included. Groundwork for sync. |
limitquery | integer 1–500; default 100 | |
cursorquery | string | Opaque value from a previous page's next_cursor. |
GET /v1/households/{hid}/items/{id} read
One item, with its photos.
For a client without the whole list at hand, e.g. the web app opening a thing's page by its link.
A deleted or let-go item comes back as its tombstone (deleted_at, and disposed_at when let go),
so the client can say so and restore it whole. 404 for an unknown item. Any member may read.
| Parameter | Type | Description |
|---|---|---|
hidpath required | string (uuid) | A household the caller belongs to; 404 otherwise. |
idpath required | string (uuid) | The row's ID, generated by the client (UUID v7). |
PUT /v1/households/{hid}/items/{id} write
Create or update an item under the client's ID (idempotent upsert).
Same upsert rules as locations. The item sits in exactly one place: a live container_id or a
live location_id of this household (422 otherwise). quantity and value are stored to two
decimal places. A change bumps last_touched_at. Needs editor or owner.
| Parameter | Type | Description |
|---|---|---|
hidpath required | string (uuid) | A household the caller belongs to; 404 otherwise. |
idpath required | string (uuid) | The row's ID, generated by the client (UUID v7). |
If-Matchheader | string pattern ^"[0-9]{1,18}"$ | The version the client last saw, as an entity tag: |
If-None-Matchheader | string: * |
|
restorequery | boolean default false | A deleted row is brought back with this body instead of answering 410: its ID, photos
and moves stay. The usual rules apply (a live parent, a free code). |
Body: ItemInput
| Response | Type | Description |
|---|---|---|
200 | Item | The item as stored. |
412 | ItemConflict | Changed since the |
429 | Problem | Rate limited; retry after the given number of seconds. |
error | Problem | Error (RFC 9457 problem details). |
DELETE /v1/households/{hid}/items/{id} write
Soft-delete an item.
A repeat is a 204 too. Needs editor or owner.
| Parameter | Type | Description |
|---|---|---|
hidpath required | string (uuid) | A household the caller belongs to; 404 otherwise. |
idpath required | string (uuid) | The row's ID, generated by the client (UUID v7). |
If-Matchheader | string pattern ^"[0-9]{1,18}"$ | The version the client last saw, as an entity tag: |
| Response | Type | Description |
|---|---|---|
204 | Deleted (a tombstone stays for sync). | |
412 | ItemConflict | Changed since the |
429 | Problem | Rate limited; retry after the given number of seconds. |
error | Problem | Error (RFC 9457 problem details). |
POST /v1/households/{hid}/items/{id}/dispose write
Let a thing go.
Deletes it with disposed_at and disposed_reason in one write. A thing let go stays in the
export. A repeat returns the stored row. 410 for a thing deleted without being let go. Undo is
PUT …/items/{id}?restore=true, which clears both fields. Needs editor or owner.
| Parameter | Type | Description |
|---|---|---|
hidpath required | string (uuid) | A household the caller belongs to; 404 otherwise. |
idpath required | string (uuid) | The row's ID, generated by the client (UUID v7). |
Body: DisposeRequest
DELETE /v1/households/{hid}/items/{id}/photos/{photo_id} write
Take a photo off an item.
The link goes; the photo itself stays, since a shelf shot may show other items too. The item's version moves, and if the photo was its cover the cover goes back to the newest. A photo the item doesn't have changes nothing, so a repeat answers the same. 404 for an unknown item, 410 for a deleted one. Needs editor or owner.
| Parameter | Type | Description |
|---|---|---|
hidpath required | string (uuid) | A household the caller belongs to; 404 otherwise. |
idpath required | string (uuid) | The row's ID, generated by the client (UUID v7). |
photo_idpath required | string (uuid) |
POST /v1/households/{hid}/items/{id}/touch write
Someone looked for this thing.
Opening a thing from search or Siri counts as using it: last_touched_at moves and so does
updated_at, so other phones pull it, but version stays. A repeat is harmless. 404 for an
unknown item, 410 for a deleted one. Needs editor or owner.
| Parameter | Type | Description |
|---|---|---|
hidpath required | string (uuid) | A household the caller belongs to; 404 otherwise. |
idpath required | string (uuid) | The row's ID, generated by the client (UUID v7). |
Codes
POST /v1/households/{hid}/codes write
Reserve fresh codes for printing labels before the boxes exist.
Returns count new 8-character tokens (Crockford base32, no I, L, O or U) that no container,
location or earlier reservation of this household has. Print one as the QR payload
neatkept://c/<code> (a container) or neatkept://l/<code> (a location), with the code
itself in human-readable form beside it. Until something is saved with it, resolve reports
the code as unassigned. Not idempotent: a retry reserves new codes, and unused ones cost
nothing. Needs editor or owner.
| Parameter | Type | Description |
|---|---|---|
hidpath required | string (uuid) | A household the caller belongs to; 404 otherwise. |
Body: CodeReservationRequest
| Response | Type | Description |
|---|---|---|
201 | CodeReservation | The reserved codes. |
429 | Problem | Rate limited; retry after the given number of seconds. |
error | Problem | Error (RFC 9457 problem details). |
GET /v1/households/{hid}/resolve/{code} read
What a label's code names, with its path and what's inside.
Codes are matched normalized: case, spaces and the separators -, _, . don't matter
("w 01" finds "W-01"), and a code of Cyrillic look-alikes ("ВОХ1") matches the Latin one.
A token typed by hand also matches with O read as 0 and I or L as 1. The code is looked up
among the household's live containers and locations; a code that only has a reservation is
unassigned. 404 if the household has no such code. Any member may read.
| Parameter | Type | Description |
|---|---|---|
hidpath required | string (uuid) | A household the caller belongs to; 404 otherwise. |
codepath required | string 1–64 characters | The code from a label: the part of a scanned |
| Response | Type | Description |
|---|---|---|
200 | Resolution | What the code names. |
429 | Problem | Rate limited; retry after the given number of seconds. |
error | Problem | Error (RFC 9457 problem details). |
Photos
GET /v1/households/{hid}/photos/{id} read
A photo, with short-lived URLs to read it.
thumb_url is there once the photo is ready; original_url once it is uploaded and until
the original expires (5 years). Both are presigned GET URLs that expire at urls_expire_at.
Any member may read. Reading grants no write: there is never an upload here, a pending
photo's upload URL comes only from PUT (editor or owner).
| Parameter | Type | Description |
|---|---|---|
hidpath required | string (uuid) | A household the caller belongs to; 404 otherwise. |
idpath required | string (uuid) | The row's ID, generated by the client (UUID v7). |
PUT /v1/households/{hid}/photos/{id} write
Create a photo under the client's ID and get a URL to upload the JPEG to.
Photos never go through the API. This creates the photo's row and returns
upload: a presigned PUT URL plus the headers to send with it. Storage accepts only a
JPEG of exactly size_bytes bytes there (and, with checksum_sha256, only that file). Send
every header in upload.headers (a browser sets Content-Length and Host itself). Once the
upload succeeds, call …/uploaded.
A repeat while the photo is pending returns a fresh URL (and may change size_bytes);
once uploaded, the photo is returned as is, without upload. Needs editor or owner.
| Parameter | Type | Description |
|---|---|---|
hidpath required | string (uuid) | A household the caller belongs to; 404 otherwise. |
idpath required | string (uuid) | The row's ID, generated by the client (UUID v7). |
Body: PhotoInput
POST /v1/households/{hid}/photos/{id}/uploaded write
Tell the API the JPEG is in storage, so the thumbnail gets made.
The API checks the file in storage (409 if it isn't there yet: ask again; 409 with type
https://neatkept.com/problems/upload-mismatch if it isn't the photo's size or checksum_sha256:
repeat the PUT for a fresh URL and upload it again), marks the photo uploaded
and queues the thumbnail; the photo turns ready, or failed if the file is not a JPEG it
can decode. A repeat returns the photo as it is. Needs editor or owner.
| Parameter | Type | Description |
|---|---|---|
hidpath required | string (uuid) | A household the caller belongs to; 404 otherwise. |
idpath required | string (uuid) | The row's ID, generated by the client (UUID v7). |
Barcode
GET /v1/barcode/{ean} read
What product a barcode is, from the shared cache or a product database.
The answer comes from a cache shared by all users, or from external product databases asked in
turn, and is cached. 400 if the code is not a valid EAN/UPC (wrong length or check
digit). 404 if no database knows it (remembered for a while). 503 if a database could not be
reached and none found it. On 404 or 503, photograph the packaging instead (ingest mode barcode).
A UPC-A comes back as its EAN-13 (a leading zero).
| Parameter | Type | Description |
|---|---|---|
eanpath required | string pattern ^[0-9]{8,14}$ | A scanned EAN-8, UPC-A, EAN-13 or GTIN-14, digits only, with its check digit. |
Search
POST /v1/households/{hid}/duplicates read
Do I have this already? The items a thing may be another copy of.
Items with the same barcode, then the nearest items by meaning that the text model
judges the same kind of thing ("cordless drill" and "drill", not "drill bits"), at most three,
live items only. Without the AI (off or down) only barcodes count. The same check runs on every
draft of a capture (DraftItem.duplicates). 400 when neither name nor barcode is given.
A UPC-A or zero-indicator GTIN-14 finds the items that keep it as its EAN-13, as item writes
store it. A barcode that is no valid EAN-8, UPC-A, EAN-13 or GTIN-14 (length, check
digit) is ignored: with a name only the name counts, alone it finds nothing (an empty list,
not an error). Any member may ask. Without the asker's consent only barcodes count.
| Parameter | Type | Description |
|---|---|---|
hidpath required | string (uuid) | A household the caller belongs to; 404 otherwise. |
Body: DuplicatesRequest
| Response | Type | Description |
|---|---|---|
200 | DuplicatesResult | The items, barcode matches first; empty when there are none. |
429 | Problem | Rate limited; retry after the given number of seconds. |
error | Problem | Error (RFC 9457 problem details). |
POST /v1/households/{hid}/search read
Find things across every property of the household.
Hybrid search: full text over name, tags and description (each word, as a prefix), trigram similarity on the name (typos), and embedding similarity (meaning, across languages: "дриль" finds "cordless drill"), fused with Reciprocal Rank Fusion. Live items only. If the embedding can't be had in time, the answer comes from the text matches alone. Each hit carries where the thing is, property first, and a thumbnail. Any member may read. Without the user's consent no model is asked: words, typos and categories only, and ai is false.
| Parameter | Type | Description |
|---|---|---|
hidpath required | string (uuid) | A household the caller belongs to; 404 otherwise. |
Body: SearchRequest
| Response | Type | Description |
|---|---|---|
200 | SearchResult | Hits, best first; empty when nothing matches. |
429 | Problem | Rate limited; retry after the given number of seconds. |
error | Problem | Error (RFC 9457 problem details). |
Moves
GET /v1/households/{hid}/moves read
The household's move history, newest first.
item_id narrows to the moves of that item, container_id to those of that box; at most one of
the two. A box's moves carry its items, so an item's own list holds only the moves made of it. Moves of deleted things stay. Any member may read.
| Parameter | Type | Description |
|---|---|---|
hidpath required | string (uuid) | A household the caller belongs to; 404 otherwise. |
item_idquery | string (uuid) | |
container_idquery | string (uuid) | |
limitquery | integer 1–500; default 100 | |
cursorquery | string | Opaque value from a previous page's next_cursor. |
PUT /v1/households/{hid}/moves/{id} write
Move an item or a box, under the client's move ID.
Puts the subject in its new place and logs the move, in one transaction. An item goes to
a live location or a live box; a box goes to a live location (422 on to_container_id) and takes
everything in it along. Any property of the household will do. The subject must be live (422 on
item_id / container_id), and not already there (422 on the target). An item's last_touched_at
is bumped. A repeat with the same body returns the move; the same ID with another body is 409.
Needs editor or owner.
| Parameter | Type | Description |
|---|---|---|
hidpath required | string (uuid) | A household the caller belongs to; 404 otherwise. |
idpath required | string (uuid) | The row's ID, generated by the client (UUID v7). |
If-Matchheader | string pattern ^"[0-9]{1,18}"$ | The version the client last saw, as an entity tag: |
Body: MoveInput
| Response | Type | Description |
|---|---|---|
200 | Move | The move as logged. |
412 | MoveConflict | Changed since the |
429 | Problem | Rate limited; retry after the given number of seconds. |
error | Problem | Error (RFC 9457 problem details). |
Declutter
GET /v1/households/{hid}/declutter-rules read
The household's declutter rules.
Same list rules as locations. Any member may read.
| Parameter | Type | Description |
|---|---|---|
hidpath required | string (uuid) | A household the caller belongs to; 404 otherwise. |
updated_sincequery | string (date-time) | Only rows changed after this instant, tombstones (deleted_at set) included. Groundwork for sync. |
limitquery | integer 1–500; default 100 | |
cursorquery | string | Opaque value from a previous page's next_cursor. |
| Response | Type | Description |
|---|---|---|
200 | DeclutterRuleList | A page of rules. |
429 | Problem | Rate limited; retry after the given number of seconds. |
error | Problem | Error (RFC 9457 problem details). |
PUT /v1/households/{hid}/declutter-rules/{id} write
Create or update a rule under the client's ID (idempotent upsert).
The whole rule. At most one of scope_location_id (that place and everything below it) and
scope_container_id, a live one of this household (422 otherwise); none means all places.
410 for a deleted rule. Needs editor or owner. Free on every plan.
| Parameter | Type | Description |
|---|---|---|
hidpath required | string (uuid) | A household the caller belongs to; 404 otherwise. |
idpath required | string (uuid) | The row's ID, generated by the client (UUID v7). |
If-Matchheader | string pattern ^"[0-9]{1,18}"$ | The version the client last saw, as an entity tag: |
If-None-Matchheader | string: * |
|
Body: DeclutterRuleInput
| Response | Type | Description |
|---|---|---|
200 | DeclutterRule | The rule as stored. |
412 | DeclutterRuleConflict | Changed since the |
429 | Problem | Rate limited; retry after the given number of seconds. |
error | Problem | Error (RFC 9457 problem details). |
DELETE /v1/households/{hid}/declutter-rules/{id} write
Soft-delete a rule.
A repeat is a 204 too. Needs editor or owner.
| Parameter | Type | Description |
|---|---|---|
hidpath required | string (uuid) | A household the caller belongs to; 404 otherwise. |
idpath required | string (uuid) | The row's ID, generated by the client (UUID v7). |
If-Matchheader | string pattern ^"[0-9]{1,18}"$ | The version the client last saw, as an entity tag: |
| Response | Type | Description |
|---|---|---|
204 | Deleted (a tombstone stays for sync). | |
412 | DeclutterRuleConflict | Changed since the |
429 | Problem | Rate limited; retry after the given number of seconds. |
error | Problem | Error (RFC 9457 problem details). |
Schemas
BoundingBox
Where the thing is in the photo, as fractions of its width and height.
| Field | Type | Description |
|---|---|---|
h required | number (double) | |
w required | number (double) | |
x required | number (double) | |
y required | number (double) |
CodeInput
The label's code: a reserved token or what's written on the label by hand (W01). At most 32
letters or digits once normalized (see resolve); stored normalized. Absent: a location
keeps its code (or has none), a container keeps its code (or gets a generated one).
string
CodeReservation
| Field | Type | Description |
|---|---|---|
codes required | array of string |
CodeReservationRequest
| Field | Type | Description |
|---|---|---|
count required | integer 1–200 | How many codes to reserve. |
Container
| Field | Type | Description |
|---|---|---|
code required | string | The label's code, normalized. |
created_at required | string (date-time) | |
id required | string (uuid) | |
location_id required | string (uuid) | |
updated_at required | string (date-time) | |
version required | integer (int64) | |
carried_at | string (date-time) | When they took it along. |
carried_by | string (uuid) | Who has it with them; absent while it's in its place. |
deleted_at | string (date-time) | Set on tombstones only. |
label | string | |
photo_id | string (uuid) | The box's latest photo: the newest capture confirmed in |
ContainerConflict
A 412 problem with the container as it is now.
| Field | Type | Description |
|---|---|---|
current required | Container | |
status required | integer | |
title required | string | |
type required | string | |
detail | string | |
request_id | string |
ContainerInput
| Field | Type | Description |
|---|---|---|
location_id required | string (uuid) | |
code | CodeInput | |
label | string at most 200 characters | What's written on the box, e.g. "Winter clothes". |
ContainerList
Contents
What's directly inside a container or a location (absent for unassigned). Each list holds at
most 200 rows; truncated says one was cut, and the list endpoints page through the rest.
DeclutterCondition
unopened_for: unopened, counted from purchased_at, else from when it was added.
untouched_for: since last_touched_at. owned_for: since purchased_at (none: never).
A new value is a breaking change in responses.
string: unopened_for, untouched_for, owned_for
DeclutterRule
| Field | Type | Description |
|---|---|---|
categories required | array of string | |
condition required | DeclutterCondition | |
created_at required | string (date-time) | |
enabled required | boolean | |
except_categories required | array of string | |
id required | string (uuid) | |
months required | integer (int32) | |
tags required | array of string | |
updated_at required | string (date-time) | |
version required | integer (int64) | |
deleted_at | string (date-time) | Set on tombstones only. |
scope_container_id | string (uuid) | |
scope_location_id | string (uuid) |
DeclutterRuleConflict
A 412 problem with the rule as it is now.
| Field | Type | Description |
|---|---|---|
current required | DeclutterRule | |
status required | integer | |
title required | string | |
type required | string | |
detail | string | |
request_id | string |
DeclutterRuleInput
| Field | Type | Description |
|---|---|---|
condition required | DeclutterCondition | |
months required | integer (int32) 1–600 | |
categories | array of string at most 50 items | Only these categories; empty is any. |
enabled | boolean default true | |
except_categories | array of string at most 50 items | Never these categories. |
scope_container_id | string (uuid) | |
scope_location_id | string (uuid) | |
tags | array of string at most 50 items | Any of these tags; empty is any. |
DeclutterRuleList
| Field | Type | Description |
|---|---|---|
items required | array of DeclutterRule | |
as_of | PullAsOf | |
next_cursor | string | Pass as ?cursor= for the next page. Absent on the last page. |
DisposeReason
Why a thing was let go. A new value is a breaking change in responses.
string: sold, given, thrown, other
DisposeRequest
| Field | Type | Description |
|---|---|---|
reason required | DisposeReason |
Duplicate
An item the thing may be another copy of, with where it is (as in SearchHit).
| Field | Type | Description |
|---|---|---|
item required | Item | |
path required | array of Location | Where it is, property first, down to the location the item (or its box) is in. |
reason required | string: barcode, similar |
|
container | Container | |
thumb | SearchThumb |
DuplicatesRequest
| Field | Type | Description |
|---|---|---|
barcode | string pattern ^[0-9]{8,14}$ | Its EAN/UPC, if scanned. |
name | string at most 500 characters | What the thing is called, as typed or scanned. |
DuplicatesResult
| Field | Type | Description |
|---|---|---|
duplicates required | array of Duplicate |
FXRates
| Field | Type | Description |
|---|---|---|
base required | string: EUR | |
date required | string or null pattern ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ | The ECB day the rates are of (YYYY-MM-DD). |
rates required | map of number (double) | Units of each currency per euro, keyed by ISO 4217 code. |
FieldError
| Field | Type | Description |
|---|---|---|
field required | string | |
message required | string |
Geo
Where a property is, so the phone can tell which one the user is at. Only a
property has it. In a PUT, absent keeps the current coordinates (an older app never wipes
them); forget_geo: true removes them. Any other kind has none: geo sent with it is
ignored, and a property turned into another kind loses its coordinates.
The phone's own location is never sent.
| Field | Type | Description |
|---|---|---|
latitude required | number (double) -90–90 | |
longitude required | number (double) -180–180 |
Household
| Field | Type | Description |
|---|---|---|
created_at required | string (date-time) | |
id required | string (uuid) | |
name required | string | |
plan required | string: free, pro, family | |
role required | string: owner, editor, viewer | The current user's role in this household. |
updated_at required | string (date-time) | |
version required | integer (int64) | |
ai_language | string or null | The language the AI names things in; null = each member's own. |
currencies | array of string | ISO 4217 codes the household's values are in; the first is the default. Always sent; optional so apps keep reading servers from before it. |
HouseholdList
| Field | Type | Description |
|---|---|---|
items required | array of Household |
HouseholdPlanStatus
| Field | Type | Description | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
ai_scans required | object |
| ||||||||||||
can_purchase required | boolean | The caller is an owner. | ||||||||||||
members required | object |
| ||||||||||||
offers required | array of PlanOffer | Every plan's limits, free first, so the app never hard-codes them. | ||||||||||||
plan required | string: free, pro, family | |||||||||||||
source required | string: free, subscription, comp | |||||||||||||
subscription | HouseholdSubscription |
HouseholdSubscription
Only when source is subscription.
| Field | Type | Description |
|---|---|---|
expires_at required | string (date-time) | |
product_id required | string | |
status required | string: active, grace, billing_retry | |
will_renew required | boolean | |
purchased_by | string (uuid) | |
store | string: app_store, google_play | Where it was bought. An app offers Manage subscription instead of its own store's products when the other store holds it. Omitted by older servers: App Store. |
Item
| Field | Type | Description |
|---|---|---|
created_at required | string (date-time) | |
id required | string (uuid) | |
name required | string | |
packaging required | Packaging | |
photos required | array of ItemPhoto | The photos the item was captured in or photographed alone ( |
quantity required | number (double) | |
tags required | array of string | |
unopened required | boolean | |
updated_at required | string (date-time) | |
version required | integer (int64) | |
barcode | string | The product's EAN/UPC, normalized. |
carried_at | string (date-time) | When they took it along. |
carried_by | string (uuid) | Who has it with them; absent while it's in its place. |
category | string | |
container_id | string (uuid) | |
cover_photo_id | string (uuid) | The chosen cover, as set; |
declutter_keep | boolean default false | |
declutter_snoozed_until | string (date-time) | |
deleted_at | string (date-time) | Set on tombstones only. |
description | string | |
disposed_at | string (date-time) | Let go; set on tombstones only. |
disposed_reason | DisposeReason | |
last_touched_at | string (date-time) | |
location_id | string (uuid) | |
purchased_at | string | The day it was bought, |
value | number (double) | |
value_currency | string |
ItemConflict
A 412 problem with the item as it is now.
| Field | Type | Description |
|---|---|---|
current required | Item | |
status required | integer | |
title required | string | |
type required | string | |
detail | string | |
request_id | string |
ItemInput
Exactly one of container_id and location_id.
| Field | Type | Description |
|---|---|---|
name required | string 1–500 characters | |
barcode | string pattern ^([0-9]{8,14})?$ | The product's EAN/UPC, once looked up with |
category | string at most 100 characters | |
container_id | string (uuid) | |
cover_photo_id | string (uuid) | The photo lists and search show for this item. Absent keeps the current one. Any ID is taken, even of a photo still on its way: it shows once the item has that photo, and until then the newest does. |
declutter_keep | boolean | Keep forever; the declutter list never shows it. Absent keeps the item's. |
declutter_snoozed_until | string (date-time) | Keep for now; the declutter list skips it until then. A time in the past un-snoozes; absent or null keeps the item's. |
description | string at most 2000 characters | |
location_id | string (uuid) | |
packaging | Packaging | |
purchased_at | string pattern ^([0-9]{4}-[0-9]{2}-[0-9]{2})?$ | The day it was bought ( |
quantity | number (double) 0.01–99999999; default 1 | |
tags | array of string at most 50 items | |
unopened | boolean default false | |
value | number (double) 0–9999999999 | Optional, entered by hand; needs value_currency. |
value_currency | string pattern ^[A-Z]{3}$ | ISO 4217 code. |
ItemList
ItemPhoto
| Field | Type | Description |
|---|---|---|
photo_id required | string (uuid) | URLs come from |
bbox | BoundingBox |
Location
| Field | Type | Description |
|---|---|---|
created_at required | string (date-time) | |
id required | string (uuid) | |
kind required | LocationKind | |
name required | string | |
updated_at required | string (date-time) | |
version required | integer (int64) | |
code | string | The label's code, normalized. Absent if it has none. |
deleted_at | string (date-time) | Set on tombstones only. |
geo | Geo | |
parent_id | string (uuid) | Absent for a property. |
LocationConflict
A 412 problem with the location as it is now.
| Field | Type | Description |
|---|---|---|
current required | Location | |
status required | integer | |
title required | string | |
type required | string | |
detail | string | |
request_id | string |
LocationInput
| Field | Type | Description |
|---|---|---|
kind required | LocationKind | |
name required | string 1–200 characters | |
code | CodeInput | |
forget_geo | boolean | True removes the property's coordinates; |
geo | Geo | |
parent_id | string (uuid) | Absent for a property, required for every other kind. |
LocationKind
Tree level, from the root down. A child is always of a deeper kind than its parent.
string: property, room, storage, zone, shelf
LocationList
Move
Exactly one of item_id and container_id, one of from_location_id and from_container_id, and one of to_location_id and to_container_id unless taken.
| Field | Type | Description |
|---|---|---|
id required | string (uuid) | |
moved_at required | string (date-time) | |
moved_by required | string (uuid) | The user who moved it. |
taken required | boolean | Taken along: no to_* then. |
container_id | string (uuid) | |
from_container_id | string (uuid) | |
from_location_id | string (uuid) | |
item_id | string (uuid) | |
to_container_id | string (uuid) | |
to_location_id | string (uuid) |
MoveConflict
A 412 problem with the moved item or box as it is now (one of the two).
MoveInput
Exactly one of item_id and container_id (what moves), and exactly one of with_me, to_location_id and
to_container_id (where to). with_me: true takes it along; a move to its own place puts a taken
thing back; any move to a place clears who carries it.
| Field | Type | Description |
|---|---|---|
container_id | string (uuid) | |
item_id | string (uuid) | |
to_container_id | string (uuid) | |
to_location_id | string (uuid) | |
with_me | boolean | Take it along: it's carried by the caller until put somewhere. |
MoveList
| Field | Type | Description |
|---|---|---|
items required | array of Move | |
next_cursor | string | Pass as ?cursor= for the next page. Absent on the last page. |
Packaging
original: still in its retail packaging.
string: none, original
Photo
| Field | Type | Description |
|---|---|---|
created_at required | string (date-time) | |
id required | string (uuid) | |
original_expires_at required | string (date-time) | After this only the thumbnail is kept. |
status required | PhotoStatus | |
height | integer | Pixels, known once |
original_url | string (uri) | |
size_bytes | integer (int64) | |
taken_at | string (date-time) | |
thumb_url | string (uri) | |
upload | PhotoUpload | |
urls_expire_at | string (date-time) | When |
width | integer | Pixels, known once |
PhotoInput
| Field | Type | Description |
|---|---|---|
size_bytes required | integer (int64) 1–20971520 | The JPEG's exact size (at most 20 MiB). The phone downscales to a 2048 px long edge first. |
checksum_sha256 | string pattern ^[A-Za-z0-9+/]{43}=$ | The JPEG's SHA-256, base64. Binds the photo to this file, not just its size:
|
item_id | string (uuid) | A plain photo of this item: linked to it on create, no ingest; the item's version moves. 409 while the item isn't there (made offline, not synced yet: retry), 410 once it's deleted. Nothing is created then. |
taken_at | string (date-time) | When the picture was taken, if known. |
PhotoStatus
pending: waiting for the upload. uploaded: in storage, thumbnail on its way.
ready: thumbnail made. failed: the file is not a JPEG we can decode.
string: pending, uploaded, ready, failed
PhotoUpload
Send the JPEG with PUT url, the given headers and nothing else.
| Field | Type | Description |
|---|---|---|
expires_at required | string (date-time) | |
headers required | map of string | Headers the signature covers, e.g. |
url required | string (uri) |
PlanOffer
| Field | Type | Description |
|---|---|---|
members_allowed required | integer | |
plan required | string: free, pro, family | |
ai_scans_allowed | integer | AI scans a month; omitted when unlimited. |
Problem
RFC 9457 problem details. detail is safe to show; internals are only logged.
| Field | Type | Description |
|---|---|---|
status required | integer | |
title required | string | |
type required | string |
|
allowed | integer | With type plan-limit — what the plan allows. |
detail | string | |
errors | array of FieldError | Field-level validation errors. |
limit | string: ai_scans, members | With type plan-limit (HTTP 402) — which limit was hit. |
plan | string: free, pro, family | With type plan-limit — the household's plan. |
request_id | string | Same as the X-Request-ID response header. |
resets_at | string (date-time) | With limit ai_scans — when the month's scans come back. |
used | integer | With type plan-limit — used so far (this month for ai_scans). |
Product
| Field | Type | Description |
|---|---|---|
barcode required | string | The normalized code (UPC-A as EAN-13). |
fetched_at required | string (date-time) | |
name required | string | The product's name, in whatever language the database has it. |
source required | string | Which database answered, e.g. |
brand | string | |
category | string | The database's most specific category, e.g. |
model | string |
PullAsOf
The database's time just before this page was read. A client paging through a list (a first sync or updated_since changes) keeps the first page's as_of and moves its watermark to no later than that, so a write that commits while it pages is in the next pull however long the paging took. Absent from older servers.
string (date-time)
Resolution
| Field | Type | Description |
|---|---|---|
code required | string | The code as stored. |
kind required | ResolutionKind | |
path required | array of Location | The locations above what the code names, property first: for a container, down to the
location it's in; for a location, down to its parent. Empty for a property and for
|
container | Container | |
contents | Contents | |
location | Location |
ResolutionKind
unassigned: a reserved code nothing is saved with yet.
string: container, location, unassigned
SearchHit
| Field | Type | Description |
|---|---|---|
item required | Item | |
path required | array of Location | Where it is, property first, down to the location the item (or its box) is in. |
score required | number (double) | The fused RRF score; only the order means anything. |
container | Container | |
thumb | SearchThumb |
SearchRequest
| Field | Type | Description |
|---|---|---|
query required | string 1–200 characters | What the user typed or said. |
limit | integer 1–50; default 20 |
SearchResult
| Field | Type | Description |
|---|---|---|
ai required | boolean | Whether the AI took part: the query's embedding or its rewrite. False when the user hasn't allowed the AI, and when the AI failed and the hits come from the words alone. |
hits required | array of SearchHit |
SearchThumb
A thumbnail to show: the item's cover (its first photo), or else its box's. When photo_id is one of the
item's photos, crop to that photo's bbox if it has one.
| Field | Type | Description |
|---|---|---|
expires_at required | string (date-time) | |
photo_id required | string (uuid) | |
url required | string (uri) |