Skip to content
Rigel docs
Open Rigel

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.

  1. Open https://rigel.house and sign in with Google.
  2. Open the browser's developer tools and find the cookie named rigel_session for rigel.house (the Application tab in Chrome and Edge, the Storage tab in Firefox). The cookie is HttpOnly, so it doesn't show in document.cookie.
  3. Copy its value and put it in an environment variable.
Shell
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.

TypeScript
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
curl -sS https://rigel.house/api/v1/me \
  -H "Cookie: rigel_session=$RIGEL_SESSION"

# Copy a household id from the response:
export HOUSEHOLD=hh_01JC4M7V2R5T8W1X4Z7C0D3F6H
TypeScript
const { 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

Request, curl
curl 'https://rigel.house/api/v1/me' \
  -H "Cookie: rigel_session=$RIGEL_SESSION"
Request, TypeScript
const response = await fetch("https://rigel.house/api/v1/me", {
  headers: {
    Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
  },
});
const data = await response.json();
Request, HTTP
GET /api/v1/me HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Response, 200
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
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_01JC4M7V2T1K3M5N7P9Q2R4S6V
TypeScript
const { 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

Request, curl
curl 'https://rigel.house/api/v1/households/hh_01M6V9CS6GGVFCY7NAS8ZTQV95/queries/family.person.list' \
  -H "Cookie: rigel_session=$RIGEL_SESSION"
Request, TypeScript
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();
Request, HTTP
GET /api/v1/households/hh_01M6V9CS6GGVFCY7NAS8ZTQV95/queries/family.person.list HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Response, 200
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.

curl
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" }
EOF
TypeScript
const 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

Request, curl
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"
}'
Request, TypeScript
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();
Request, HTTP
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"
}
Response, 200
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.

curl
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" }'
TypeScript
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);
Response, 404
{
  "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#

The reference has a page for every command and query, with its fields, errors and recorded examples.

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