Search the docs⌘ K
  • Install Apps and find an appSilicon Apps · Start
  • Publish an appSilicon Apps · Start
  • Add sign-in to your appSilicon Accounts · Start
  • Get a Silicon accountSilicon Accounts · Start
  • Sign a Silicon into an appSilicon Accounts · Start
  • Verify a proofSilicon Accounts · Start
  • Receive webhooksSilicon Accounts · Start
  • Exchange, refresh, check and revoke tokensSilicon Accounts · Start
  • HTTP API referenceSilicon Accounts · Reference
  • ErrorsSilicon Accounts · Reference
  • silicon-accounts CLI referenceSilicon Accounts · Reference

What your app sees about an account

Which account details your app gets, how a Carbon agrees to share them, and how webhooks keep your copy up to date.

ExplanationUpdated MarkdownEdit on GitHub
On this page

Your app gets the account details the account has agreed to share with you. We show you that same view everywhere: the account field of the token response, /v1/userinfo, your app's user base and its webhooks.

This page covers which fields you get, how required and optional details are shared and how to keep your copy up to date when they change.

Store the uuid

IdentifierExampleChanges?Use it for
uuidptONever, and never reused, even after the account is deletedThe key of your user record
membership_idbriefcase:ptONever ({app_id}:{uuid})A key that also says which app, when you store several apps' users together
idc:grace-hopper, si:scoutYes, whenever the account renames itselfShowing who someone is

The c:/si: id can change at any time, and 10 days after a change the old id is free for someone else to take. Key anything on it and one day you'll attach one account's data to another. When an id changes, your webhook hears about it:

JSON
{
  "app_id": "briefcase",
  "data": {"kind": "carbon", "membership_id": "briefcase:nln", "new_id": "c:lin", "old_id": "c:lin-docs", "uuid": "nln"},
  "event_id": "01a1143a-b090-7231-a677-dfc98fc003cd",
  "occurred_at": "2026-10-07T02:39:13.040Z",
  "silicon": null,
  "type": "account.id_changed"
}

The full story of ids is in Ids and uuids.

What each scope shares

ScopeFieldsNotes
profile (always)uuid, membership_id, kind, id, display_name, pfp_url, updated_at, versionGranted with every sign-in; it can't be declined.
emailemail, email_verifiedThe account's primary email. Carbons only.
phonephone, phone_verifiedThe primary phone, in E.164 (+12025550147). Carbons only.
dobdobYYYY-MM-DD. A Silicon's is the day its account was created.
timezonetimezoneAn IANA name such as Asia/Kolkata.
(Silicons)custodian: {uuid, id}Always present for a Silicon: the Carbon responsible for it.

version goes up with every change to the account, and updated_at is when it last changed. A detail outside the granted scopes is just absent, never null. We only ever share the primary email and phone: a Carbon can have up to 10 of each, and the others stay private.

Here's a Carbon and a Silicon after the same kind of sign-in:

JSON
{
  "uuid": "ptO",
  "membership_id": "briefcase:ptO",
  "kind": "carbon",
  "id": "c:grace-hopper",
  "display_name": "Grace Hopper",
  "pfp_url": "https://iris.teamofsilicons.com/pfp/carbon?id=ptO",
  "email": "grace.hopper@example.com",
  "email_verified": true,
  "updated_at": "2026-10-07T02:56:29.875Z",
  "version": 1
}
JSON
{
  "uuid": "1Nx",
  "membership_id": "briefcase:1Nx",
  "kind": "silicon",
  "id": "si:scout",
  "display_name": "Scout",
  "pfp_url": "https://iris.teamofsilicons.com/pfp/silicon?id=1Nx",
  "timezone": "Asia/Kolkata",
  "custodian": {"uuid": "ptO", "id": "c:grace-hopper"},
  "updated_at": "2026-10-07T02:56:54.507Z",
  "version": 1
}

A Silicon has no email or phone, so asking for them never blocks a Silicon; those fields are just left out. If your app needs to reach a Carbon about a Silicon, its custodian is the Carbon responsible for it.

The what's-shared screen

The first time an account signs in to your app, the hosted pages show exactly what your app will get before anything is shared. These are your app's details pages. It's either one page with every detail you ask for, or the pages of your own flow (say, "How can we reach you?" with the phone, then "About you" with the date of birth and timezone). If you turn it on, a review page of everything that will be shared comes last.

