Accounts and users
These routes manage who can access an account, which account the current session is operating on, and account-level usage visibility for signed-in members. They use the same session cookie as Auth and sessions — call them against whichever host you signed in on.
How it works
Use these routes after a session has already been established.
- List accessible accounts if the user belongs to more than one account.
- Switch the active account if needed.
- List current users, invitations, and workspace grants for that account.
- Create invitations, update roles, assign workspace grants, or remove access.
- Read aggregate trends or detailed AI usage when a member needs account-level activity reporting.
Organization roles are scoped to one account:
owneris the top authority for that organization.admincan manage users, workspaces, settings, documents, ingestion, and service accounts, but cannot remove or demote owners.membercan use workspaces broadly and manage personal API credentials for their own automation, but cannot manage users, rename the organization, create or delete workspaces, or manage service accounts.
Account and API-access lifecycle routes use the signed-in dashboard session. Use API access to issue personal or service credentials for eligible automation routes; those credentials cannot manage accounts or mint other credentials.
Open-source deployments have one self-created organization per server. The first signup creates it, later users join it by invitation, and signed-in users cannot create another organization. This restriction does not apply to workspaces: owners and admins can continue to create workspaces inside the organization with POST /api/v1/workspace.
Enterprise Edition allows signed-in users to create an additional organization with POST /api/v1/account/accounts. The creator becomes its owner, a default workspace is created, and the session switches to the new organization. The Enterprise monthly anti-abuse cap applies to this additional-organization path.
Usage trends
Any active account member can read usage trends for the current account.
GET /api/v1/account/usage-trends?from=2026-06-01&to=2026-06-30&granularity=dayThe response is a continuous UTC bucket series. Each bucket includes:
- conversations created in the period
- user and assistant message counts
- succeeded input, output, and total token usage
Use granularity=day, granularity=week, or granularity=month. from and to are inclusive YYYY-MM-DD UTC dates. Very large requests are rejected when they would produce more than 366 buckets.
You can narrow the report with workspaceId and agentId. Both filters must belong to the current account. Token usage is directly scoped by account and workspace. When agentId is supplied, token events are attributed through their conversation. Token events without a conversation are not included in agent-filtered token totals because they cannot be tied to an agent.
The report returns aggregate counts only. It does not return message content, prompts, completions, retrieved chunks, or document content.
Detailed AI usage
Use detailed usage when you need to understand the model and embedding work behind a date range, rather than just its aggregate trend. Any active account member can read the two views:
GET /api/v1/account/usage/messages?from=2026-06-01&to=2026-06-30
GET /api/v1/account/usage/internal-operations?from=2026-06-01&to=2026-06-30/usage/messages returns one summary for each visitor-facing user message. Its modelTokens and embeddingTokens fields stay separate: model totals include input, completion, reasoning coverage, and—when reasoning coverage is complete—visible output. Each summary also includes its messageId and conversationId, which lets the dashboard open that turn without returning the message body. Embedding input and vector counts remain a separate subtotal, so a retrieval embedding cannot change the message’s reasoning or visible-output figure.
/usage/internal-operations returns one event at a time. It covers work such as agent setup, dashboard test chat, evals, directive drafting and coherence checks, metadata generation, document processing, and provider attempts that have no visitor message. Each row includes a stable operation label, provider and model, event kind, status, usage quality, token dimensions when available, and embedding vector count when applicable.
Both endpoints accept inclusive UTC from and to dates in YYYY-MM-DD form, an optional account-owned workspaceId, limit from 1 through 100 (50 by default), and an opaque cursor for the next page. The date range can span at most 90 days. A workspace filter that belongs to another account returns 400.
The responses are deliberately operational rather than conversational. They return attribution and token accounting, never prompt text, model completion text, message bodies, document content, provider request IDs, idempotency keys, or error details.
Invite a user
Create an invitation from the active account context.
POST /api/v1/account/invitationsThe request body is defined by the OpenAPI schema. Use the API Reference for the exact payload.
The invite request includes a role field. Use member by default. Use admin only for trusted teammates who should manage the organization.
Radioso emails the invitation to the address you name, using the mail sender configured for the deployment. The response tells you what happened: emailDelivered is true when a mail provider accepted the message. It is false both when the provider rejected the message and when the deployment has no mail provider configured, because a server without RESEND_MAIL_API_KEY records outbound mail to its log rather than sending it. Either way the invitation is created and acceptanceUrl holds an origin-relative acceptance path — join it to your app origin and you have a link you can pass along yourself. That link is the only copy of the token, so save it if you plan to share it manually; the API never returns it again.
If emailDelivered comes back false, check MAIL_FROM_EMAIL and RESEND_MAIL_API_KEY on the backend. The logs separate the two causes: a configured provider that rejects the message writes an account_invitation_mail_delivery_failed warning carrying the provider status code, while a deployment with no provider configured stays quiet, since recording mail to the log is what that configuration asks for. In both cases the account.invitation.create audit event carries emailDelivered, so you can tell an unsent invitation from an ignored one.
Accept an invitation
The person you invited opens the acceptance link and joins from there. Two things decide what they see, and both come from one unauthenticated read:
GET /api/v1/auth/invitations/{invitationToken}It returns the invited email, the invitation status, its expiresAt, requiresExistingPassword, and federatedProviders.
requiresExistingPassword shapes the password field. When it is false, nobody has a Radioso login for that address yet, so the invitee picks a password and it becomes their credential. When it is true, a login already exists, and the password they type is checked against the one they already have — a new password will be rejected as a bad credential, so ask for the existing one.
federatedProviders lists the distinct identity providers that login can sign in with, such as ["google"]. Someone who signed up through a provider has no password to type at all, so lead with the provider when this array is non-empty and keep the password field as the alternative. An empty array means password is the only credential.
Offer a provider only when it appears here. Sending someone with no Radioso login through provider sign-in creates them a fresh organization rather than joining them to this one — the invitation is not part of that handshake.
Either way the password path is the same call:
POST /api/v1/auth/invitations/{invitationToken}/acceptThe body carries email and password. The email has to match the address the invitation was sent to. A successful response sets the session cookie and returns the joined account and its workspace.
Someone who is already signed in as the invited address does not need a password at all:
POST /api/v1/auth/invitations/{invitationToken}/accept-as-current-userThis one takes no body and authenticates with the session cookie. It returns the same payload as the password path and switches the session to the joined account. It is also the only way in for a user who signs in with Google, because a federated login has no password to check.
Common failure modes
401 Invalid email or password on the password path means a login already exists for that address and the password did not match it. Read requiresExistingPassword and federatedProviders before you build the form. If the invitee has forgotten the password, or federatedProviders names a provider your deployment does not offer, send them through POST /api/v1/auth/password-reset/request and have them retry with the new one.
401 Invitation email does not match means the submitted email — or, on the session path, the signed-in user’s email — is not the address the invitation was addressed to. Invitations are bound to one mailbox and cannot be redirected to another.
409 Invitation is no longer valid means the invitation was revoked, already accepted, or past expiresAt. Issue a new one.
Revoke an invitation
Cancel a pending invitation that has not been accepted yet. Use the invitation id from the invitations array in GET /api/v1/account/users, which carries the invitations still waiting to be accepted.
DELETE /api/v1/account/invitations/{invitationId}Owners and admins can revoke. A revoked invitation can no longer be accepted, and its acceptance link stops working. Revoking an invitation that has already been accepted returns 409; remove that user with DELETE /api/v1/account/users/{membershipId} instead.
Workspace grants
Workspace grants are optional. If no grant exists, the user inherits their organization role for the workspace.
Use workspace grants to give a user admin or member access for a workspace. Grants do not remove inherited access.
PUT /api/v1/account/workspaces/{workspaceId}/grants/{userId}
DELETE /api/v1/account/workspaces/{workspaceId}/grants/{userId}Switch account context
Switching accounts changes which account subsequent session-scoped routes operate on.
{
"accountId": "account-uuid",
"preferredWorkspaceId": "workspace-uuid"
}accountId is required. preferredWorkspaceId is optional.
Remove account access
Use the membership id from GET /api/v1/account/users when removing access.
The route is:
DELETE /api/v1/account/users/{membershipId}Endpoint reference
GET /api/v1/account/users
GET /api/v1/account/accounts
GET /api/v1/account/usage-trends
GET /api/v1/account/usage/messages
GET /api/v1/account/usage/internal-operations
POST /api/v1/account/accounts
POST /api/v1/account/invitations
POST /api/v1/account/switch
PATCH /api/v1/account/users/{membershipId}
PUT /api/v1/account/workspaces/{workspaceId}/grants/{userId}
DELETE /api/v1/account/invitations/{invitationId}
DELETE /api/v1/account/workspaces/{workspaceId}/grants/{userId}
DELETE /api/v1/account/users/{membershipId}Common failure modes
401means the session is missing or no longer valid.400means a usage range, cursor, page limit, or workspace filter is invalid for the current account.403means the caller lacks the required permission. It is also returned when an open-source user attempts to create an additional organization.404means the membership does not exist in the current account.409usually means the requested removal would violate an account rule.