Docs

Docs / Start / Authentication

Authentication

A bearer key, scoped to what it may do.

Authorization: Bearer mk_live_1a2b3c4d…

Keys are shown once, when issued, and stored only as their SHA-256 — the same way a signing token is, and for the same reason. A key that is lost is replaced, not recovered.

Scopes

A key's scopes are its capabilities. There is no "full access" scope, because the useful question about an integration is not whether it is trusted but what it needs.

The four parts of signing:write below are for connected apps, so that the person connecting one can let it send without letting it void. A key made in the dashboard takes signing:write whole, or not at all (Teams, roles and sign-in).

ScopeWhat it carries
signing:readRead templates, documents, certificates and packets.
signing:writePrepare, send, remind and void documents; void a packet; hand out signing links to frame; record consent to text. All four of the next scopes.
signing:sendPrepare and send documents, and record consent to text. Part of signing:write. A connected app’s, not a key’s.
signing:remindRemind a signer who has not signed. Part of signing:write. A connected app’s, not a key’s.
signing:linksHand out a fresh signing link, or one to frame. Part of signing:write. A connected app’s, not a key’s.
signing:voidVoid a document or a packet. Part of signing:write. A connected app’s, not a key’s.
signing:templatesCreate templates, change drafts, publish, revise and discard them, and change their settings.
signing:workflowsStart a workflow for a participant.
signing:subjectsImport reservations, set guest lists, build and start packets.
signing:webhooksManage endpoints.
signing:devicesManage counter tablets: add one and get its pairing code, list, re-pair, rename, stop.
signing:applicationsRegister your own application and the public keys its approvals are signed with; revoke a key, disable an application.
signing:assertWhat your application’s server holds: hand over a person’s saved signature, sign with their approval, read the signing back. It cannot register a key.

A booking integration wants signing:subjects, not signing:write. The two are different trust levels: one is "act on my behalf", the other is "decide who my guests are".

Only from where it should come from

A key can be pinned to addresses or ranges when it is made — one per line under Only from these addresses, up to twenty, IPv4 or IPv6. Presented from anywhere else it is refused exactly as a wrong key is (401, the same sentence), so a copy that leaks from a log or a laptop does nothing. A key with no list works from anywhere. The list travels with the key: to change it, make a new key and revoke the old one, the way scopes change.

The address checked is the one the request arrives from, as our edge sees it. An integration behind an egress with a fixed address pins to that; one whose address moves should not pin at all rather than pin to a range it does not control.

Connecting through OAuth

A product that serves many businesses does not want to ask each one to paste a key. It sends the business to SignSealer once, the business agrees, and the product is handed tokens — an access token that is a scoped key for an hour, and a refresh token that gets the next one. This is OAuth 2.0 with the authorization code flow and PKCE, and nothing else: no implicit flow, no password grant, and S256 is the only challenge method accepted. Register your product with partners@signsealer.com (a name, a homepage, the exact redirect address or addresses, and the scopes it will ask for); you get a client_id and, shown once, a client_secret. A WordPress site running our plugin does not need any of this: the plugin registers a client for that one site itself, after SignSealer has fetched the plugin's own page on the site to confirm it is there (POST /oauth/register), and the site owner only presses Connect and approves. Because such a client vouched for itself -- we checked that the site runs the plugin, not who runs the site -- its consent screen says so, it may have signing:read, signing:write, signing:workflows and signing:webhooks but never signing:templates, and a request we cannot answer is explained on our page rather than sent back to the site. A person pressing decline is sent back, with error=access_denied.

An AI assistant connecting to the MCP server needs none of it either. It finds this flow from the server's metadata and identifies itself, by registering (RFC 7591, POST /oauth/register with redirect_uris) or by using the https address of its own client metadata document as its client_id. Such an app is held to signing:read and signing:write, its consent screen says SignSealer has not checked it, and it may return to the loopback (http://127.0.0.1:<port>/…) when it runs on the person's own computer.

1. Send the browser to authorize.

GET https://api.signsealer.com/oauth/authorize
    ?response_type=code
    &client_id=ss_client_…
    &redirect_uri=https://your.product/callback
    &scope=signing:read%20signing:write
    &state=…                      # yours, returned untouched
    &code_challenge=…             # base64url(sha256(code_verifier))
    &code_challenge_method=S256

