Skip to content
Rigel docs
Open Rigel

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.

Text
rcp_01JC9C1H8W3ZT6QXN5KDV2RMYA

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

PrefixNames
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 as 2026-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 query dashboard.day.get and the Drive browser use integer UTC milliseconds instead, such as 1794240000000. The field's description says which.
  • An instant you send is ISO 8601 with a zone: 2026-11-09T10:00:00-06:00 or the same moment as 2026-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-DD with 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/Chicago that 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.

JSON
{ "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:

JSON
{
  "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.

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.

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

Rigel is in early access, and sign-up is by invitation. Open Rigel