Neatkept API reference

Generated from the OpenAPI spec. New here? Start with the guide.

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.

ResponseTypeDescription
200FXRates

The rates.

429Problem

Rate limited; retry after the given number of seconds.

errorProblem

Error (RFC 9457 problem details).

GET /v1/households read

Households the current user belongs to, with their role.

Requires Authorization: Bearer <access token>; 401 otherwise.

ResponseTypeDescription
200HouseholdList

The user's households.

429Problem

Rate limited; retry after the given number of seconds.

errorProblem

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).

ParameterTypeDescription
hid
path required
string (uuid)

A household the caller belongs to; 404 otherwise.

ResponseTypeDescription
200HouseholdPlanStatus

Plan status.

429Problem

Rate limited; retry after the given number of seconds.

errorProblem

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.

ParameterTypeDescription
hid
path required
string (uuid)

A household the caller belongs to; 404 otherwise.

updated_since
query
string (date-time)

Only rows changed after this instant, tombstones (deleted_at set) included. Groundwork for sync.

limit
query
integer
1–500; default 100
cursor
query
string

Opaque value from a previous page's next_cursor.

ResponseTypeDescription
200LocationList

A page of locations.

429Problem

Rate limited; retry after the given number of seconds.

errorProblem

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.

ParameterTypeDescription
hid
path required
string (uuid)

A household the caller belongs to; 404 otherwise.

id
path required
string (uuid)

The row's ID, generated by the client (UUID v7).

ResponseTypeDescription
200Location

The location.

429Problem

Rate limited; retry after the given number of seconds.

errorProblem

Error (RFC 9457 problem details).

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.

ParameterTypeDescription
hid
path required
string (uuid)

A household the caller belongs to; 404 otherwise.

id
path required
string (uuid)

The row's ID, generated by the client (UUID v7).

If-Match
header
string
pattern ^"[0-9]{1,18}"$

The version the client last saw, as an entity tag: "7". A different stored version answers 412 with the current row; a deleted row still answers 410 first. Ignored on a create. Not together with If-None-Match (400). Absent: no check.

If-None-Match
header
string: *

* only: create the row, never replace one. A live row the body would change answers 412 with the current row, so a create resent after a lost reply can't overwrite a change made meanwhile; a live row the body matches answers 200 as a repeat, writing nothing. A deleted row still answers 410 first (where the endpoint takes restore=true, which brings it back; declutter rules don't). Not together with If-Match (400). Absent: the usual upsert.

restore
query
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). If-Match, when sent, is checked against the deleted row's version: deleting bumps it, so a client sends the version it got back from the delete, not the one before. On a live or new row: no effect.

Body: LocationInput

ResponseTypeDescription
200Location

The location as stored.

412LocationConflict

Changed since the If-Match version, or the row exists, other than the body, under If-None-Match; current is the row as it is now.

429Problem

Rate limited; retry after the given number of seconds.

errorProblem

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.

ParameterTypeDescription
hid
path required
string (uuid)

A household the caller belongs to; 404 otherwise.

id
path required
string (uuid)

The row's ID, generated by the client (UUID v7).

If-Match
header
string
pattern ^"[0-9]{1,18}"$

The version the client last saw, as an entity tag: "7". A different stored version answers 412 with the current row; a deleted row still answers 410 first. Ignored on a create. Not together with If-None-Match (400). Absent: no check.

ResponseTypeDescription
204

Deleted (a tombstone stays for sync).

412LocationConflict

Changed since the If-Match version; current is the row as it is now.

429Problem

Rate limited; retry after the given number of seconds.

errorProblem

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.

ParameterTypeDescription
hid
path required
string (uuid)

A household the caller belongs to; 404 otherwise.

location_id
query
string (uuid)
carried
query
boolean
default false

Only what someone has with them (carried_by set), e.g. for a "with me" list. Not with updated_since (400 on carried): a row put down drops out of the filter, so a sync of changes would never see it.

