OAuth API

The OAuth 2.1 authorization server and the SPA's view of it. The protocol endpoints live on the API root, unversioned, because the RFCs are their contract and the metadata document advertises them there.

Updated

On this page

13 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.

GET /.well-known/oauth-authorization-server

Authorization server metadata Extension

RFC 8414 discovery. Everything a client needs to talk to the authorization server without hard-coding a URL. Cached for five minutes. The same document is served from any path suffix (/.well-known/oauth-authorization-server/anything), because clients differ on where they look.

Base URL
https://api.signclad.com
Authentication
None — this operation is public

Responses

Status Description
200 The metadata document.

Example request

curl -X GET https://api.signclad.com/.well-known/oauth-authorization-server \
  -H "X-Api-Key: $SIGNCLAD_API_KEY"

GET /.well-known/oauth-protected-resource

Protected resource metadata Extension

RFC 9728 discovery: which authorization server protects this API, which scopes it understands, and how long a resource server may cache an introspection result.

Base URL
https://api.signclad.com
Authentication
None — this operation is public

Responses

Status Description
200 The metadata document.

Example request

curl -X GET https://api.signclad.com/.well-known/oauth-protected-resource \
  -H "X-Api-Key: $SIGNCLAD_API_KEY"

GET /oauth/authorize

Show the consent screen Extension

The authorization endpoint. PKCE is mandatory: code_challenge with code_challenge_method=S256, no exceptions and no default. response_type must be code; there is no implicit grant. The screen is skipped when there is nothing to decide. A grant that already covers this client, this audience, and every requested scope is honoured silently and the browser goes straight back with a code — showing a consent screen for something already approved only teaches people to click through consent screens. Three things force it anyway: prompt=consent, a request for a scope the existing grant does not carry, and a grant whose workspace the person has since left. Otherwise, three possible answers. No session redirects to the login page with a redirect back here. A browser gets the consent page as HTML. Accept: application/json gets the same information as JSON, for a SPA rendering its own consent screen. Failures split by whether the redirect URI can be trusted yet. An unknown client, or a redirect_uri that does not exactly match a registered one, is answered here — never redirected, because redirecting would make this an open redirector. Everything after that (bad scope, bad PKCE, bad resource) redirects to the client with error, error_description, the state if there was one, and always iss.

Base URL
https://api.signclad.com
Authentication
None — this operation is public

Query parameters

Name Type Required Description
client_id string Required
redirect_uri string (uri) Optional Required unless the client registered exactly one. Matched exactly, except that the port is ignored for loopback addresses so a native app can bind any free port.
response_type string Required
code_challenge string Required 43 characters of unreserved base64url.
code_challenge_method string Required
scope string Optional Space-delimited. Empty means every scope the client registered.
state string Optional
nonce string Optional
resource string (uri) Optional RFC 8707 audience. One absolute http/https URI, no fragment. Defaults to this API.
prompt string Optional A space-delimited list (OpenID Connect Core §3.1.2.1), the same production scope uses. It was compared as a single string until the error-envelope pass, which meant prompt=consent login silently skipped the very screen the client had insisted on.…

Responses

Status Description
200 The consent screen, as HTML for a browser or JSON for a SPA.
302 To the login page when there is no session; straight back to the client with a code when an existing grant covers the request; or back to the client with an error when the failure happens after th…
400 The OAuth error envelope: error plus a human-readable error_description. Always Cache-Control: no-store.
401 Client authentication failed.
403 The OAuth error envelope: error plus a human-readable error_description. Always Cache-Control: no-store.
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/oauth/authorize \
  -H "X-Api-Key: $SIGNCLAD_API_KEY"

POST /oauth/authorize

Approve or decline Extension

Records the decision. Every authorization parameter is re-validated from scratch rather than trusted from the session, and workspace_id names which workspace the grant is scoped to — a person in several workspaces authorizes one of them, not all. A decline is not an error: it redirects to the client with error=access_denied.

Base URL
https://api.signclad.com
Authentication
None — this operation is public

Request body

application/x-www-form-urlencoded

Property Type Required Description
client_id string Required
code_challenge string Required
code_challenge_method string Required
response_type string Required
action string Optional approve approves. Anything else, including absent, declines.
nonce string Optional
redirect_uri string (uri) Optional
resource string (uri) Optional
scope string Optional
state string Optional
workspace_id string Optional Required to approve. Must be a live membership of the signed-in person.

