Chapter 3

API reference

Every endpoint is a POST. This chapter covers the ones you need to sign somebody in, keep them signed in, and find out who they are from your own server.

How to call it

The API speaks Connect RPC, which over HTTP is a plain POST with a JSON body. There is no REST path building and no query string: the path is the method, and everything else is in the body.

POST https://api.albos.org/auth.v1.AuthService/GetCurrentUser
Content-Type: application/json
Connect-Protocol-Version: 1

{}

There are three ways to say who is calling.

HowWho uses itNotes
Session cookieA browserSet by the sign-in call. Send credentials: "include" and call from an allowed origin.
Authorization: BearerA native appThe access token from the sign-in response.
X-API-Key: ca_...Your own serverAn API key — a different header from the one above, and deliberately not allowed through CORS, so it cannot be used from a browser. See chapter 4.
The generated clients in chapter 7 do all of this for you. The raw shape is here so that a language without a generated client is still usable.

The endpoints

The ones an integration actually uses. "Code" means the account has to have entered an authenticator code — see chapter 8 — which an API key can never do.

PathWhat it doesSign-inCode
OAuthService/GetAuthorizationURLStart a Google or Apple sign-in——
OAuthService/HandleCallbackFinish it, and get a session——
AuthService/VerifyTokenAsk whether an access token is still goodAPI key—
AuthService/GetCurrentUserWho is thisyes—
AuthService/RefreshTokenRenew the access tokenrefresh token—
AuthService/LogoutEnd the sessionyes—
TenantService/ListMyTenantsWhich tenants this account belongs toyes—
TenantService/SelectTenantChoose the one later calls act onyes—
TenantService/ListTenantMembersWho else is in ityesyes
UserService/StepUpSubmit a code to get past the lineyes—
AuthService/LoginAddress and password. Operators only.——
AuthService/VerifyTwoFactorThe second step of that sign-in——

Signing somebody in

People who use the service sign in with Google or Apple. Two calls: one to get the URL to send them to, one to turn what comes back into a session.

POST/auth.v1.OAuthService/GetAuthorizationURL

Returns the URL to send the browser to, and a state value to check when it comes back.

FieldTypeRequiredNotes
providerenumrequired1 for Google, 3 for Apple
redirectUristringrequiredMust match what is registered for your integration
useExistingSessionbooloptionalLet the provider use the session the browser already has, instead of always showing an account chooser. For joining from a QR code, where the point is to get through.
{ "authorizationUrl": "https://accounts.google.com/o/oauth2/v2/auth?...",
  "state": "9f2c..." }
POST/auth.v1.OAuthService/HandleCallback

Exchanges the code the provider sent back for a session. This is the call that signs them in.

FieldTypeRequiredNotes
providerenumrequiredThe same one you started with
codestringrequiredFrom the provider's redirect
statestringrequiredMust match what GetAuthorizationURL returned
redirectUristringrequiredThe same one you started with
ReturnsTypeNotes
userUserSee below
tokensAuthTokensaccessToken, refreshToken, expiresIn in seconds
isNewUserboolTrue the first time. Useful for sending somebody to a welcome screen rather than straight in.
From a browser, the tokens also arrive as cookies and you can ignore the tokens field entirely. A native app keeps them itself.

Checking a token from your server

POST/auth.v1.AuthService/VerifyToken

Your backend receives an access token from your own front end and asks whether it is still good and whose it is. This is what an API key is for.

FieldTypeRequiredNotes
accessTokenstringrequiredThe token to check
ReturnsTypeNotes
activeboolFalse when the token is malformed, expired, or its session has ended. Check this before anything else.
userUserOnly when active is true
sessionIdstringWhich session it belongs to
rolestringThe operator role, empty for ordinary users. Not the role inside a tenant.
expiresAtint64Unix seconds
Do not decide anything from the token's own contents without this call. Signing out, disabling an account and lowering a role all take effect immediately, and a token that is still within its fifteen minutes will not know.

Keeping the session going

POST/auth.v1.AuthService/GetCurrentUser

Who the caller is. Empty request body.