updated_since
query
string (date-time)

Only rows changed after this instant, tombstones (deleted_at set) included. Groundwork for sync.

limit
query
integer
1–500; default 100
cursor
query
string

Opaque value from a previous page's next_cursor.

ResponseTypeDescription
200ContainerList

A page of containers.

429Problem

Rate limited; retry after the given number of seconds.

errorProblem

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.

ParameterTypeDescription
hid
path required
string (uuid)

A household the caller belongs to; 404 otherwise.

id
path required
string (uuid)

The row's ID, generated by the client (UUID v7).

ResponseTypeDescription
200Container

The container.

429Problem

Rate limited; retry after the given number of seconds.

errorProblem

Error (RFC 9457 problem details).

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.

ParameterTypeDescription
hid
path required
string (uuid)

A household the caller belongs to; 404 otherwise.

id
path required
string (uuid)

The row's ID, generated by the client (UUID v7).

If-Match
header
string
pattern ^"[0-9]{1,18}"$

The version the client last saw, as an entity tag: "7". A different stored version answers 412 with the current row; a deleted row still answers 410 first. Ignored on a create. Not together with If-None-Match (400). Absent: no check.

If-None-Match
header
string: *

* only: create the row, never replace one. A live row the body would change answers 412 with the current row, so a create resent after a lost reply can't overwrite a change made meanwhile; a live row the body matches answers 200 as a repeat, writing nothing. A deleted row still answers 410 first (where the endpoint takes restore=true, which brings it back; declutter rules don't). Not together with If-Match (400). Absent: the usual upsert.

restore
query
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). If-Match, when sent, is checked against the deleted row's version: deleting bumps it, so a client sends the version it got back from the delete, not the one before. On a live or new row: no effect.

Body: ContainerInput

ResponseTypeDescription
200Container

The container as stored.

412ContainerConflict

Changed since the If-Match version, or the row exists, other than the body, under If-None-Match; current is the row as it is now.

429Problem

Rate limited; retry after the given number of seconds.

errorProblem

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.

ParameterTypeDescription
hid
path required
string (uuid)

A household the caller belongs to; 404 otherwise.

id
path required
string (uuid)

The row's ID, generated by the client (UUID v7).

If-Match
header
string
pattern ^"[0-9]{1,18}"$

The version the client last saw, as an entity tag: "7". A different stored version answers 412 with the current row; a deleted row still answers 410 first. Ignored on a create. Not together with If-None-Match (400). Absent: no check.

ResponseTypeDescription
204

Deleted (a tombstone stays for sync).

412ContainerConflict

Changed since the If-Match version; current is the row as it is now.

429Problem

Rate limited; retry after the given number of seconds.

errorProblem

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.

ParameterTypeDescription
hid
path required
string (uuid)

A household the caller belongs to; 404 otherwise.

container_id
query
string (uuid)
location_id
query
string (uuid)
subtree
query
boolean
default false

With location_id, include everything below it.

carried
query
boolean
default false

Only what someone has with them (carried_by set), e.g. for a "with me" list. Not with updated_since (400 on carried): a row put down drops out of the filter, so a sync of changes would never see it.

updated_since
query
string (date-time)

Only rows changed after this instant, tombstones (deleted_at set) included. Groundwork for sync.

limit
query
integer
1–500; default 100
cursor
query
string

Opaque value from a previous page's next_cursor.

ResponseTypeDescription
200ItemList

A page of items.

429Problem

Rate limited; retry after the given number of seconds.

errorProblem

Error (RFC 9457 problem details).

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.

ParameterTypeDescription
hid
path required
string (uuid)

A household the caller belongs to; 404 otherwise.

id
path required
string (uuid)

The row's ID, generated by the client (UUID v7).

ResponseTypeDescription
200Item

The item.

429Problem

Rate limited; retry after the given number of seconds.

errorProblem

Error (RFC 9457 problem details).

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.

ParameterTypeDescription
hid
path required
string (uuid)

