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).
| Scope | What it carries |
|---|---|
| signing:read | Read templates, documents, certificates and packets. |
| signing:write | Prepare, 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:send | Prepare and send documents, and record consent to text. Part of signing:write. A connected app’s, not a key’s. |
| signing:remind | Remind a signer who has not signed. Part of signing:write. A connected app’s, not a key’s. |
| signing:links | Hand out a fresh signing link, or one to frame. Part of signing:write. A connected app’s, not a key’s. |
| signing:void | Void a document or a packet. Part of signing:write. A connected app’s, not a key’s. |
| signing:templates | Create templates, change drafts, publish, revise and discard them, and change their settings. |
| signing:workflows | Start a workflow for a participant. |
| signing:subjects | Import reservations, set guest lists, build and start packets. |
| signing:webhooks | Manage endpoints. |
| signing:devices | Manage counter tablets: add one and get its pairing code, list, re-pair, rename, stop. |
| signing:applications | Register your own application and the public keys its approvals are signed with; revoke a key, disable an application. |
| signing:assert | What 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