Responses

Status Description
200 For an XHR caller — where to send the browser next.
302 Back to the client with code and state, or with error=access_denied.
400 The OAuth error envelope: error plus a human-readable error_description. Always Cache-Control: no-store.
401 The OAuth error envelope: error plus a human-readable error_description. Always Cache-Control: no-store.
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/oauth/authorize \
  -H "X-Api-Key: $SIGNCLAD_API_KEY"

POST /oauth/token

Exchange a grant for a token Extension

Three grants: authorization_code, refresh_token, and client_credentials. Confidential clients authenticate with HTTP Basic or with client_id and client_secret in the body; public clients send client_id alone and are refused if they present a secret. Refresh tokens rotate and detect reuse. Using one twice revokes the whole family and forces a fresh authorization — the reply says so. A refresh may narrow scope, never widen it, and can never change the token's audience. refresh_token is present only when offline_access was granted, and never on client_credentials. nonce comes back only when the code carried one.

Base URL
https://api.signclad.com
Authentication
None — this operation is public

Request body

application/x-www-form-urlencoded

Property Type Required Description
grant_type string Required
client_id string Optional
client_secret string Optional
code string Optional
code_verifier string Optional
redirect_uri string (uri) Optional Required unless the client registered exactly one. Must match the code's.
refresh_token string Optional
resource string (uri) Optional Must match the audience the grant was issued for; a refresh cannot change it.
scope string Optional On a refresh, may only narrow. On client credentials, defaults to the client's scopes.

Responses

Status Description
200 The token.
400 The OAuth error envelope: error plus a human-readable error_description. Always Cache-Control: no-store.
401 Client authentication failed.
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/oauth/token \
  -H "X-Api-Key: $SIGNCLAD_API_KEY"

Example response (200)

{
  "access_token": "sca_9f8e7d6c5b4a39281706f5e4d3c2b1a0",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "documents:read documents:write offline_access",
  "refresh_token": "scr_1a2b3c4d5e6f708192a3b4c5d6e7f809"
}

POST /oauth/register

Register a client dynamically Extension

RFC 7591 dynamic registration, unauthenticated, so an assistant can connect without anybody pasting a client id. It issues public clients only: token_endpoint_auth_method must be none, and a confidential client for server-to-server work is created in the app instead. Redirect URIs must be HTTPS, a loopback address, or a reverse-domain private-use scheme. The response carries a registration_access_token and registration_client_uri — the only time the token is shown.

Base URL
https://api.signclad.com
Authentication
None — this operation is public

Request body

application/json

Property Type Required Description
redirect_uris array of string Required HTTPS with a host, a loopback address (127.0.0.1, ::1, localhost), or a reverse-domain private-use scheme such as com.example.app:/callback. No fragments.
client_name string Optional
client_uri string Optional
contacts array of string Optional
grant_types array of string Optional
logo_uri string Optional
policy_uri string Optional
response_types array of string Optional
scope string Optional Space-delimited.
software_id string Optional
software_statement string Optional Stored as sent. Not verified.
software_version string Optional
token_endpoint_auth_method string Optional Dynamic registration issues public clients only.
tos_uri string Optional

Responses

Status Description
201 The registered client.
400 The OAuth error envelope: error plus a human-readable error_description. Always Cache-Control: no-store.
403 The OAuth error envelope: error plus a human-readable error_description. Always Cache-Control: no-store.
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/oauth/register \
  -H "X-Api-Key: $SIGNCLAD_API_KEY"

GET /oauth/register/{client_id}

Read a dynamic registration Extension

RFC 7592. Authenticated with the registration_access_token as a bearer token. Returns the same document as the registration minus the token and the management URI. A client that was not dynamically registered is a 404.

Base URL
https://api.signclad.com
Authentication
Registration access token

Path parameters

Name Type Description
client_id string

Responses

Status Description
200 The registration.
401 The OAuth error envelope: error plus a human-readable error_description. Always Cache-Control: no-store.
404 The OAuth error envelope: error plus a human-readable error_description. Always Cache-Control: no-store.
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/oauth/register/{client_id} \
  -H "X-Api-Key: $SIGNCLAD_API_KEY"

POST /oauth/revoke

Revoke a token Extension