A household the caller belongs to; 404 otherwise.

id
path required
string (uuid)

The row's ID, generated by the client (UUID v7).

If-Match
header
string
pattern ^"[0-9]{1,18}"$

The version the client last saw, as an entity tag: "7". A different stored version answers 412 with the current row; a deleted row still answers 410 first. Ignored on a create. Not together with If-None-Match (400). Absent: no check.

If-None-Match
header
string: *

* only: create the row, never replace one. A live row the body would change answers 412 with the current row, so a create resent after a lost reply can't overwrite a change made meanwhile; a live row the body matches answers 200 as a repeat, writing nothing. A deleted row still answers 410 first (where the endpoint takes restore=true, which brings it back; declutter rules don't). Not together with If-Match (400). Absent: the usual upsert.

restore
query
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). If-Match, when sent, is checked against the deleted row's version: deleting bumps it, so a client sends the version it got back from the delete, not the one before. On a live or new row: no effect.

Body: ItemInput

ResponseTypeDescription
200Item

The item as stored.

412ItemConflict

Changed since the If-Match version, or the row exists, other than the body, under If-None-Match; current is the row as it is now.

429Problem

Rate limited; retry after the given number of seconds.

errorProblem

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.

ParameterTypeDescription
hid
path required
string (uuid)

A household the caller belongs to; 404 otherwise.

id
path required
string (uuid)

The row's ID, generated by the client (UUID v7).

If-Match
header
string
pattern ^"[0-9]{1,18}"$

The version the client last saw, as an entity tag: "7". A different stored version answers 412 with the current row; a deleted row still answers 410 first. Ignored on a create. Not together with If-None-Match (400). Absent: no check.

ResponseTypeDescription
204

Deleted (a tombstone stays for sync).

412ItemConflict

Changed since the If-Match version; current is the row as it is now.

429Problem

Rate limited; retry after the given number of seconds.

errorProblem

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.

ParameterTypeDescription
hid
path required
string (uuid)

A household the caller belongs to; 404 otherwise.

id
path required
string (uuid)

The row's ID, generated by the client (UUID v7).

Body: DisposeRequest

ResponseTypeDescription
200Item

The thing, let go.

429Problem

Rate limited; retry after the given number of seconds.

errorProblem

Error (RFC 9457 problem details).

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.

ParameterTypeDescription
hid
path required
string (uuid)

A household the caller belongs to; 404 otherwise.

id
path required
string (uuid)

The row's ID, generated by the client (UUID v7).

photo_id
path required
string (uuid)
ResponseTypeDescription
200Item

The item as stored now.

429Problem

Rate limited; retry after the given number of seconds.

errorProblem

Error (RFC 9457 problem details).

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.

ParameterTypeDescription
hid
path required
string (uuid)

A household the caller belongs to; 404 otherwise.

id
path required
string (uuid)

The row's ID, generated by the client (UUID v7).

ResponseTypeDescription
204

Touched.

429Problem

Rate limited; retry after the given number of seconds.

errorProblem

Error (RFC 9457 problem details).

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.

ParameterTypeDescription
hid
path required
string (uuid)

A household the caller belongs to; 404 otherwise.

Body: CodeReservationRequest

ResponseTypeDescription
201CodeReservation

The reserved codes.

429Problem

Rate limited; retry after the given number of seconds.

errorProblem

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.

ParameterTypeDescription
hid
path required
string (uuid)

A household the caller belongs to; 404 otherwise.

code
path required
string
1–64 characters

The code from a label: the part of a scanned neatkept://c/<code> or neatkept://l/<code> after the last slash, or a code typed or read off a handwritten label.

ResponseTypeDescription
200Resolution

What the code names.

429Problem

Rate limited; retry after the given number of seconds.

errorProblem

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).

ParameterTypeDescription
hid
path required
string (uuid)

A household the caller belongs to; 404 otherwise.

id
path required
string (uuid)

