Invite a teammate — owner only
const url = 'http://localhost:3000/operator/team';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"email":"hello@example.com","name":"example","role":"owner"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url http://localhost:3000/operator/team \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "email": "hello@example.com", "name": "example", "role": "owner" }'Creates an operator on the caller’s own tenant and returns a one-time invite token. We send no email: the owner copies the link and delivers it however they already talk to their colleague.
role accepts owner or member only. staff is rejected by the schema with a
400 — a tenant owner minting platform staff would escape their own tenant, so it
fails at the edge rather than as a constraint violation deep in a repository.
Authorizations
Section titled “Authorizations”Request Bodyrequired
Section titled “Request Bodyrequired”object
Responses
Section titled “Responses”The invited teammate and their one-time token.
The created teammate plus their one-time invite token. The token is returned EXACTLY ONCE — only its sha256 is stored, the same way the tenant api_secret is handled — so the owner must copy the link before leaving the page.
object
An operator on the caller’s own tenant. invite_pending is true until they accept their invite and set a password. No password material is ever returned.
object
Example
{ "member": { "role": "owner", "status": "active" }}Invalid body — including role: staff, which the schema rejects here rather than letting it reach the database CHECK.
object
object
Present only for VALIDATION_FAILED.
object
Example
{ "error": { "code": "VALIDATION_FAILED" }}Invalid, expired, revoked, or absent token.
object
object
Present only for VALIDATION_FAILED.
object
Example
{ "error": { "code": "VALIDATION_FAILED" }}Authenticated but not permitted.
object
object
Present only for VALIDATION_FAILED.
object
Example
{ "error": { "code": "VALIDATION_FAILED" }}That email already has an operator row. Operator emails are unique across the whole platform, not per tenant — login is email + password and must resolve to one account — so this also fires for an address belonging to a different tenant. The route is rate-limited per tenant for that reason; see the design doc’s known-limit note.
object
object
Present only for VALIDATION_FAILED.
object
Example
{ "error": { "code": "VALIDATION_FAILED" }}Too many invites from this tenant in the last minute.
object
object
Present only for VALIDATION_FAILED.
object
Example
{ "error": { "code": "VALIDATION_FAILED" }}