Quickstart
Get a session, find your household, read with a query, change something with a command, and see what a rejection looks like.
Get a session#
Personal API tokens aren't built yet, so a script signs in the way the web app does: with the session cookie. Rigel is in early access, and only people it has let in can create an account.
- Open
https://rigel.houseand sign in with Google. - Open the browser's developer tools and find the cookie named
rigel_sessionfor rigel.house (the Application tab in Chrome and Edge, the Storage tab in Firefox). The cookie is HttpOnly, so it doesn't show indocument.cookie. - Copy its value and put it in an environment variable.
export RIGEL_SESSION='paste the cookie value here'The TypeScript examples use this small helper. Run them with Node 24 or later, Bun or Deno, which run .ts files directly. A browser page can't use the helper, because browsers don't let a page set the Cookie header.
type Init = { method?: string; headers?: Record<string, string>; body?: unknown };
async function rigel<T = unknown>(path: string, init: Init = {}) {
const headers: Record<string, string> = {
Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
...init.headers,
};
if (init.body !== undefined) headers["Content-Type"] = "application/json";
const response = await fetch(`https://rigel.house/api/v1${path}`, {
method: init.method ?? "GET",
headers,
body: init.body === undefined ? null : JSON.stringify(init.body),
});
return { status: response.status, body: (await response.json()) as T };
}Find your household#
GET /api/v1/me returns your account and the households you belong to. Every household route needs a household's id, which starts with hh_.
curl -sS https://rigel.house/api/v1/me \
-H "Cookie: rigel_session=$RIGEL_SESSION"
# Copy a household id from the response:
export HOUSEHOLD=hh_01JC4M7V2R5T8W1X4Z7C0D3F6Hconst { body: me } = await rigel<{
user: { id: string; email: string; name: string };
households: { id: string; name: string; role: "admin" }[];
}>("/me");
const first = me.households[0];
if (!first) throw new Error("This account has no households yet.");
const household = `/households/${first.id}`;Who you are and your households
curl 'https://rigel.house/api/v1/me' \
-H "Cookie: rigel_session=$RIGEL_SESSION"const response = await fetch("https://rigel.house/api/v1/me", {
headers: {
Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
},
});
const data = await response.json();GET /api/v1/me HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: req_01M6V9CS707TD2XHCECQH18ZG3
{
"user": {
"id": "usr_01M6V9CS5XV66GR856KS52MTTZ",
"email": "ada@example.com",
"name": "Ada Okafor"
},
"households": [
{
"id": "hh_01M6V9CS6GGVFCY7NAS8ZTQV95",
"name": "The Okafors",
"role": "admin"
}
]
}A new account has no household yet. See Households and people to create one, and sample data to fill a new household with a made-up family's records so there is something to read.
Read with a query#
family.person.list lists the people in the household's immediate family, in the order the family page shows them. A query is a GET with no body, and it answers with the data itself. The first person is the one who created the household. Keep their ID for the next step.
curl -sS "https://rigel.house/api/v1/households/$HOUSEHOLD/queries/family.person.list" \
-H "Cookie: rigel_session=$RIGEL_SESSION"
# Copy the first person's id from the response:
export PERSON=per_01JC4M7V2T1K3M5N7P9Q2R4S6Vconst { body: family } = await rigel<{
items: { id: string; name: string; age: string | null; signs_in: boolean }[];
}>(`${household}/queries/family.person.list`);
const person = family.items[0];
if (!person) throw new Error("The household has no people.");List the people in the household
curl 'https://rigel.house/api/v1/households/hh_01M6V9CS6GGVFCY7NAS8ZTQV95/queries/family.person.list' \
-H "Cookie: rigel_session=$RIGEL_SESSION"const response = await fetch("https://rigel.house/api/v1/households/hh_01M6V9CS6GGVFCY7NAS8ZTQV95/queries/family.person.list", {
headers: {
Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
},
});
const data = await response.json();GET /api/v1/households/hh_01M6V9CS6GGVFCY7NAS8ZTQV95/queries/family.person.list HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: req_01M6V9CS7D3NXMENJ9SW62671Y
{
"items": [
{
"id": "per_01M6V9CS6RX26JK5KVYGRN22D2",
"name": "Ada Okafor",
"age": null,
"signs_in": true
},
{
"id": "per_01M6V9CS744BQQH188FS6CXY9W",
"name": "Ben Okafor",
"age": null,
"signs_in": false
},
{
"id": "per_01M6V9CS79TMYHDAM7FTA9ZK3Z",
"name": "Nia",
"age": "8 years old",
"signs_in": false
},
{
"id": "per_01M6V9CS7CDAEP5VDA5T8R9TG6",
"name": "Theo",
"age": "5 years old",
"signs_in": false
}
]
}age is in words and is null without a birth date. signs_in says whether the person has an account. See Queries and lists for parameters and paging.
Change something with a command#
family.note.record remembers one thing about a person. It adds a note to your household, which shows on the family page, so run it on a household where that is fine. You can remove the note afterward with family.note.forget.
A command is a POST with a JSON body and an Idempotency-Key. The key must be cmd_ followed by 26 characters from 0-9 and A-Z without I, L, O and U. Make a new one for every command. If a request fails and you don't know whether it arrived, send it again with the same key. The command runs at most once.
export KEY="cmd_$(LC_ALL=C tr -dc '0-9A-HJKMNP-TV-Z' </dev/urandom | head -c 26)"
curl -sS -X POST "https://rigel.house/api/v1/households/$HOUSEHOLD/commands/family.note.record" \
-H "Cookie: rigel_session=$RIGEL_SESSION" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $KEY" \
-d @- <<EOF
{ "person_id": "$PERSON", "text": "Takes oat milk in coffee" }
EOFconst alphabet = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
function commandId(): string {
let time = Date.now();
let id = "";
for (let i = 0; i < 10; i++) {
id = alphabet.charAt(time % 32) + id;
time = Math.floor(time / 32);
}
for (const byte of crypto.getRandomValues(new Uint8Array(16))) id += alphabet.charAt(byte % 32);
return `cmd_${id}`;
}
const key = commandId();
const recorded = await rigel<{ result: { note_id: string }; version: number }>(
`${household}/commands/family.note.record`,
{
method: "POST",
headers: { "Idempotency-Key": key },
body: { person_id: person.id, text: "Takes oat milk in coffee" },
},
);
console.log(recorded.status, recorded.body);A note about a person, noted now
curl -X POST 'https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/family.note.record' \
-H "Cookie: rigel_session=$RIGEL_SESSION" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA' \
-d '{
"person_id": "per_01JC000000000000000000000A",
"text": "Doesn'\''t like carrots"
}'const response = await fetch("https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/family.note.record", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Idempotency-Key": "cmd_01JC4N0000AAAAAAAAAAAAAAAA",
Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
},
body: JSON.stringify({
person_id: "per_01JC000000000000000000000A",
text: "Doesn't like carrots",
}),
});
const data = await response.json();POST /api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/family.note.record HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Content-Type: application/json
Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA
{
"person_id": "per_01JC000000000000000000000A",
"text": "Doesn't like carrots"
}HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: req_01K9D9S2R0MX70R7FWCDZ7XW8J
{
"result": {
"note_id": "fnt_01JC4N0000MBGE5RJ6Q7V6V6T8"
},
"version": 1
}A command that was applied answers 200 with { result, version }. result is what the command returns, here the ID of the new note. version is the household's version after the command. Send the same request again with the same key and you get the same result and version back, and no second note. See idempotency keys.
Read it back with family.knowledge.get, which lists each person with their notes.
See a rejection#
A command can be refused because of the household's state. Send the same command about a person who isn't in the household. Nothing changes, and the response says why.
export KEY="cmd_$(LC_ALL=C tr -dc '0-9A-HJKMNP-TV-Z' </dev/urandom | head -c 26)"
curl -sS -w '\nHTTP %{http_code}\n' -X POST \
"https://rigel.house/api/v1/households/$HOUSEHOLD/commands/family.note.record" \
-H "Cookie: rigel_session=$RIGEL_SESSION" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $KEY" \
-d '{ "person_id": "per_01JC4N8Z6Q2V7W3X9Y5K1M0ABC", "text": "Takes oat milk in coffee" }'const refused = await rigel<{ error: { code: string; message: string; recovery: string } }>(
`${household}/commands/family.note.record`,
{
method: "POST",
headers: { "Idempotency-Key": commandId() },
body: { person_id: "per_01JC4N8Z6Q2V7W3X9Y5K1M0ABC", text: "Takes oat milk in coffee" },
},
);
console.log(refused.status, refused.body.error.code);{
"error": {
"code": "person_not_found",
"message": "That person isn't in the household any more. No changes were applied.",
"details": { "person_id": "per_01JC4N8Z6Q2V7W3X9Y5K1M0ABC" },
"recovery": "Read the current state, then send a command that fits it.",
"request_id": "req_01JC4N9C4Q8S1V4X7Z0B3D6F9H"
}
}Every error has this envelope. code is stable, so branch on it. The status is 404 because a record the command names is missing. A rejection for any other reason in the household's state is 409. A request that doesn't fit the command's input, such as a blank text, is 400 with validation_failed. See Errors and retries.
Where to go next#
- Commands covers idempotency, rejections and what Rigel may run.
- Queries and lists covers parameters, paging and limits.
- Errors and retries covers every status and what is safe to retry.
- The sync guide is for clients that keep a local copy of a household.
- Rigel, the agent covers asking Rigel to do things, and the routines and triggers it runs on its own.
The reference has a page for every command and query, with its fields, errors and recorded examples.