The row's ID, generated by the client (UUID v7).

ResponseTypeDescription
200Photo

The photo.

429Problem

Rate limited; retry after the given number of seconds.

errorProblem

Error (RFC 9457 problem details).

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.

ParameterTypeDescription
hid
path required
string (uuid)

A household the caller belongs to; 404 otherwise.

id
path required
string (uuid)

The row's ID, generated by the client (UUID v7).

Body: PhotoInput

ResponseTypeDescription
200Photo

The photo as stored, with upload while it is pending.

429Problem

Rate limited; retry after the given number of seconds.

errorProblem

Error (RFC 9457 problem details).

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.

ParameterTypeDescription
hid
path required
string (uuid)

A household the caller belongs to; 404 otherwise.

id
path required
string (uuid)

The row's ID, generated by the client (UUID v7).

ResponseTypeDescription
200Photo

The photo.

429Problem

Rate limited; retry after the given number of seconds.

errorProblem

Error (RFC 9457 problem details).

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).

ParameterTypeDescription
ean
path required
string
pattern ^[0-9]{8,14}$

A scanned EAN-8, UPC-A, EAN-13 or GTIN-14, digits only, with its check digit.

ResponseTypeDescription
200Product

The product.

429Problem

Rate limited; retry after the given number of seconds.

errorProblem

Error (RFC 9457 problem details).

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.

ParameterTypeDescription
hid
path required
string (uuid)

A household the caller belongs to; 404 otherwise.

Body: DuplicatesRequest

ResponseTypeDescription
200DuplicatesResult

The items, barcode matches first; empty when there are none.

429Problem

Rate limited; retry after the given number of seconds.

errorProblem

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.

ParameterTypeDescription
hid
path required
string (uuid)

A household the caller belongs to; 404 otherwise.

Body: SearchRequest

ResponseTypeDescription
200SearchResult

Hits, best first; empty when nothing matches.

429Problem

Rate limited; retry after the given number of seconds.

errorProblem

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.

ParameterTypeDescription
hid
path required
string (uuid)

A household the caller belongs to; 404 otherwise.

item_id
query
string (uuid)
container_id
query
string (uuid)
limit
query
integer
1–500; default 100
cursor
query
string

Opaque value from a previous page's next_cursor.

ResponseTypeDescription
200MoveList

A page of moves.

429Problem

Rate limited; retry after the given number of seconds.

errorProblem

Error (RFC 9457 problem details).

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.

ParameterTypeDescription
hid
path required
string (uuid)

A household the caller belongs to; 404 otherwise.

id
path required
string (uuid)

The row's ID, generated by the client (UUID v7).

If-Match
header
string
pattern ^"[0-9]{1,18}"$

The version the client last saw, as an entity tag: "7". A different stored version answers 412 with the current row; a deleted row still answers 410 first. Ignored on a create. Not together with If-None-Match (400). Absent: no check.

Body: MoveInput

ResponseTypeDescription
200Move

The move as logged.

412MoveConflict

Changed since the If-Match version; current is the row as it is now.

429Problem

Rate limited; retry after the given number of seconds.

errorProblem

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.

ParameterTypeDescription
hid
path required
string (uuid)

A household the caller belongs to; 404 otherwise.

updated_since
query
string (date-time)

Only rows changed after this instant, tombstones (deleted_at set) included. Groundwork for sync.

limit
query
integer
1–500; default 100
cursor
query
string

Opaque value from a previous page's next_cursor.

ResponseTypeDescription
200DeclutterRuleList

A page of rules.

429Problem

Rate limited; retry after the given number of seconds.

errorProblem

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.

ParameterTypeDescription
hid
path required
string (uuid)

A household the caller belongs to; 404 otherwise.

id
path required
string (uuid)

The row's ID, generated by the client (UUID v7).

If-Match
header
string
pattern ^"[0-9]{1,18}"$

