Authentication API
Sign-in itself: register, log in, log out, Google, magic links, password reset, email verification. These set and clear a session cookie, live on the API root rather than under `/api/v1`, and are not part of the versioned contract.
Updated
On this page
- POST /register
- POST /login
- POST /login/mfa
- POST /logout
- POST /logout/everywhere
- POST /auth/magic_link
- POST /auth/magic_link/consume
- POST /auth/password/forgot
- POST /auth/password/reset
- POST /auth/email/verify
- POST /auth/email/verify/resend
- GET /auth/email/verify/{id}/{hash}
- GET /auth/google/redirect
- GET /auth/google/callback
- POST /contact
- GET /sso/{workspace_slug}/login
- POST /sso/{workspace_slug}/acs
- GET /sso/{workspace_slug}/callback
- POST /sso/{workspace_slug}/slo
- GET /sso/{workspace_slug}/metadata
20 operations, generated from
openapi/signclad.v1.yaml. Try them in the
interactive reference, browse
every endpoint group, or read the
guides for the prose version.
- POST
/registerCreate an account - POST
/loginSign in - POST
/login/mfaAnswer a second-factor challenge - POST
/logoutSign out - POST
/logout/everywhereSign out of every session - POST
/auth/magic_linkRequest a sign-in link - POST
/auth/magic_link/consumeSign in with a link - POST
/auth/password/forgotRequest a password reset - POST
/auth/password/resetSet a new password from a reset link - POST
/auth/email/verifyVerify a new account's email address - POST
/auth/email/verify/resendSend a fresh verification code - GET
/auth/email/verify/{id}/{hash}Verify an email address - GET
/auth/google/redirectStart Google sign-in - GET
/auth/google/callbackFinish Google sign-in - POST
/contactSend a message to Signclad - GET
/sso/{workspace_slug}/loginBegin SSO sign-in - POST
/sso/{workspace_slug}/acsSAML assertion consumer - GET
/sso/{workspace_slug}/callbackOIDC redirect target - POST
/sso/{workspace_slug}/sloSign out of SSO - GET
/sso/{workspace_slug}/metadataPublic SAML SP metadata
POST /register
Create an account Extension
Creates a user, their account, and their first workspace, and signs them in. Session
cookie, not a token: call GET /sanctum/csrf-cookie first and send the X-XSRF-TOKEN
header, as with every route on the API root. A six-digit, ten-minute verification code is
emailed immediately; until it is accepted, the session can only call GET /api/v1/me and
the verification routes.
6 requests per minute per IP. 403 when registration is closed, 409 when the caller is
already signed in.
- Base URL
https://api.signclad.com- Authentication
- None — this operation is public
Request body
application/json
| Property | Type | Required | Description |
|---|---|---|---|
email | string (email) | Required | Lower-cased and trimmed before it is stored. |
name | string | Required | |
password | string | Required | At least 12 characters and at most 72 bytes, checked against a breach corpus. A long password that appears in a known breach is refused with a 422, so length alone is not a guarantee the value will be accepted. |
workspace_name | string | Required | |
locale | string | Optional | |
timezone | string | Optional |
Responses
| Status | Description |
|---|---|
201 | Registered and signed in. |
403 | Authenticated, but not allowed to do this: an insufficient role, a credential type the route refuses, an unenrolled second factor, or a signer acting out of turn. meta.error always says which.… |
409 | Already signed in. This status is not in the shared renderer, so the body is Laravel's default {"message": "You are already signed in."} rather than the usual envelope. |
422 | errors.email reads An account with this email already exists. Sign in instead. for a taken address — including when two registrations race — or This workspace requires single sign-on. |
429 | Rate limited. The message contains the literal 429 Too Many Requests for compatibility with clients that match on it.… |
Example request
curl -X POST https://api.signclad.com/register \
-H "X-Api-Key: $SIGNCLAD_API_KEY" POST /login
Sign in Extension
Signs in with an email and password, 6 attempts per minute per IP.
Two different 200 bodies. When no second factor is needed the response is the
session payload. When one is, the response is a challenge — mfa_required: true and the
methods to offer — and it is still a 200, not a 4xx, because nothing went wrong. The
two shapes share no discriminating property; branch on mfa_required.
- Base URL
https://api.signclad.com- Authentication
- None — this operation is public
Request body
application/json
| Property | Type | Required | Description |
|---|---|---|---|
email | string (email) | Required | |
password | string | Required | |
remember | boolean | Optional |
Responses
| Status | Description |
|---|---|
200 | Signed in, or challenged for a second factor. |
422 | errors.email reads These credentials do not match our records. — the same message whether the address is unknown or the password is wrong — or This workspace requires single sign-on. |
429 | Rate limited. The message contains the literal 429 Too Many Requests for compatibility with clients that match on it.… |
Example request
curl -X POST https://api.signclad.com/login \
-H "X-Api-Key: $SIGNCLAD_API_KEY" Example response (200)
{
"ok": true,
"user": {
"id": "7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d",
"name": "Bryce Matheson",
"email": "[email protected]",
"email_verified": true
},
"mfa": {
"required": true,
"enrolled": true,
"methods": [
"passkey",
"totp",
"recovery"
],
"enforced_from": "2026-10-01T00:00:00Z",
"days_remaining": null,
"blocked": false
}
} POST /login/mfa
Answer a second-factor challenge Extension
Completes the challenge POST /login (or a magic link) started. The pending challenge
lives in the session, so nothing identifies it in the body. 10 attempts per minute per IP.
- Base URL
https://api.signclad.com- Authentication
- None — this operation is public
Request body
application/json
| Property | Type | Required | Description |
|---|---|---|---|
method | string | Required | |
code | string or null | Optional | |
credential | object | Optional | The credential the browser returned, forwarded verbatim. |
Responses
| Status | Description |
|---|---|
200 | Signed in. |
422 | errors.method reads The sign-in attempt expired. Start again. when no challenge is pending. errors.code reads That code is not valid., `The sign-in attempt expired.… |
429 | Rate limited. The message contains the literal 429 Too Many Requests for compatibility with clients that match on it.… |
Example request
curl -X POST https://api.signclad.com/login/mfa \
-H "X-Api-Key: $SIGNCLAD_API_KEY" POST /logout
Sign out Extension
Clears this session. Always 200, even when nobody was signed in.
- Base URL
https://api.signclad.com- Authentication
- None — this operation is public
Responses
| Status | Description |
|---|---|
200 | Signed out. |
Example request
curl -X POST https://api.signclad.com/logout \
-H "X-Api-Key: $SIGNCLAD_API_KEY" POST /logout/everywhere
Sign out of every session Extension
Revokes every session for this user, this one included. The count of revoked sessions goes to the security event log, not to the response.
- Base URL
https://api.signclad.com- Authentication
- None — this operation is public
Responses
| Status | Description |
|---|---|
200 | Signed out everywhere. |
Example request
curl -X POST https://api.signclad.com/logout/everywhere \
-H "X-Api-Key: $SIGNCLAD_API_KEY" POST /auth/magic_link
Request a sign-in link Extension
Emails a single-use sign-in link. The response is identical whether or not the address has an account — that is the point — so it never confirms who is registered. 5 per minute per IP.
- Base URL
https://api.signclad.com- Authentication
- None — this operation is public
Request body
application/json
| Property | Type | Required | Description |
|---|---|---|---|
email | string (email) | Required |
Responses
| Status | Description |
|---|---|
200 | Sent, if there was somewhere to send it. |
429 | Rate limited. The message contains the literal 429 Too Many Requests for compatibility with clients that match on it.… |
Example request
curl -X POST https://api.signclad.com/auth/magic_link \
-H "X-Api-Key: $SIGNCLAD_API_KEY" POST /auth/magic_link/consume
Sign in with a link Extension
Exchanges the token from a sign-in link for a session. Like POST /login this answers
either the session payload or a second-factor challenge, both as 200. The session
payload here carries no mfa block and reports email_verified: true — following the
link is itself proof of the address.
- Base URL
https://api.signclad.com- Authentication
- None — this operation is public
Request body
application/json
| Property | Type | Required | Description |
|---|---|---|---|
token | string | Required |
Responses
| Status | Description |
|---|---|
200 | Signed in, or challenged for a second factor. |
422 | errors.token reads That sign-in link is no longer valid. Ask for a new one. |
429 | Rate limited. The message contains the literal 429 Too Many Requests for compatibility with clients that match on it.… |
Example request
curl -X POST https://api.signclad.com/auth/magic_link/consume \
-H "X-Api-Key: $SIGNCLAD_API_KEY" POST /auth/password/forgot
Request a password reset Extension
The same silence as the magic link — {"sent": true} whoever asked. 5 per minute per IP.
- Base URL
https://api.signclad.com- Authentication
- None — this operation is public
Request body
application/json
| Property | Type | Required | Description |
|---|---|---|---|
email | string (email) | Required |
Responses
| Status | Description |
|---|---|
200 | Sent, if there was somewhere to send it. |
429 | Rate limited. The message contains the literal 429 Too Many Requests for compatibility with clients that match on it.… |
Example request
curl -X POST https://api.signclad.com/auth/password/forgot \
-H "X-Api-Key: $SIGNCLAD_API_KEY" POST /auth/password/reset
Set a new password from a reset link Extension
Sets the password and revokes every existing session, returning how many. It does not sign the caller in; they log in with the new password.
- Base URL
https://api.signclad.com- Authentication
- None — this operation is public
Request body
application/json
| Property | Type | Required | Description |
|---|---|---|---|
password | string | Required | At least 12 characters and at most 72 bytes, checked against a breach corpus. A long password that appears in a known breach is refused with a 422, so length alone is not a guarantee the value will be accepted. |
token | string | Required |
Responses
| Status | Description |
|---|---|
200 | Password changed. |
422 | errors.token reads That reset link is no longer valid. Ask for a new one. |
429 | Rate limited. The message contains the literal 429 Too Many Requests for compatibility with clients that match on it.… |
Example request
curl -X POST https://api.signclad.com/auth/password/reset \
-H "X-Api-Key: $SIGNCLAD_API_KEY" POST /auth/email/verify
Verify a new account's email address Extension
Accepts the six-digit code sent after password registration. The code expires after ten minutes, works once, is bound to the signed-in browser session and current email address, and is retired after five failed guesses.
- Base URL
https://api.signclad.com- Authentication
- None — this operation is public
Request body
application/json
| Property | Type | Required | Description |
|---|---|---|---|
code | string | Required |
Responses
| Status | Description |
|---|---|
204 | Email verified, or it was already verified. |
401 | Missing or invalid credentials. Unauthenticated requests are rate limited at 50 per minute.… |
422 | The code is malformed, invalid, expired, or exhausted. |
429 | Rate limited. The message contains the literal 429 Too Many Requests for compatibility with clients that match on it.… |
Example request
curl -X POST https://api.signclad.com/auth/email/verify \
-H "X-Api-Key: $SIGNCLAD_API_KEY" POST /auth/email/verify/resend
Send a fresh verification code Extension
Replaces the current code and sends a fresh one to the signed-in user's address. Always
204 when the address is already verified or nobody is signed in. Limited to one send per
minute and five per hour for the account, plus 3 requests per minute per IP.
- Base URL
https://api.signclad.com- Authentication
- None — this operation is public
Responses
| Status | Description |
|---|---|
204 | Nothing to report, by design. |
429 | Rate limited. The message contains the literal 429 Too Many Requests for compatibility with clients that match on it.… |
Example request
curl -X POST https://api.signclad.com/auth/email/verify/resend \
-H "X-Api-Key: $SIGNCLAD_API_KEY" GET /auth/email/verify/{id}/{hash}
Verify an email address Extension
The link in the verification email. It is a Laravel signed URL — a tampered one is 403
before the controller runs — and it always redirects to the web app rather than
answering JSON, with ?status=verified, already_verified, or invalid.
- Base URL
https://api.signclad.com- Authentication
- None — this operation is public
Path parameters
| Name | Type | Description |
|---|---|---|
id | string | The user's id. |
hash | string | SHA-1 of the address being verified. |
Responses
| Status | Description |
|---|---|
302 | Redirect to the web app's /auth/email-verified page with a status query parameter. |
403 | Authenticated, but not allowed to do this: an insufficient role, a credential type the route refuses, an unenrolled second factor, or a signer acting out of turn. meta.error always says which.… |
429 | Rate limited. The message contains the literal 429 Too Many Requests for compatibility with clients that match on it.… |
Example request
curl -X GET https://api.signclad.com/auth/email/verify/{id}/{hash} \
-H "X-Api-Key: $SIGNCLAD_API_KEY" GET /auth/google/redirect
Start Google sign-in Extension
Redirects to Google with the openid profile email scopes. 20 per minute per IP.
- Base URL
https://api.signclad.com- Authentication
- None — this operation is public
Responses
| Status | Description |
|---|---|
302 | Redirect to Google. |
429 | Rate limited. The message contains the literal 429 Too Many Requests for compatibility with clients that match on it.… |
Example request
curl -X GET https://api.signclad.com/auth/google/redirect \
-H "X-Api-Key: $SIGNCLAD_API_KEY" GET /auth/google/callback
Finish Google sign-in Extension
Google's redirect target. Always redirects to the web app, never JSON: on success to the
configured landing path, on failure to the login page with ?error=google_failed,
google_no_email, or google_link_requires_verified_email.
- Base URL
https://api.signclad.com- Authentication
- None — this operation is public
Responses
| Status | Description |
|---|---|
302 | Redirect to the web app. |
Example request
curl -X GET https://api.signclad.com/auth/google/callback \
-H "X-Api-Key: $SIGNCLAD_API_KEY" POST /contact
Send a message to Signclad Extension
The marketing site's contact form. Session-backed and CSRF-protected like the rest of the root.
- Base URL
https://api.signclad.com- Authentication
- None — this operation is public
Request body
application/json
| Property | Type | Required | Description |
|---|---|---|---|
email | string (email) | Required | |
message | string | Required | |
name | string | Required | No line breaks — a header-injection guard. |
subject | string | Required | No line breaks. |
Responses
| Status | Description |
|---|---|
200 | Sent. |
422 | Validation failed. errors maps a field name to a message string, or to an object keyed by an error code or by a per-item key. Values are never arrays. |
Example request
curl -X POST https://api.signclad.com/contact \
-H "X-Api-Key: $SIGNCLAD_API_KEY" GET /sso/{workspace_slug}/login
Begin SSO sign-in Extension
Redirects to the IdP — a SAML AuthnRequest, or the OIDC authorization endpoint. Nothing
here is authenticated; that is the point.
There are no error bodies on this flow. Every failure redirects to the web app's login page
with ?sso_error=<reason>, and the reasons are not_configured, not_available_on_plan,
invalid_assertion, assertion_replayed, unknown_state, domain_not_allowed, no_email,
no_membership, provider_unreachable, invalid_id_token, and provider_refused.
An unknown slug, a plan without SSO, a missing connection, and an unusable one all answer
the same 404, so nothing here reveals which workspaces exist or how they are configured.
- Base URL
https://api.signclad.com- Authentication
- None — this operation is public
Path parameters
| Name | Type | Description |
|---|---|---|
workspace_slug | string | The workspace's slug, as it appears in its SSO URLs. |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
return_to | string | Optional | Where to land after signing in. Must be a site-relative path; anything else — including a protocol-relative //host — is dropped silently rather than refused. |
Responses
| Status | Description |
|---|---|
302 | To the IdP, or to the login page with an sso_error. |
404 | No such record for this workspace. Signclad never answers 404 for a record that exists: a 404 is authoritative. |
429 | Rate limited. The message contains the literal 429 Too Many Requests for compatibility with clients that match on it.… |
Example request
curl -X GET https://api.signclad.com/sso/{workspace_slug}/login \
-H "X-Api-Key: $SIGNCLAD_API_KEY" POST /sso/{workspace_slug}/acs
SAML assertion consumer Extension
Where the IdP posts its SAMLResponse. The XML signature is the credential, so there is no
session, no bearer token, and no CSRF token on this route.
Five things are checked: the signature, the audience, InResponseTo, replay, and whether the
asserted address is in an allowed domain. Success sets the session cookie and redirects into
the app; every failure redirects to the login page with ?sso_error=<reason>.
- Base URL
https://api.signclad.com- Authentication
- None — this operation is public
Path parameters
| Name | Type | Description |
|---|---|---|
workspace_slug | string | The workspace's slug, as it appears in its SSO URLs. |
Request body
application/x-www-form-urlencoded
| Property | Type | Required | Description |
|---|---|---|---|
SAMLResponse | string | Required | |
RelayState | string | Optional | Honoured only on an IdP-initiated sign-in. |
Responses
| Status | Description |
|---|---|
302 | Into the app on success, or to the login page with an sso_error. |
404 | No such record for this workspace. Signclad never answers 404 for a record that exists: a 404 is authoritative. |
429 | Rate limited. The message contains the literal 429 Too Many Requests for compatibility with clients that match on it.… |
Example request
curl -X POST https://api.signclad.com/sso/{workspace_slug}/acs \
-H "X-Api-Key: $SIGNCLAD_API_KEY" GET /sso/{workspace_slug}/callback
OIDC redirect target Extension
Where the OIDC provider sends the browser back. The stored state row, the PKCE verifier,
and the nonce are what bind the response to the request. Same redirect-only error model as
the SAML routes.
- Base URL
https://api.signclad.com- Authentication
- None — this operation is public
Path parameters
| Name | Type | Description |
|---|---|---|
workspace_slug | string | The workspace's slug, as it appears in its SSO URLs. |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
code | string | Optional | |
state | string | Optional | |
error | string | Optional | Set by the provider when the person declined or it refused. |
Responses
| Status | Description |
|---|---|
302 | Into the app on success, or to the login page with an sso_error. |
404 | No such record for this workspace. Signclad never answers 404 for a record that exists: a 404 is authoritative. |
429 | Rate limited. The message contains the literal 429 Too Many Requests for compatibility with clients that match on it.… |
Example request
curl -X GET https://api.signclad.com/sso/{workspace_slug}/callback \
-H "X-Api-Key: $SIGNCLAD_API_KEY" POST /sso/{workspace_slug}/slo
Sign out of SSO Extension
Clears the Signclad session and hands back the IdP's logout URL for the browser to visit,
when the connection publishes one. Answers 200 with the same body whether or not there was
a session to clear.
The one SSO route that returns JSON rather than a redirect — and, unlike the assertion
consumer, it is CSRF-protected, so a SPA must send the token. It does not accept an
IdP-initiated LogoutRequest; there is no GET.
- Base URL
https://api.signclad.com- Authentication
- None — this operation is public
Path parameters
| Name | Type | Description |
|---|---|---|
workspace_slug | string | The workspace's slug, as it appears in its SSO URLs. |
Responses
| Status | Description |
|---|---|
200 | Signed out. idp_logout_url is null when the IdP publishes none. |
404 | No such record for this workspace. Signclad never answers 404 for a record that exists: a 404 is authoritative. |
429 | Rate limited. The message contains the literal 429 Too Many Requests for compatibility with clients that match on it.… |
Example request
curl -X POST https://api.signclad.com/sso/{workspace_slug}/slo \
-H "X-Api-Key: $SIGNCLAD_API_KEY" GET /sso/{workspace_slug}/metadata
Public SAML SP metadata Extension
The same service-provider metadata as GET /sso/metadata, at a public URL an IdP can fetch
for itself. Cached five minutes. Every failure — unknown slug, unentitled plan, no
connection, metadata that will not validate — is one undifferentiated 404.
- Base URL
https://api.signclad.com- Authentication
- None — this operation is public
Path parameters
| Name | Type | Description |
|---|---|---|
workspace_slug | string | The workspace's slug, as it appears in its SSO URLs. |
Responses
| Status | Description |
|---|---|
200 | The SP metadata. |
404 | No such record for this workspace. Signclad never answers 404 for a record that exists: a 404 is authoritative. |
429 | Rate limited. The message contains the literal 429 Too Many Requests for compatibility with clients that match on it.… |
Example request
curl -X GET https://api.signclad.com/sso/{workspace_slug}/metadata \
-H "X-Api-Key: $SIGNCLAD_API_KEY"