Outside accounts
A member connects a Google account in a browser, an app uses it for the household, and anyone in the household can disconnect it.
What an outside account is#
An outside account is a login at another service that a member connects so that one of Rigel's apps can use it for the household. It is called an integration in the API, and its ID starts with int_. Each integration is one use of one account. Connecting a Google account for Calendar and again for Drive makes two integrations with two separate grants, and you can disconnect either on its own.
| Service | App | What it can do | Google permissions asked for |
|---|---|---|---|
google_calendar | calendar | Sees your Google calendars and keeps the ones you link in step with Rigel, both ways. | calendar.events, calendar.calendarlist.readonly |
gmail | Reads the messages that match the labels or senders you choose. Never sends or deletes mail. | gmail.readonly | |
google_drive | documents | Reads the folders and files you choose to import. Never changes your Drive. | drive.readonly |
imap | Reads the folders you choose from your mailbox. Never sends or deletes mail. | Can't be connected yet |
Each service asks for the narrowest permissions its features need. Gmail and Drive are read-only. Rigel never sends or deletes mail through Gmail, and never changes a Drive. Credentials are stored encrypted, apart from the household's records. No route, query or pulled row returns one.
Anything an integration brings in is information, not instructions. Rigel reads it as material and acts only through commands, as it does for any content from outside the household.
Connecting an account#
Connecting is a browser flow, like signing in. GET /api/v1/households/{household_id}/integrations/{service}/connect checks that you belong to the household, then redirects to Google's consent screen for that service's permissions. service is google_calendar, gmail or google_drive. A script can't approve the consent screen, so open the address in a browser that is signed in to Rigel. Everything after that is ordinary API calls.
https://rigel.house/api/v1/households/{household_id}/integrations/google_calendar/connect?return_to=/calendarStart connecting Google Calendar
A browser navigation. The API sets short-lived cookies for the flow and redirects to Google's consent screen for the scopes the service needs.
curl 'https://rigel.house/api/v1/households/hh_01M6V9CS6GGVFCY7NAS8ZTQV95/integrations/google_calendar/connect?return_to=/calendar/settings' \
-H "Cookie: rigel_session=$RIGEL_SESSION"const response = await fetch("https://rigel.house/api/v1/households/hh_01M6V9CS6GGVFCY7NAS8ZTQV95/integrations/google_calendar/connect?return_to=/calendar/settings", {
headers: {
Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
},
});
const data = await response.json();GET /api/v1/households/hh_01M6V9CS6GGVFCY7NAS8ZTQV95/integrations/google_calendar/connect?return_to=/calendar/settings HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>HTTP/1.1 302 Found
X-Request-Id: req_01M6V9CSTCYAGMXCXZ29SZ15HC
Location: https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=example.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Frigel.house%2Fapi%2Fv1%2Fintegrations%2Fgoogle%2Fcallback&state=bkFAFL19JVpMKKoJbXhTR_bV5aEuYeVXc30ixzrrCVc&code_challenge_method=S256&code_challenge=WDbFCXZ96GXUydCM5i19LwunqeS6zqnxlhbl01A9gv4&scope=openid+email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcalendar.events+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcalendar.calendarlist.readonly&access_type=offline&prompt=consentreturn_to is a path on rigel.house to open afterward. A path on another origin, or under /api, is ignored and the browser lands on /. The flow has 10 minutes to finish. Google sends the browser to GET /api/v1/integrations/google/callback, which you don't call yourself. It always redirects and never answers with JSON.
- On success the browser lands on
return_towithintegration_connectedset to the new integration's ID. - On failure it lands there with
integration_errorset to one of the values below, and nothing is stored.
integration_error | What happened |
|---|---|
cancelled | The person backed out at Google. |
scopes_missing | The person unticked a permission the service needs. Connect again and keep every box ticked. |
provider_failed | The flow expired or Google refused it. Start again. |
signed_out | The Rigel session ended during the flow. |
household_not_found | The person isn't a member of the household any more. |
not_configured | This server can't connect accounts. |
A household you aren't in, or a service that isn't connected this way, answers 404 before any redirect. Connecting the same Google account for the same service again updates the existing integration instead of adding another, and sets it back to connected.
Seeing what is connected#
household.integration.list returns every connected account in the household and whether it works. Pass app to see only one app's. Every member sees all of them.
List the connected accounts
Everyone in the household can see what's connected. No credential is ever returned.
curl 'https://rigel.house/api/v1/households/hh_01M6V9CS6GGVFCY7NAS8ZTQV95/queries/household.integration.list' \
-H "Cookie: rigel_session=$RIGEL_SESSION"const response = await fetch("https://rigel.house/api/v1/households/hh_01M6V9CS6GGVFCY7NAS8ZTQV95/queries/household.integration.list", {
headers: {
Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
},
});
const data = await response.json();GET /api/v1/households/hh_01M6V9CS6GGVFCY7NAS8ZTQV95/queries/household.integration.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_01M6V9CSTR7FX5F933B1WVA0H3
{
"items": [
{
"id": "int_01M6V9CSTFHJQXMXWK5DX51W4V",
"service": "google_calendar",
"name": "Google Calendar",
"what_it_can_do": "Sees your Google calendars and keeps the ones you link in step with Rigel, both ways.",
"app": "calendar",
"account_label": "ada.okafor@example.com",
"state": "connected",
"problem": null,
"connected_by_person_id": "per_01M6V9CS6RX26JK5KVYGRN22D2",
"connected_at": "2026-11-07T15:17:52.590Z"
},
{
"id": "int_01M6V9CSTNJWMPDJYH543TTF7A",
"service": "gmail",
"name": "Gmail",
"what_it_can_do": "Reads the messages that match the labels or senders you choose. Never sends or deletes mail.",
"app": "email",
"account_label": "okafors.mail@example.com",
"state": "connected",
"problem": null,
"connected_by_person_id": "per_01M6V9CS6RX26JK5KVYGRN22D2",
"connected_at": "2026-11-07T15:17:52.595Z"
},
{
"id": "int_01M6V9CSTQDH5WHAHGST73CA42",
"service": "google_drive",
"name": "Google Drive",
"what_it_can_do": "Reads the folders and files you choose to import. Never changes your Drive.",
"app": "documents",
"account_label": "ada.okafor@example.com",
"state": "connected",
"problem": null,
"connected_by_person_id": "per_01M6V9CS6RX26JK5KVYGRN22D2",
"connected_at": "2026-11-07T15:17:52.599Z"
}
]
}state is connected or needs_reconnect. An integration needs a reconnect when Google stops accepting its credential, for example because the person revoked access or changed their password. problem then says what is wrong in words. The app stops using the account until someone connects it again, as above. connected_by_person_id is the family person who connected it, or null if they have left.
Disconnecting#
household.integration.disconnect deletes the integration, its stored credential and the app records that only exist for it, such as the calendars linked to a Google account or a Gmail filter. What it brought in stays: events, emails and imported documents are household records now. Any member of the household can disconnect any integration. The command is not for Rigel, so Rigel never runs it.
Rigel doesn't revoke the grant at Google. The credential Rigel held is deleted and can't be used again. To remove Rigel's access at Google as well, the person opens their Google account's permissions page.
Google Calendar#
Once google_calendar is connected, calendar.google.link keeps one Rigel calendar in step with one Google calendar. It needs the integration_id and Google's ID for the calendar, google_calendar_id. Give calendar_id to link an existing Rigel calendar, or leave it out and Rigel makes a new one named after the Google calendar. direction is both (the default), import or export. A read-only Google calendar can only be imported, and is_busy_only imports just the times as "Busy". Rigel then pulls the whole Google calendar within moments and keeps the two in step in the direction you chose. calendar.google.unlink ends the link. The events stay in Rigel, unlinked, and nothing is deleted on Google. calendar.calendar.list shows each calendar's link.
Google Drive#
With google_drive connected, GET /api/v1/households/{household_id}/documents/google/files lists one page of a Drive folder, 100 entries at a time, folders first and then by name. Pass the integration_id and a parent_id: root for My Drive, shared for Shared with me, or a folder's Drive ID. The answer is { items, next_page_token }, and next_page_token goes back as page_token for the next page. Each item has an id, a name, a kind of folder, google_doc or file, the mime_type, modified_at in integer UTC milliseconds, and size in bytes or null. Nothing it returns is kept.
It answers 404 integration_not_found when the integration isn't connected or isn't a Drive account, 409 integration_needs_reconnect when Google no longer accepts Rigel's access, and 502 provider_failed when Google couldn't be reached. A 502 is worth trying again.
documents.google.import brings the chosen folders and files in as documents. It takes the integration_id, the Drive IDs in google_file_ids and an optional folder_id to import into. Folders are recreated. Google Docs become Markdown, or HTML when Markdown would lose something. Other files come over as files. Anything already imported is skipped. The command answers at once with an import_id, and the work runs as a job. Its progress is on the import's row, document_import, which reaches you in a pull. documents.import.dismiss removes a finished import from the library list and keeps what it imported.
Gmail#
Connecting gmail does nothing until a filter says which mail Rigel may read. email.connection.set_filter takes the integration_id, up to 20 labels and up to 20 senders (addresses or domains), with at least one between them. Rigel checks Gmail every 5 minutes for mail that matches, and reads it as it reads mail forwarded to its own address. Mail that matches neither is never read. See the email guide.
Who the history says did it#
What an integration writes is recorded as made by that outside account, acting for the member who connected it. A calendar event that arrived from Google shows that in the household's history, not a person. If the integration is disconnected while one of its background jobs is still running, the job's later commands are refused and write nothing. See the audit trail.