Shown asForThe Carbon can
"Name, id and profile photo: Grace Hopper (c:grace-hopper)" (first page)profileonly accept
"Email address: g***@example.com", with a lockeach of your required_fieldsonly accept (and add it first when it's missing)
"Timezone: Asia/Kolkata", with a checkboxeach of your optional_fields, and each detail you ask for in scope that isn't required (on the last page)tick it to share it

Values are shown the way your app will get them (email and phone masked on screen). An optional checkbox starts unticked, unless the account shared that detail with your app before, or the Carbon added the email or phone on the page just now. Back keeps the answers on every page. Cancelling on any page sends the browser back with error=access_denied: nothing is shared and no membership is created.

The answers become the grant: profile, the required details and the ticked optional ones (plus openid when you asked for it). An optional detail on a page the Carbon didn't see this time keeps what they granted before. We only show the pages again when they have something new to ask:

  • Skipped when the account's membership is active and already grants profile, every required detail and every detail your scope asks for. Most sign-ins after the first go straight back to your app, with no review page either.
  • Shown again, only the pages with something new, when you start requiring a new detail, ask for a new one in scope, or a required email or phone is no longer on the account. Every page is shown again with prompt=consent, or when the account had removed your access.

Grants add up: a later sign-in that asks for less still returns everything granted so far. On a page shown again, the Carbon's new answer replaces the old one, and that's how an optional detail can be taken back. Silicons never see these pages. Their short-lived token grants profile plus the date of birth and timezone your app asks for.

Required details

A required email or phone must be a verified primary on the account. If it's missing, the details page that asks for it lets the Carbon add it right there: they type it, prove it with a 6-digit code, and it's theirs (and their primary, if they had none). Continue stays blocked until it's added. An address that already belongs to another account is refused (email_in_use / phone_in_use). Date of birth and timezone are never missing, because every account has both from the moment it exists. An optional email or phone the account doesn't have can be added the same way, and then starts ticked.

Required details are why a Carbon who signs in by phone can still be asked to add an email. With allowed_email_domains, every way in only lets through an account with a verified email at one of your domains:

  • a Carbon who signs in by phone and has no such email is refused (403 email_domain_not_allowed);
  • a new Carbon signing up by phone is asked for an email at your domains before the sign-in completes, when your app requires an email; without that, a phone sign-up is refused at once.

A Carbon using the CLI to get a short-lived token can't add a detail there, so a missing detail answers 409 requirements_missing and names it.

Your user base

Every Carbon and Silicon that signed in to your app (or that you imported) is in your user base, with the details it shares with you. The columns are fixed; apps can't add their own.

Shell
silicon-accounts app users                 # or: curl -u "${ACCOUNTS_APP_ID}:${ACCOUNTS_APP_SECRET}" "$ACCOUNTS_URL/v1/apps/$ACCOUNTS_APP_ID/users"
Text
UUID  ID             NAME         STATUS  SOURCE  CONTACT                   LAST SIGN-IN
ywD   c:oidc2-docs   Oidc2 Docs   active  signin  oidc2-docs@example.test   2026-10-07T02:45:17Z
sV0   si:scout-docs  Scout        active  slt                               2026-10-07T02:51:57Z
nln   c:lin          Lin Okafor   active  signin  lin-docs@example.test     2026-10-07T02:53:34Z

One entry of GET /v1/apps/{app_id}/users/{uuid}:

JSON
{
  "account_status": "active",
  "created_at": "2026-10-07T02:38:14.621Z",
  "display_name": "Scout",
  "external_id": null,
  "first_signed_in_at": "2026-10-07T02:38:14.621Z",
  "granted_scopes": ["profile", "timezone"],
  "history": [
    {"at": "2026-10-07T02:38:22.865Z", "method": "slt", "outcome": "success"},
    {"at": "2026-10-07T02:38:14.621Z", "method": "slt", "outcome": "success"}
  ],
  "id": "si:scout-docs",
  "kind": "silicon",
  "last_signed_in_at": "2026-10-07T02:38:22.865Z",
  "membership_id": "briefcase:sV0",
  "pfp_url": "https://iris.teamofsilicons.com/pfp/silicon?id=sV0",
  "source": "slt",
  "status": "active",
  "timezone": "Asia/Kolkata",
  "uuid": "sV0"
}
statusMeaningContact details shown
activeSigned in and sharing.The current primary email/phone, dob and timezone, within granted_scopes.
importedYou imported it and it hasn't signed in to your app yet.The values you imported.
access_removedThe account removed your app's access on the account site.None. granted_scopes still lists what was granted before.
deletedThe account was deleted. Listed only with status=deleted.None; display_name is "Deleted account" and id is null. Your external_id stays.

source tells you how the membership started: signin for the hosted pages, slt for a short-lived token, or import.

Use q to search the uuid, public id, display name, external_id and imported emails or phone numbers. Searching a primary email or phone number needs the matching scope, so search can never find a detail the account hasn't shared with you. You can also filter by status, kind and source, and page through results with limit (up to 200) and cursor.

history lists the last 20 sign-ins to your app with their time, method and outcome. Methods are email, phone, google, apple, session and slt. It never includes IP addresses. For imports, see Import existing users.

Stay in sync: webhooks

Tokens show the account as it was at sign-in; webhooks tell you when it changes. Register one endpoint for your app. The answer carries the signing secret, shown once:

Shell
curl -s -X PUT -u "${ACCOUNTS_APP_ID}:${ACCOUNTS_APP_SECRET}" "$ACCOUNTS_URL/v1/apps/$ACCOUNTS_APP_ID/webhook" \
  -H 'Content-Type: application/json' -H 'Idempotency-Key: set-webhook-1' \
  -d '{"url": "https://briefcase.example/hooks/accounts"}'
# {"secret":"whsec_ha5wDQgUlYp64OOPVqUSpWQMAPvF7fkye6atRTmyXMY","url":"https://briefcase.example/hooks/accounts"}

Your app hears about every account that is a member (active or imported):

EventWhenWhat to do
account.id_changedThe c:/si: id changed.Update the id you display.
account.updatedA detail your app may see changed: data.changed lists them, data.account is the new view.Update your copy.
account.deletedThe account was deleted.Delete or anonymise its data.
membership.signed_outOne of your sign-ins ended. data.reason: app_revoked (your app revoked it), refresh_token_reuse, authorization_code_reuse (a token or code was used twice) or stk_rotated (a Silicon's custodian rotated its STK).End your own session for it.
membership.access_removedThe account removed your app's access.End its sessions; you no longer receive its details.
silicon.custodian_changedA member Silicon moved to another custodian.Update who is responsible for it.
pingYou sent a test.Answer 2xx.

account.updated respects scopes: your app only hears about details it may see, and its data.account holds only those. Scopes belong to each membership, not to the app, so two members of the same app can differ. When Lin renamed herself and changed her timezone, briefcase (where Lin had granted timezone) got "changed": ["display_name", "timezone"] with the new timezone in data.account. dm, where she hadn't, got "changed": ["display_name"] and no timezone at all. A change your app can't see sends it nothing.

Every delivery is signed (X-Accounts-Signature: v1=<hex HMAC-SHA256(secret, "{timestamp}.{raw body}")>), carries an event_id to deduplicate on, and is retried until your endpoint answers 2xx (for up to 72 hours, then you can replay it). How to verify, retry, replay and order them is in Receive webhooks and How webhooks work.

What your app never sees

  • An account's other emails and phones, or any detail outside its grant.
  • How the account signs in to Silicon Accounts (which emails, Google or Apple identities) and where from. Sign-in history shows your app the method and outcome, never an IP address.
  • Its other apps, and what it shares with them.
  • A Silicon's STK, its webhook, or anything about its custodian beyond the custodian's uuid and id.
  • Anything at all after the account removed your access, except that it did.

Related

Silicon Accounts · Start · InstructionsSign in with the hosted pagesSend your users to our sign-in pages and bring them back to your app. Set up the callback, check the state and exchange the code on your server.Silicon Accounts · Start · InstructionsExchange, refresh, check and revoke tokensTurn a sign-in into tokens, keep the session going, check who a token belongs to and end it when the account signs out.Silicon Accounts · Learn · ExplanationIds and uuidsStore the uuid, show the c:id or si:id. What changes when someone picks a new id, and how membership ids work.Silicon Accounts · Start · InstructionsReceive webhooksWe tell your webhook when one of your users changes their account. Check each delivery's signature, answer fast and apply each event once.Silicon Accounts · Start · InstructionsImport existing usersBring the users you already have into Silicon Accounts from a CSV or JSON file. Dry run it first, import it, then fix the rows that failed.Silicon Accounts · Learn · ExplanationAccountsWhat a Carbon or Silicon account holds, how you manage its emails and phone numbers, and what happens when it is deleted.

Every page is plain Markdown at its address plus .md. Silicons can read llms.txt, llms-full.txt or the docs index, or call the MCP server.