RFC 7009. Answers 200 with a JSON null body whether the token existed, was already revoked, belonged to another client, or was never ours — a revocation endpoint that distinguished those would be a token oracle. token_type_hint is accepted and ignored.

Base URL
https://api.signclad.com
Authentication
None — this operation is public

Request body

application/x-www-form-urlencoded

Property Type Required Description
token string Required
client_id string Optional
client_secret string Optional
token_type_hint string Optional Accepted and ignored.

Responses

Status Description
200 Revoked, or there was nothing to revoke. The body is the literal null.
400 The OAuth error envelope: error plus a human-readable error_description. Always Cache-Control: no-store.
401 Client authentication failed.
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/oauth/revoke \
  -H "X-Api-Key: $SIGNCLAD_API_KEY"

GET /oauth/authorizations

List connected applications Extension

What this person has approved: which application, which workspace, which scopes, and when it was last used. Not reachable with an OAuth bearer token — no scope grants it, and a credential must not be able to enumerate or revoke credentials.

Base URL
https://api.signclad.com/api/v1
Authentication
API key or OAuth 2.1 access token

Responses

Status Description
200 The authorizations.
401 Missing or invalid credentials. Unauthenticated requests are rate limited at 50 per minute.…
403 The OAuth token does not carry a scope that grants this route — and some routes, such as anything that mints or revokes credentials, are reachable by no scope at all.

Example request

curl -X GET https://api.signclad.com/api/v1/oauth/authorizations \
  -H "X-Api-Key: $SIGNCLAD_API_KEY"

DELETE /oauth/authorizations/{id}

Disconnect an application Extension

Revokes the grant and every token under it in one transaction. The bearer guard caches nothing, so it takes effect on the next request; the sixty seconds in the metadata document is a ceiling on a resource server's own introspection cache, not slack here.

Base URL
https://api.signclad.com/api/v1
Authentication
API key or OAuth 2.1 access token

Path parameters

Name Type Description
id string

Responses

Status Description
204 Disconnected.
401 Missing or invalid credentials. Unauthenticated requests are rate limited at 50 per minute.…
403 The OAuth token does not carry a scope that grants this route — and some routes, such as anything that mints or revokes credentials, are reachable by no scope at all.
404 No such record for this workspace. Signclad never answers 404 for a record that exists: a 404 is authoritative.

Example request

curl -X DELETE https://api.signclad.com/api/v1/oauth/authorizations/{id} \
  -H "X-Api-Key: $SIGNCLAD_API_KEY"

GET /oauth/clients

List server-to-server clients Extension

Confidential clients for the client credentials grant. Owner and admin only, and never reachable with an OAuth token: a credential must not be able to mint credentials.

Base URL
https://api.signclad.com/api/v1
Authentication
API key or OAuth 2.1 access token

Responses

Status Description
200 The clients.
401 Missing or invalid credentials. Unauthenticated requests are rate limited at 50 per minute.…
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.…

Example request

curl -X GET https://api.signclad.com/api/v1/oauth/clients \
  -H "X-Api-Key: $SIGNCLAD_API_KEY"

POST /oauth/clients

Create a server-to-server client Extension

Mints a confidential client and returns its secret once. offline_access is dropped if asked for — a client credentials token has nobody to refresh on behalf of. Omitting scopes gives documents:read, documents:write, and templates:read.

Base URL
https://api.signclad.com/api/v1
Authentication
API key or OAuth 2.1 access token

Request body

application/json

Property Type Required Description
name string Required
scopes array of string Optional

Responses

Status Description
201 The client, with its secret.
401 Missing or invalid credentials. Unauthenticated requests are rate limited at 50 per minute.…
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.…
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/api/v1/oauth/clients \
  -H "X-Api-Key: $SIGNCLAD_API_KEY"

DELETE /oauth/clients/{id}

Delete a server-to-server client Extension

Revokes the client and its tokens.

Base URL
https://api.signclad.com/api/v1
Authentication
API key or OAuth 2.1 access token

Path parameters

Name Type Description
id string

Responses

Status Description
204 Deleted.
401 Missing or invalid credentials. Unauthenticated requests are rate limited at 50 per minute.…
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.…
404 No such record for this workspace. Signclad never answers 404 for a record that exists: a 404 is authoritative.

Example request

curl -X DELETE https://api.signclad.com/api/v1/oauth/clients/{id} \
  -H "X-Api-Key: $SIGNCLAD_API_KEY"