Skip to content

Signing in ​

Signing in with a password and a second factor, through OIDC or SAML, and signing out.

POST /v1/login ​

Exchange a username and password for a session token.

Returns {token, principal} and sets the session cookie (HttpOnly, SameSite=Lax, and Secure when the request came over HTTPS directly or with X-Forwarded-Proto: https). A principal with mustChangePassword must call POST /v1/me/password before anything else. After 5 failed attempts for a username, or 20 from one address, within 15 minutes, sign-in is refused with 429 and Retry-After. A user without a password (one who signs in through an OIDC provider) is refused as a wrong password is. With auth.passwords off, only admin.username may sign in here.

A user with MFA gets no session for the right password. The answer is {mfa: {ticket, methods, expiresIn}}: methods lists totp, passkey and recovery, as the user has them, and the ticket answers POST /v1/login/mfa within expiresIn seconds (300). A wrong password looks the same for every user. A principal with mfaEnrolmentRequired must set up MFA (/v1/me/mfa) before anything else.

Request body (application/json)

FieldTypeRequiredDescription
usernamestringyes
passwordstringyes

Responses

StatusDescriptionBody
200Session, or the second step's ticket
401Wrong username or password, or the user is disabled
403Passwords are off (passwords-off) and this isn't admin.username
429Too many failed attempts, or too many sign-in requests from this address (the signin rate limit); Retry-After says how many seconds to wait

POST /v1/login/mfa ​

Answer the second step of a password sign-in.

Sends the ticket from POST /v1/login with one answer: totp (a code from the user's app), recoveryCode, or passkey (the browser's PublicKeyCredential for the options of /v1/login/mfa/passkey-options, as JSON). A passkey whose sign counter went backwards is refused, as a wrong answer. A right answer ends the ticket and answers {token, principal} with the session cookie, as POST /v1/login does; a recovery code adds recoveryCodesLeft. A ticket allows 5 wrong answers, then it is dropped. Wrong answers also count as failed sign-ins (429 with Retry-After).

Request body (application/json)

FieldTypeRequiredDescription
ticketstringyes
totpstring
recoveryCodestring
passkeyobject

Responses

StatusDescriptionBody
200Session
400No answer was sent
401The ticket is unknown, used or expired (ticket-expired), or the answer is wrong (wrong-code, with triesLeft)
409TOTP secrets don't open with this server's key (totp-unavailable); use a recovery code
429Too many failed attempts, or too many sign-in requests from this address (the signin rate limit); Retry-After says how many seconds to wait

POST /v1/login/mfa/passkey-options ​

The WebAuthn request options for a ticket's passkey answer.

Answers {publicKey: PublicKeyCredentialRequestOptions}: a fresh challenge, kept with the ticket, and the user's passkeys. The RP ID is publicURL's host.

Request body (application/json)

FieldTypeRequiredDescription
ticketstringyes

Responses

StatusDescriptionBody
200The request options, for navigator.credentials.get
401The ticket is unknown, used or expired (ticket-expired)
409The user has no passkey, or passkeys are off without publicURL (no-passkeys)
429The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class.object

POST /v1/logout ​

End the caller's login session.

Ends the login token (bearer or session cookie) and wipes the viewer sessions opened under it, with their data. API keys aren't ended this way; their viewer sessions last until they are deleted or expire.

Responses

StatusDescriptionBody
204Logged out
401Not logged in
429The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class.object

GET /v1/auth/providers ​

The OIDC and SAML providers people can sign in with.

Needs no sign-in. kind is oidc or saml, and start is the path a sign-in button navigates to (with ?next=). available is false until the provider's discovery (OIDC) or metadata (SAML) has answered. passwords is false when auth.passwords is off; only admin.username may then sign in with a password.

Responses

StatusDescriptionBody
200{providers: [{id, name, kind, start, available}], passwords}
429The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class.object

GET /v1/auth/oidc/{provider}/start ​

Start signing in through an OIDC provider.

A browser navigates here (a link or location.assign, not a form). The flow (state, nonce and PKCE verifier) is sealed into the rf_oidc cookie, scoped to /v1/auth/oidc/, for 10 minutes, and the browser is sent to the provider. next is a path on this server to return to; anything else, or a path under /v1/auth/, returns to /.

Parameters

