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
- GET /.well-known/oauth-authorization-server
- GET /.well-known/oauth-protected-resource
- GET /oauth/authorize
- POST /oauth/authorize
- POST /oauth/token
- POST /oauth/register
- GET /oauth/register/{client_id}
- POST /oauth/revoke
- GET /oauth/authorizations
- DELETE /oauth/authorizations/{id}
- GET /oauth/clients
- POST /oauth/clients
- DELETE /oauth/clients/{id}
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-serverAuthorization server metadata - GET
/.well-known/oauth-protected-resourceProtected resource metadata - GET
/oauth/authorizeShow the consent screen - POST
/oauth/authorizeApprove or decline - POST
/oauth/tokenExchange a grant for a token - POST
/oauth/registerRegister a client dynamically - GET
/oauth/register/{client_id}Read a dynamic registration - POST
/oauth/revokeRevoke a token - GET
/oauth/authorizationsList connected applications - DELETE
/oauth/authorizations/{id}Disconnect an application - GET
/oauth/clientsList server-to-server clients - POST
/oauth/clientsCreate a server-to-server client - DELETE
/oauth/clients/{id}Delete a server-to-server client
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"