The person signs in if they are not already, sees your product's registered name, homepage and the scopes asked, and agrees or declines. The redirect_uri must match one you registered exactly — not a prefix, not a pattern — and a request whose client or redirect does not check out is answered as a page, never as a redirect. Everything else that is wrong comes back to your redirect as error= with state, the way RFC 6749 says -- for a client we registered. A client a site registered for itself gets a page instead (RFC 9700 §4.11.2), and a scope it may not have is left out of the grant rather than refused. Agreement comes back as ?code=…&state=…; the code lives sixty seconds and is good once.

2. Exchange the code, server to server.

POST https://api.signsealer.com/oauth/token
Authorization: Basic base64(client_id:client_secret)
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=…
&redirect_uri=https://your.product/callback
&code_verifier=…
{
  "access_token": "mk_live_…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "signing:read signing:write",
  "refresh_token": "…"
}

The access token is a key like any other on this page — send it as Authorization: Bearer, and every scope rule above applies. The client may also be sent as client_id and client_secret in the body. Refresh with grant_type=refresh_token&refresh_token=…; each refresh token is good once and the answer carries the next. A refresh token presented twice is treated as stolen: the connection is revoked, and the reason is on the business's audit trail. An AI app that registered itself without naming refresh_token in its grant_types is given no refresh_token: when its hour is up it sends the person through /oauth/authorize again (AI assistants).

The scopes a business can grant are the ones you registered, narrowed to what the person agreeing can do themselves. The person pressing Connect has to hold the permission to manage API keys, which only owners and admins have unless it is given to somebody else (Teams, roles and sign-in). The business sees the connection under Integrations → Connected apps and can end it there at any time, with a reason on its audit trail; every token under it stops working that minute, and your next call answers 401.

The connection is also the person's who agreed to it. If they are removed from the business, leave it, or are given a role or permissions that no longer include everything the connection holds, it ends the same way, with its tokens; and a refresh asks the same question every time, so one that changed any other way ends at the next refresh. A refresh token is good once, for thirty days. A refresh that is refused answers 400 invalid_grant with a sentence saying why: send the business through /oauth/authorize again, as somebody who can still give what your app asks for.

Testing

Build against your sandbox with a test key (mk_test_). Make one in the portal under Integrations → API keys → + API key, with Test chosen. The first one makes the sandbox: a second account beside yours, with your address and a copy of your published templates and forms as they are that moment. GET /v1/account answers "mode": "test" for a test key and "live" for any other.

  • Nothing is sent to anybody. A document is prepared, sent and signed exactly as it is live, and the send's answer carries each signing link, but no email or text leaves: the sandbox is a rehearsal account, and the worker closes its messages without handing them to a provider.
  • Nothing is billed. No usage is recorded for a sandbox at all, so nothing is drawn from credit, no allowance runs out, and it can never carry a card.
  • Webhooks are delivered to the endpoints you register with the test key, signed the same way, so your handler is tested against the real thing.
  • It says so. Every signing page and public form of the sandbox opens with a line saying it is a test document and not a real agreement.
  • It is framed where you are. The sites your pages may be framed on and sent back to are your live account's, so an embedded test signing works on the site it will run on.
  • Nothing crosses over. A test key sees only the sandbox and a live key only your account. Nothing made with a test key appears in your documents, your bill or your records.

Templates changed after the sandbox was made are changed in the sandbox, with the test key, like any other. Revoke a test key, with a reason, on the same page.

Rate limits

Each key may make 300 requests a minute. Every answer to a keyed request says where it stands: X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (when the window turns over, in seconds since the epoch). Past it, 429 with Retry-After. A business holds ten live keys until it is paying and fifty from then, and how many people it may send signing links to in an hour and a day grows with how far it is trusted. Limits and paging has the rest.

Revoking

Revoking a key needs a reason, and the reason goes on the audit trail. A key revoked is a membership deactivated in the same statement — there is no window in which the key is "revoked but still working".

Ready to build? An API key takes a minute in the portal, and the free plan covers the first 10 agreements a month.

Get an API key