The version the client last saw, as an entity tag: "7". A different stored version answers 412 with the current row; a deleted row still answers 410 first. Ignored on a create. Not together with If-None-Match (400). Absent: no check.

If-None-Match
header
string: *

* only: create the row, never replace one. A live row the body would change answers 412 with the current row, so a create resent after a lost reply can't overwrite a change made meanwhile; a live row the body matches answers 200 as a repeat, writing nothing. A deleted row still answers 410 first (where the endpoint takes restore=true, which brings it back; declutter rules don't). Not together with If-Match (400). Absent: the usual upsert.

Body: DeclutterRuleInput

ResponseTypeDescription
200DeclutterRule

The rule as stored.

412DeclutterRuleConflict

Changed since the If-Match version, or the rule exists, other than the body, under If-None-Match; current is the rule as it is now.

429Problem

Rate limited; retry after the given number of seconds.

errorProblem

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.

ParameterTypeDescription
hid
path required
string (uuid)

A household the caller belongs to; 404 otherwise.

id
path required
string (uuid)

The row's ID, generated by the client (UUID v7).

If-Match
header
string
pattern ^"[0-9]{1,18}"$

The version the client last saw, as an entity tag: "7". A different stored version answers 412 with the current row; a deleted row still answers 410 first. Ignored on a create. Not together with If-None-Match (400). Absent: no check.

ResponseTypeDescription
204

Deleted (a tombstone stays for sync).

412DeclutterRuleConflict

Changed since the If-Match version.

429Problem

Rate limited; retry after the given number of seconds.

errorProblem

Error (RFC 9457 problem details).

Schemas

BoundingBox

Where the thing is in the photo, as fractions of its width and height.