ReturnsTypeNotes
userUserSee below
POST/auth.v1.AuthService/RefreshToken

Exchanges a refresh token for a new pair. A refresh token is single use: if the same one is presented twice, every token in that session is revoked, on the assumption that one of the two presenters stole it.

FieldTypeRequiredNotes
refreshTokenstringoptionalLeave it out from a browser: the cookie is used. A native app sends the one it kept.
POST/auth.v1.AuthService/Logout

Ends the session and clears the cookies. Empty request body, empty response.

Set-Cookie: __Host-ca_access_token=;  Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=0
Set-Cookie: __Host-ca_refresh_token=; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=0

Tenants

POST/auth.v1.TenantService/ListMyTenants

Which tenants the account belongs to, and which one is currently selected. Empty request body.

ReturnsTypeNotes
tenants[]Tenantid, name, myRole, memberCount, maxMembers, plan, memberLeaveMode, acceptsJoinRequests, suspendedAt, suspendedReason
selectedTenantIdstringEmpty when none is selected
remainingTenantAllowanceint32How many more this account may create
suspendedAt being set means the tenant has no owner who can act. Its members can still read it and leave it; changes are refused with 412 until somebody takes it over.
POST/auth.v1.TenantService/SelectTenant

Tenant-scoped calls act on the selected tenant rather than taking an id, so that a caller cannot reach a tenant it did not choose by passing a different id. Call this first; the choice is remembered for the session.

FieldTypeRequiredNotes
tenantIdstringrequiredMust be one the account belongs to

Getting past the code

POST/auth.v1.UserService/StepUp

Submits an authenticator code so that the calls behind the line will go through. Call it when a call comes back 412; the header on that response says which of three situations it is.

FieldTypeRequiredNotes
codestringrequiredSix digits from the authenticator app, or one backup code
ReturnsTypeNotes
steppedUpAtstringWhen it was accepted. Good for the rest of the session, and for 15 minutes for the operations that cannot be undone.

The three reasons, and what each asks of the person, are in Chapter 6.

Signing in with a password

This is the operators' way in, not your users'. Accounts created with Google or Apple have no password and cannot grow one, so calling Login for them will not work. Registering with an address and password is currently switched off entirely.
POST/auth.v1.AuthService/Login

Takes an address and a password. It does not return a session: an operator must have an authenticator app, so the answer is always a challenge.

ReturnsTypeNotes
requiresTwoFactorboolTrue. Pass the token below to the next call.
twoFactorTokenstringValid for 5 minutes. Not a session — it cannot be used for anything else.
POST/auth.v1.AuthService/VerifyTwoFactor

The second step. This is what returns a session.

FieldTypeRequiredNotes
twoFactorTokenstringrequiredFrom the Login response
codestringrequiredSix digits, or one backup code
ReturnsTypeNotes
userUser—
tokensAuthTokens—
usedBackupCodeboolTrue when a backup code was spent, so you can warn about it
remainingBackupCodesint32How many are left

The User object

FieldTypeNotes
idstringStable. Key your own records off this, not off the address.
emailstringCan change, and can be an Apple relay address
displayNamestring—
avatarUrlstringEmpty when none is set
phoneNumberstringEmpty when none is set
emailVerifiedboolFalse until the address is confirmed
isEnabledboolFalse for a frozen account
rolestringOperator role, empty for ordinary users. Not the tenant role.
hasPasswordboolFalse for Google and Apple accounts
expiresAtstringEmpty unless the account is time limited
lastActiveAtstring—
createdAtstring—

Values you will see

Two different things are both called a role. The one inside a tenant is tenants[].myRole; the operator one is user.role and is empty for everybody who is not staff.

tenants[].myRoleMeans
OWNERRuns the tenant: invites, removes, changes settings, pays
MEMBERBelongs to it
tenants[].planMembersAPI keysAudit log kept
FREE1027 days
LIGHT50330 days
PRO5002030 days
tenants[].memberLeaveModeMeans
FREEA member can leave on their own. The default.
APPROVALA member asks, and an owner decides.

The operator roles and what each can do are in Chapter 5.