IDs and formats
Every ID, time, date, amount and field name in the API follows a few fixed formats, listed here.
IDs#
A record's ID is a short lowercase prefix, an underscore and a 26-character ULID. The prefix says what kind of record the ID names.
rcp_01JC9C1H8W3ZT6QXN5KDV2RMYAThe 26 characters are digits and capital letters, leaving out I, L, O and U. Treat the whole string as opaque: store it as text, compare it for equality, and don't read meaning out of it or sort a list by it except as a tiebreaker. Lists come back in the order each query documents.
The IDs of rows a command creates are derived from the command's own ID, which is the Idempotency-Key you send. Sending the same command with the same key again returns the same rows rather than making new ones. The commands guide covers retries.
A few IDs are fixed so a client can use them before anything exists. The household's main chat is cnv_00000000000000000000000000.
Having an ID grants no access. Every household route checks that you belong to the household. A household you aren't in answers 404 not_found as if it didn't exist, so none of its records can be reached either. An ID with the wrong prefix for its field is a 400 validation_failed that names the field, such as "must be a recipe ID starting with rcp_".
ID prefixes#
These are all the prefixes today. A field that takes a recipe ID takes an rcp_ ID, and so on. Some kinds of record are kept only on the server and never sent to clients. A few IDs are not records at all: cmd_ is the Idempotency-Key of a command, cli_ names a device that syncs, and req_ is the X-Request-Id header on every response.
| Prefix | Names |
|---|---|
cmd_ | command |
hh_ | household |
usr_ | user |
ses_ | session |
mem_ | membership |
req_ | request |
cli_ | client |
per_ | person |
fnt_ | family note |
int_ | integration |
fil_ | file |
ntf_ | notification |
ntm_ | notification mute |
psh_ | push subscription |
fld_ | folder |
doc_ | document |
dcb_ | document body |
dcx_ | document extract |
des_ | document edit state |
dcv_ | document version |
dlk_ | document link |
dim_ | document import |
prd_ | product |
loc_ | storage location |
inv_ | inventory item |
job_ | job |
mdl_ | model call |
agc_ | agent conversation |
agm_ | agent message |
dcd_ | dashboard card |
apc_ | app picture |
tsk_ | task |
tdp_ | task dependency |
tag_ | tag |
ttg_ | task tag |
tcm_ | task comment |
tcn_ | task comment mention |
tcr_ | task comment reaction |
twt_ | task watch |
tlk_ | task link |
tvw_ | task view |
tvv_ | task view value |
tvt_ | task view tag |
dsh_ | dish |
dsi_ | dish ingredient |
mel_ | meal |
mlp_ | meal person |
mld_ | meal dish |
dci_ | dish combo icon |
dcm_ | dish combo icon dish |
mst_ | meals setting |
pcg_ | product category |
rtn_ | agent routine |
trg_ | agent trigger |
pnd_ | pending confirmation |
eml_ | email address |
emd_ | email domain |
emk_ | email domain record |
emt_ | email thread |
emm_ | email message |
ema_ | email attachment |
emx_ | email action |
emf_ | email draft |
emj_ | email draft attachment |
emc_ | email connection |
emp_ | email correspondent |
emy_ | email message recipient |
emz_ | email draft recipient |
emq_ | email connection rule |
rcp_ | recipe |
rst_ | recipe source text |
rci_ | recipe ingredient |
rcs_ | recipe step |
rsi_ | recipe step ingredient |
rck_ | recipe cook |
cal_ | calendar |
evt_ | calendar event |
evx_ | calendar event exception |
evd_ | calendar event extra date |
eva_ | calendar event attachment |
evp_ | calendar event person |
evr_ | calendar event reminder |
gca_ | Google calendar |
gcl_ | Google calendar link |
gel_ | Google event link |
cnv_ | conversation |
msg_ | message |
mat_ | message attachment |
mmn_ | message mention |
mlk_ | message link |
mrc_ | message reaction |
cem_ | custom emoji |
stk_ | sticker |
epk_ | expression pack |
epi_ | expression pack item |
crd_ | conversation read |
cfl_ | conversation follow |
smp_ | sample record |
prp_ | property |
mlc_ | maintenance location |
cat_ | maintenance category |
eqm_ | equipment model |
evi_ | evidence |
ast_ | asset |
aph_ | asset photo |
atr_ | asset fact |
idn_ | identification |
ida_ | identification alternative |
prt_ | part |
mpt_ | model part fit |
apt_ | asset part fit |
wty_ | warranty |
ctr_ | contractor |
ccn_ | contractor contact |
mtk_ | maintenance task |
mtp_ | maintenance task part |
mtc_ | maintenance task completion |
mpj_ | maintenance project |
mpa_ | maintenance project asset |
mpu_ | maintenance project update |
iss_ | issue |
vst_ | visit |
vas_ | visit asset |
vsi_ | visit issue |
ivc_ | invoice |
ivl_ | invoice line |
osv_ | ongoing service |
shi_ | shopping list item |
sto_ | store |
ais_ | store aisle |
spa_ | product placement in a store aisle |
pgr_ | product group |
pgm_ | product group membership |
spg_ | product group a store carries |
spo_ | store product override |
trp_ | shopping trip |
tri_ | shopping trip item |
sim_ | meal a shopping list item is for |
sir_ | recipe a shopping list item is for |
tre_ | email a shopping trip was recorded from |
A field that can point at any record, such as record_id on notifications.notification.send, accepts an ID with any prefix in this table.
Times and dates#
Time comes in four forms.
- An instant is ISO 8601 in UTC, with milliseconds:
2026-11-09T16:00:00.000Z. Most command and query responses write instants like this, and the fields are named…_at. The calendar's queries write an event's start and end with the household's offset instead, such as2026-11-09T10:00:00-06:00. The rows of a pull, the routes under/agent, the socket's messages, the routines and triggers of the agent area, the Today querydashboard.day.getand the Drive browser use integer UTC milliseconds instead, such as1794240000000. The field's description says which. - An instant you send is ISO 8601 with a zone:
2026-11-09T10:00:00-06:00or the same moment as2026-11-09T16:00:00Z. A few fields, such as a calendar event's start and end, also take wall-clock time without a zone,2026-11-09T10:00, and read it in the event's own time zone. The field's description says when that is allowed. - A calendar date is
YYYY-MM-DDwith no time zone and no conversion:2026-11-11. A best-by date, a birthday and the day of a meal are dates. Don't send midnight as a stand-in for one. - A repeating local time, such as Wednesdays at 9 am, is kept as a local time and day and uses the household's time zone, an IANA name such as
America/Chicagothat you give when you create the household.
A pull returns stored rows, which is why their times are milliseconds and their booleans are 1 and 0.
A date that is only partly known carries a precision field rather than pretending. A person's birth date has birth_date_precision, one of day, month or year. With month the day is 01 and means nothing.
Amounts and money#
An amount is a number and a unit in two separate fields, and the unit is never written inside a string. The number is more than 0 and at most 1,000,000. The unit is an ID from one fixed list. A unit left out or sent as null means a plain count of the things themselves, like 3 eggs.
{ "name": "garlic", "quantity": 2, "quantity_max": 3, "unit": "clove" }The field names follow the app. A recipe ingredient has quantity, quantity_max for the top of a range, and unit. A shopping list item has quantity and unit. An inventory item has quantity and quantity_unit, plus size_value and size_unit for the size of one package.
Money is an integer count of minor units with a currency code, never a decimal: 499 is $4.99 in USD. Shopping trips record receipt prices and totals as price_minor and total_minor with a currency, and Maintenance records costs, quotes and invoices the same way. The currency is required whenever an amount is given.
Field names, null and omitted#
JSON fields, query parameters and path parameters are all snake_case. A single record comes back as itself, with no wrapper around it. Booleans in responses are true and false. Enums are strings, and the field's description names every value. Text that people write is stored as written, without trimming or reformatting.
Null and omitted mean different things. On a command that changes a record, a field you leave out is left alone, and a field you send as null is cleared, where the field allows null. This clears a birth date:
{
"person_id": "per_01JC8Q5X0RT4M8ZVB2HNQ6EK1Y",
"birth_date": null,
"birth_date_precision": null
}Responses spell "nothing" as null, and each field in the reference says whether it can be.
Links between apps#
Each real-world thing has one record, and everything else points at it by ID instead of copying its name. A meal points at its dishes, a recipe ingredient at a product, an inventory item at its place and at the person it is kept for. The fields are named …_id, and the ID is the whole link. To show a person what a record says, read the record, not a copy.
Every kind of record also has an address in the web app, which can be worked out from the ID alone. A record with no screen of its own opens the screen that shows it. A client can link to any record in this table with https://rigel.house and the path, where {id} is the record's whole ID, prefix included. The page opens for a signed-in member of the household.
| Prefix | Opens |
|---|---|
per_ | /family |
fnt_ | /family |
ntf_ | /notifications |
fld_ | /documents/folder/{id} |
doc_ | /documents/{id} |
dcb_ | /documents |
dcx_ | /documents |
dcv_ | /documents |
dlk_ | /documents |
prd_ | /inventory |
loc_ | /inventory/location/{id} |
inv_ | /inventory/item/{id} |
dcd_ | / |
tsk_ | /tasks/{id} |
tag_ | /tasks?tab=tags |
tvw_ | /tasks?view={id} |
dsh_ | /meals |
mel_ | /meals/meal/{id} |
pcg_ | /inventory |
eml_ | /email/settings |
emd_ | /email/settings |
emk_ | /email/settings |
emt_ | /email/threads/{id} |
emm_ | /email/{id} |
ema_ | /email/attachments/{id} |
emf_ | /email/drafts |
emc_ | /email/settings |
rcp_ | /recipes/{id} |
cal_ | /calendar/settings |
evt_ | /calendar?event={id} |
evx_ | /calendar |
evd_ | /calendar |
eva_ | /calendar |
evp_ | /calendar |
evr_ | /calendar |
gca_ | /calendar/settings |
gcl_ | /calendar/settings |
cnv_ | /chat/{id} |
msg_ | /chat/message/{id} |
prp_ | /maintenance/home |
mlc_ | /maintenance/home |
eqm_ | /maintenance/home |
ast_ | /maintenance/assets/{id} |
prt_ | /maintenance/home |
wty_ | /maintenance/home |
ctr_ | /maintenance/people/{id} |
ccn_ | /maintenance/people |
mtk_ | /maintenance/tasks/{id} |
mtc_ | /maintenance |
mpj_ | /maintenance/projects/{id} |
mpu_ | /maintenance/projects |
iss_ | /maintenance/issues/{id} |
vst_ | /maintenance/visits/{id} |
ivc_ | /maintenance/people |
osv_ | /maintenance/people |
shi_ | /shopping |
sto_ | /shopping/stores/{id} |
ais_ | /shopping |
pgr_ | /shopping |
trp_ | /shopping/trips/{id} |
tri_ | /shopping/trips |
tre_ | /shopping/trips |
Prefixes not in this table have no address yet. A notification sent with notifications.notification.send and a record_id opens that record when someone taps it.