NameInTypeRequiredDescription
providerpathstringyes
nextquerystring
loginHintquerystringPassed to the provider as login_hint.

Responses

StatusDescriptionBody
302To the provider's authorization endpoint
303The provider isn't available yet; to the sign-in page with signInError=failed
404No such provider
429The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class.object

GET /v1/auth/oidc/{provider}/callback ​

Finish signing in through an OIDC provider.

The provider sends the browser here. The rf_oidc cookie is used once; its state must match, and the ID token must pass every check (docs/plans/oidc.md). A known identity signs its user in; else an invite binds it; else, with jit, a user is made. The user's mapped roles come from the provider's role rules. On success the session cookie is set, as by POST /v1/login, and the browser goes to next. Any failure goes to the sign-in page with signInError set to denied, expired, no-account, disabled, domain or failed. Failures count against the client's address as failed password sign-ins do.

Parameters

NameInTypeRequiredDescription
providerpathstringyes
codequerystring
statequerystring
issquerystring
errorquerystring

Responses

StatusDescriptionBody
303To next, signed in, or to the sign-in page with signInError
429The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class.object

GET /v1/auth/saml/{provider}/metadata ​

reportd's SAML SP metadata for an IdP.

Needs no sign-in. The metadata to import into the IdP: reportd's entity ID (<publicURL>/v1/auth/saml/<id>/metadata unless the provider sets entityID), one assertion consumer service (HTTP-POST) at <publicURL>/v1/auth/saml/<id>/acs, the SP certificate for signing and encryption, whether requests are signed, that assertions must be, and the persistent NameID format.

Parameters

NameInTypeRequiredDescription
providerpathstringyes

Responses

StatusDescriptionBody
200The SP metadatastring
404No such provider
429The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class.object

GET /v1/auth/saml/{provider}/start ​

Start signing in through a SAML IdP.

A browser navigates here (a link or location.assign, not a form). It makes a signed AuthnRequest (unless signRequests is false), seals the flow (the provider, the request ID, a random RelayState and next) into the rf_saml cookie, scoped to /v1/auth/saml/, for 10 minutes, HttpOnly, Secure and SameSite=None, since the IdP returns with a cross-site POST. The browser goes to the IdP by the HTTP-Redirect binding; an IdP that takes only HTTP-POST gets a page that posts the request. next is as for OIDC.

Parameters

NameInTypeRequiredDescription
providerpathstringyes
nextquerystring

Responses

StatusDescriptionBody
200A page that posts the request to an IdP that takes only HTTP-POSTstring
302To the IdP's single sign-on service
303The IdP's metadata hasn't been read yet; to the sign-in page with signInError=failed
404No such provider
429The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class.object

POST /v1/auth/saml/{provider}/acs ​

Finish signing in through a SAML IdP (assertion consumer service).

The IdP's page posts its response here, cross-site: the one POST the cross-site check lets through, since the signed response and the rf_saml cookie authenticate it. The cookie is used once; its provider and RelayState must match, and the response must answer its request and pass every check (docs/plans/saml.md). At most 512 KiB. Then as the OIDC callback: a known identity signs its user in, an invite binds it (with trustEmail), or, with jit, a user is made, and the session cookie is set. A response with no flow cookie, as from an IdP-initiated sign-in, gets expired. Failures count against the client's address. A flow that links an identity goes on to GET /v1/auth/saml/{provider}/link.

Parameters

NameInTypeRequiredDescription
providerpathstringyes

Request body, optional (application/x-www-form-urlencoded)

FieldTypeRequiredDescription
SAMLResponsestring
RelayStatestring

Responses

StatusDescriptionBody
303To next, signed in, to the link route, or to the sign-in page with signInError
429The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class.object

Finish linking a SAML identity.

The ACS sends the browser here when the flow links an identity (POST /v1/me/identities/{provider}): the identity it verified is sealed in the rf_saml_link cookie for a minute. This GET carries the SameSite=Lax session cookie that the IdP's cross-site POST didn't, so the identity is linked to the user who began, if they are still the one signed in.

Parameters

NameInTypeRequiredDescription
providerpathstringyes

Responses

StatusDescriptionBody
303To next, linked, or to the sign-in page with signInError
429The request's class is over its rate limit (limits.rate, or the API key's own rateLimits): {error: "rate-limited", class, message, retryAfter}. The MCP tool-call and agent edit limits answer the same body without class.object