FieldTypeDescription
h requirednumber (double)
w requirednumber (double)
x requirednumber (double)
y requirednumber (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

FieldTypeDescription
codes requiredarray of string

CodeReservationRequest

FieldTypeDescription
count requiredinteger
1–200

How many codes to reserve.

Container

FieldTypeDescription
code requiredstring

The label's code, normalized.

created_at requiredstring (date-time)
id requiredstring (uuid)
location_id requiredstring (uuid)
updated_at requiredstring (date-time)
version requiredinteger (int64)
carried_atstring (date-time)

When they took it along.

carried_bystring (uuid)

Who has it with them; absent while it's in its place.

deleted_atstring (date-time)

Set on tombstones only.

labelstring
photo_idstring (uuid)

The box's latest photo: the newest capture confirmed in box mode that kept at least one item. Its URLs come from GET …/photos/{id}.

ContainerConflict

A 412 problem with the container as it is now.

FieldTypeDescription
current requiredContainer
status requiredinteger
title requiredstring
type requiredstring
detailstring
request_idstring

ContainerInput

FieldTypeDescription
location_id requiredstring (uuid)
codeCodeInput
labelstring
at most 200 characters

What's written on the box, e.g. "Winter clothes".

ContainerList

FieldTypeDescription
items requiredarray of Container
as_ofPullAsOf
next_cursorstring

Pass as ?cursor= for the next page. Absent on the last page.

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.

FieldTypeDescription
containers requiredarray of Container

Containers in the location; always empty for a container.

items requiredarray of Item
locations requiredarray of Location

Child locations; always empty for a container.

truncated requiredboolean

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

FieldTypeDescription
categories requiredarray of string
condition requiredDeclutterCondition
created_at requiredstring (date-time)
enabled requiredboolean
except_categories requiredarray of string
id requiredstring (uuid)
months requiredinteger (int32)
tags requiredarray of string
updated_at requiredstring (date-time)
version requiredinteger (int64)
deleted_atstring (date-time)

Set on tombstones only.

scope_container_idstring (uuid)
scope_location_idstring (uuid)

DeclutterRuleConflict

A 412 problem with the rule as it is now.

FieldTypeDescription
current requiredDeclutterRule
status requiredinteger
title requiredstring
type requiredstring
detailstring
request_idstring

DeclutterRuleInput

FieldTypeDescription
condition requiredDeclutterCondition
months requiredinteger (int32)
1–600
categoriesarray of string
at most 50 items

Only these categories; empty is any.

enabledboolean
default true
except_categoriesarray of string
at most 50 items

Never these categories.

scope_container_idstring (uuid)
scope_location_idstring (uuid)
tagsarray of string
at most 50 items

Any of these tags; empty is any.

DeclutterRuleList

FieldTypeDescription
items requiredarray of DeclutterRule
as_ofPullAsOf
next_cursorstring

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

FieldTypeDescription
reason requiredDisposeReason

Duplicate

An item the thing may be another copy of, with where it is (as in SearchHit).

FieldTypeDescription
item requiredItem
path requiredarray of Location

Where it is, property first, down to the location the item (or its box) is in.

reason requiredstring: barcode, similar

barcode: the same EAN/UPC. similar: the same kind of thing, by name.

containerContainer
thumbSearchThumb

DuplicatesRequest

FieldTypeDescription
barcodestring
pattern ^[0-9]{8,14}$

Its EAN/UPC, if scanned.

namestring
at most 500 characters

What the thing is called, as typed or scanned.

DuplicatesResult

FieldTypeDescription
duplicates requiredarray of Duplicate

FXRates

FieldTypeDescription
base requiredstring: EUR
date requiredstring or null
pattern ^[0-9]{4}-[0-9]{2}-[0-9]{2}$

The ECB day the rates are of (YYYY-MM-DD).

rates requiredmap of number (double)

Units of each currency per euro, keyed by ISO 4217 code.

FieldError

FieldTypeDescription
field requiredstring
message requiredstring

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.

FieldTypeDescription
latitude requirednumber (double)
-90–90
longitude requirednumber (double)
-180–180

Household

FieldTypeDescription
created_at requiredstring (date-time)
id requiredstring (uuid)
name requiredstring
plan requiredstring: free, pro, family
role requiredstring: owner, editor, viewer

The current user's role in this household.

updated_at requiredstring (date-time)
version requiredinteger (int64)
ai_languagestring or null

The language the AI names things in; null = each member's own.

currenciesarray 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

FieldTypeDescription
items requiredarray of Household

HouseholdPlanStatus

FieldTypeDescription
ai_scans requiredobject
FieldTypeDescription
resets_at requiredstring (date-time)
used requiredinteger
allowedinteger

Omitted when unlimited.

can_purchase requiredboolean

The caller is an owner.

members requiredobject
FieldTypeDescription
allowed requiredinteger
used requiredinteger
offers requiredarray of PlanOffer

Every plan's limits, free first, so the app never hard-codes them.

plan requiredstring: free, pro, family
source requiredstring: free, subscription, comp
subscriptionHouseholdSubscription

HouseholdSubscription

Only when source is subscription.

FieldTypeDescription
expires_at requiredstring (date-time)
product_id requiredstring
status requiredstring: active, grace, billing_retry
will_renew requiredboolean
purchased_bystring (uuid)
storestring: 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

FieldTypeDescription
created_at requiredstring (date-time)
id requiredstring (uuid)
name requiredstring
packaging requiredPackaging
photos requiredarray of ItemPhoto

The photos the item was captured in or photographed alone (PhotoInput.item_id): the cover first, then newest first. Read-only; removeItemPhoto takes one off.

quantity requirednumber (double)
tags requiredarray of string
unopened requiredboolean
updated_at requiredstring (date-time)
version requiredinteger (int64)
barcodestring

The product's EAN/UPC, normalized.

carried_atstring (date-time)

When they took it along.

carried_bystring (uuid)

Who has it with them; absent while it's in its place.

categorystring
container_idstring (uuid)
cover_photo_idstring (uuid)

The chosen cover, as set; photos[0] is what to show.

declutter_keepboolean
default false
declutter_snoozed_untilstring (date-time)
deleted_atstring (date-time)

Set on tombstones only.

descriptionstring
disposed_atstring (date-time)

Let go; set on tombstones only.

disposed_reasonDisposeReason
last_touched_atstring (date-time)
location_idstring (uuid)
purchased_atstring

The day it was bought, YYYY-MM-DD.

valuenumber (double)
value_currencystring

ItemConflict

A 412 problem with the item as it is now.

FieldTypeDescription
current requiredItem
status requiredinteger
title requiredstring
type requiredstring
detailstring
request_idstring

ItemInput

Exactly one of container_id and location_id.

FieldTypeDescription
name requiredstring
1–500 characters
barcodestring
pattern ^([0-9]{8,14})?$

The product's EAN/UPC, once looked up with GET /v1/barcode/{ean} (found or not); 422 for a code with a wrong check digit or one never looked up. Stored normalized (UPC-A as EAN-13). Absent keeps the item's current barcode; "" clears it.

categorystring
at most 100 characters
container_idstring (uuid)
cover_photo_idstring (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_keepboolean

Keep forever; the declutter list never shows it. Absent keeps the item's.

declutter_snoozed_untilstring (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.

descriptionstring
at most 2000 characters
location_idstring (uuid)
packagingPackaging
purchased_atstring
pattern ^([0-9]{4}-[0-9]{2}-[0-9]{2})?$

The day it was bought (YYYY-MM-DD), optional. Absent keeps the item's; "" clears it.

quantitynumber (double)
0.01–99999999; default 1
tagsarray of string
at most 50 items
unopenedboolean
default false
valuenumber (double)
0–9999999999

Optional, entered by hand; needs value_currency.

value_currencystring
pattern ^[A-Z]{3}$

ISO 4217 code.

ItemList

FieldTypeDescription
items requiredarray of Item
as_ofPullAsOf
next_cursorstring

Pass as ?cursor= for the next page. Absent on the last page.

ItemPhoto

FieldTypeDescription
photo_id requiredstring (uuid)

URLs come from GET …/photos/{id}.

bboxBoundingBox

Location

FieldTypeDescription
created_at requiredstring (date-time)
id requiredstring (uuid)
kind requiredLocationKind
name requiredstring
updated_at requiredstring (date-time)
version requiredinteger (int64)
codestring

The label's code, normalized. Absent if it has none.

deleted_atstring (date-time)

Set on tombstones only.

geoGeo
parent_idstring (uuid)

Absent for a property.

LocationConflict

A 412 problem with the location as it is now.

FieldTypeDescription
current requiredLocation
status requiredinteger
title requiredstring
type requiredstring
detailstring
request_idstring

LocationInput

FieldTypeDescription
kind requiredLocationKind
name requiredstring
1–200 characters
codeCodeInput
forget_geoboolean

True removes the property's coordinates; geo must then be absent.

geoGeo
parent_idstring (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

FieldTypeDescription
items requiredarray of Location
as_ofPullAsOf
next_cursorstring

Pass as ?cursor= for the next page. Absent on the last page.

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.

FieldTypeDescription
id requiredstring (uuid)
moved_at requiredstring (date-time)
moved_by requiredstring (uuid)

The user who moved it.

taken requiredboolean

Taken along: no to_* then.

container_idstring (uuid)
from_container_idstring (uuid)
from_location_idstring (uuid)
item_idstring (uuid)
to_container_idstring (uuid)
to_location_idstring (uuid)

MoveConflict

A 412 problem with the moved item or box as it is now (one of the two).

FieldTypeDescription
status requiredinteger
title requiredstring
type requiredstring
current_containerContainer
current_itemItem
detailstring
request_idstring

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.

FieldTypeDescription
container_idstring (uuid)
item_idstring (uuid)
to_container_idstring (uuid)
to_location_idstring (uuid)
with_meboolean

Take it along: it's carried by the caller until put somewhere.

MoveList

FieldTypeDescription
items requiredarray of Move
next_cursorstring

Pass as ?cursor= for the next page. Absent on the last page.

Packaging

original: still in its retail packaging.

string: none, original

Photo

FieldTypeDescription
created_at requiredstring (date-time)
id requiredstring (uuid)
original_expires_at requiredstring (date-time)

After this only the thumbnail is kept.

status requiredPhotoStatus
heightinteger

Pixels, known once ready.

original_urlstring (uri)
size_bytesinteger (int64)
taken_atstring (date-time)
thumb_urlstring (uri)
uploadPhotoUpload
urls_expire_atstring (date-time)

When thumb_url and original_url stop working.

widthinteger

Pixels, known once ready.

PhotoInput

FieldTypeDescription
size_bytes requiredinteger (int64)
1–20971520

The JPEG's exact size (at most 20 MiB). The phone downscales to a 2048 px long edge first.

checksum_sha256string
pattern ^[A-Za-z0-9+/]{43}=$

The JPEG's SHA-256, base64. Binds the photo to this file, not just its size: upload.headers then carry it as x-amz-checksum-sha256, and …/uploaded answers 409 unless the file in storage has it. Without it only the size is checked. A repeat while pending may change it with the size; a repeat without it drops the check.

item_idstring (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_atstring (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.

FieldTypeDescription
expires_at requiredstring (date-time)
headers requiredmap of string

Headers the signature covers, e.g. Content-Type and Content-Length.

url requiredstring (uri)

PlanOffer

FieldTypeDescription
members_allowed requiredinteger
plan requiredstring: free, pro, family
ai_scans_allowedinteger

AI scans a month; omitted when unlimited.

Problem

RFC 9457 problem details. detail is safe to show; internals are only logged.

FieldTypeDescription
status requiredinteger
title requiredstring
type requiredstring

about:blank unless the problem has a type of its own: https://neatkept.com/problems/plan-limit (402), https://neatkept.com/problems/ai-consent-required (403: the user hasn't allowed the AI; any other 403 is a role too low), https://neatkept.com/problems/update-app (410 from a lock-code or key call only shipped builds make; lock codes moved to /locks), https://neatkept.com/problems/upload-mismatch (409 from …/photos/{id}/uploaded: the file in storage isn't the photo, upload it again).

allowedinteger

With type plan-limit — what the plan allows.

detailstring
errorsarray of FieldError

Field-level validation errors.

limitstring: ai_scans, members

With type plan-limit (HTTP 402) — which limit was hit.

planstring: free, pro, family

With type plan-limit — the household's plan.

request_idstring

Same as the X-Request-ID response header.

resets_atstring (date-time)

With limit ai_scans — when the month's scans come back.

usedinteger

With type plan-limit — used so far (this month for ai_scans).

Product

FieldTypeDescription
barcode requiredstring

The normalized code (UPC-A as EAN-13).

fetched_at requiredstring (date-time)
name requiredstring

The product's name, in whatever language the database has it.

source requiredstring

Which database answered, e.g. openfoodfacts.

brandstring
categorystring

The database's most specific category, e.g. sweet spreads.

modelstring

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

FieldTypeDescription
code requiredstring

The code as stored.

kind requiredResolutionKind
path requiredarray 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 unassigned.

containerContainer
contentsContents
locationLocation

ResolutionKind

unassigned: a reserved code nothing is saved with yet.

string: container, location, unassigned

SearchHit

FieldTypeDescription
item requiredItem
path requiredarray of Location

Where it is, property first, down to the location the item (or its box) is in.

score requirednumber (double)

The fused RRF score; only the order means anything.

containerContainer
thumbSearchThumb

SearchRequest

FieldTypeDescription
query requiredstring
1–200 characters

What the user typed or said.

limitinteger
1–50; default 20

SearchResult

FieldTypeDescription
ai requiredboolean

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 requiredarray 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.

FieldTypeDescription
expires_at requiredstring (date-time)
photo_id requiredstring (uuid)
url requiredstring (uri)