Platforms and partners
A point-of-sale system, a property manager, a booking engine: your product has customers of its own, and each of them signs waivers or agreements. This page is how that works, and who pays whom.
How your customers get credentials
Every business you serve is a business in SignSealer — its
own templates, documents, evidence and team, isolated from every other by
SignSealer, not by anything your code has to get right. Our servers connect to
the database as the owner of its tables, so the isolation they keep is in the
functions they call: each checks which business the caller belongs to and
filters what it reads and writes by that business. Row level security on every
table binds anybody who reaches the tables directly through the data API.
Today each one connects the same way any
customer does: somebody at that business signs in to the portal, opens
Integrations → API keys, and creates an API key with the scopes your integration
needs (signing:read and signing:write for most). Your
product stores that key for that business and sends it as
Authorization: Bearer. Keys are scoped, revocable, and shown once;
the authentication page has the details.
Or your product asks the business once. Send the person to
/oauth/authorize, they agree on a consent screen that shows your
registered name and the scopes you asked for, and your server exchanges the
code at /oauth/token for an access token that works for an hour
and a refresh token that gets the next one. It is standard OAuth 2.0 with
PKCE; any client library speaks it, and the
authentication page has the two requests
in full. The business can end the connection under Integrations → Connected
apps whenever it likes. Nothing about the key-based path changes.
We handle the signing; you share in the revenue
The businesses you bring pay SignSealer directly, and we run everything for them — billing, support, the signing pages, the texts, the evidence. You share in what they pay, month after month, for as long as they stay. The terms are in your partner agreement; tell us about your product and we will send you yours.
- Which businesses are yours. One that signs up through your link, installs your app in its first weeks, or redeems a promotion code made for you. Once it is yours it stays yours. A business your own people own — somebody on your partner dashboard, or one of your addresses — is not one you bring, and is not counted.
- When your share starts. A person here looks at every new partner account first. Your link works from the day you enroll and businesses that sign up through it are recorded as yours; your share starts on the day we approve the account, on what they pay from then on.
- When you are paid. Each month closes into a statement that does not change afterwards: each business, what it added, and what you are owed.
- Seeing it yourself. Your partner dashboard shows your
link, your businesses and every statement, and the same figures are in the
API at
/v1/partner, read with your OAuth client's credentials — never a business's key.
Custom branding
The businesses you bring can sign on pages wearing your name, colors and logo, with Powered by SignSealer at the foot, and their mail says it was sent through you. You set it up yourself from your partner dashboard, at no charge. Each business that texts does so from a number of its own, registered in its own name (coming soon). A business that buys white label for itself shows its own brand instead.
Texting numbers for the businesses you serve
Each business that texts does so from a number of its own, applied for by its owner or an admin under the business's legal name: the carriers register the business, not you, so you cannot apply for it. What you can do is take each owner to the right page with your answers already in it, and show where each one has got to on your own screen.
- Which businesses need one.
GET /v1/accountwith each business's key or token:texting.why_notisnumberwhile it waits for its own, andtexting.applicationsays where its application has got to (status,phoneonce verified,reasonwhen the carriers asked for a change), or null when it has not applied. - A link for its owner.
POST /v1/texting/number/invitationswith what you know, in the application's own names (legal_name,ein,website, the address, the contact, thekindof number). Put theurlit returns in front of the owner. It opens, after they sign in, on that business, with your answers filled in and your app named as the one that filled them; they read each one, tick that it is true and apply. Fourteen days, used up when they apply. - Each step as it happens. Subscribe the business's
endpoint to
texting.number_updated, or to*(Webhooks).
Billing your customers yourself, by agreement
Where you would rather your customer paid you alone, a business can be billed through a partner: it pays SignSealer nothing, its billing page says who bills it, and its completed agreements roll up onto your monthly statement at a wholesale rate agreed with us. It is set up by hand, for the deal that needs it.
Either way, start with the partner form: a few questions about your product and your customers, and a quote comes back to you by email.
What stays the same
- Isolation. A key for one business reaches that business and no other, whoever holds it.
- Evidence. The certificate, the trail and the sealed PDF belong to the business that sent the document, and it can download them without you.
- Webhooks. Register your endpoint per business, or one endpoint that reads
tenant_idfrom the payload; either way the signature is per business. - The signer's experience. The signing page is served by us, in our name or with your custom branding over it; embed the link, text it, or open it on a tablet. It loads nothing from anywhere else.
The partner API
Three routes, and all three only read: your program and this month so
far, the businesses that are yours, and every month that has closed. They
answer on https://api.signsealer.com, beside the rest of the API.
Nothing you send changes what you earn; the share and the program are set
by SignSealer.
Credentials
Send your OAuth client's client_id (ss_client_…)
and client_secret (ss_secret_…) by HTTP Basic, the
same pair /oauth/token takes.
curl -u "$SIGNSEALER_CLIENT_ID:$SIGNSEALER_CLIENT_SECRET" https://api.signsealer.com/v1/partner
Your client id is on your partner dashboard, under API and app installs. The secret is shown once and kept only as a hash. A partner account set up from its quote is not sent one, so make it there: the dashboard's owner presses Make a new client secret, and only the owner can.
A new secret replaces the old one at once.
It is the same secret your app sends to /oauth/token, so give your
app the new one at the same time: until it has it, exchanging a code or
refreshing a token there fails.
The credentials work from the day you enroll, while your account is still
under review. A business's API key or access token is refused, and so are a
public client (one with no secret), a disabled client and a client that is not
enrolled as a partner. Each of them gets the same 401 unauthorised
with a WWW-Authenticate: Basic header: which part was wrong is not
the caller's to learn.
GET /v1/partner
Your program, this month so far, and the link to hand a business. The figures in these examples are made up; your share is the one on your quote.
{
"partner": "DeckPOS",
"client_id": "ss_client_0123456789abcdef0123456789abcdef",
"referral_code": "deckpos",
"share_percent": 10,
"active": true,
"custom_branding": true,
"review": "approved",
"earning": true,
"earning_since": "2026-09-02T15:04:05+00:00",
"businesses": 12,
"month_to_date": {
"month": "2026-10-01",
"businesses": 9,
"gross_cents": 43900,
"fees_cents": 1650,
"sms_cost_cents": 1000,
"revenue_cents": 41250,
"share_bp": 1000,
"share_cents": 4125
},
"signup_url": "https://app.signsealer.com/signup?partner=deckpos"
}
| Field | Means |
|---|---|
partner | The name your OAuth client is registered under. |
referral_code | The code on the end of your signup link. |
signup_url | Your signup link: https:// and your code. |
share_percent | Your share, as a percentage: 10 is 10%. |
active | false while your program is paused. No new business is made yours; the ones you have stay yours and keep earning. |
custom_branding | Whether your custom branding is on. |
review | pending, approved or declined. pending until a person here has looked at your account. |
earning | true once your account is approved. |
earning_since | When your share started counting; null until you are approved. |
businesses | How many businesses are yours, closed ones included. |
month_to_date | This calendar month so far, in UTC, worked out the way its statement will be (below). month is its first day; businesses counts the ones that added something; share_bp is your share in hundredths of a percent; share_cents is truncated to the cent. |
Nothing accrues before your account is approved, so until then every amount
in month_to_date is 0. Businesses that sign up through
your link meanwhile are still recorded as yours.
GET /v1/partner/businesses
The businesses that are yours, newest first by when they became yours.
| Query | Means |
|---|---|
limit | A whole number from 1 to 200; 50 when left out. |
offset | How many to skip: 0 or more; 0 when left out. |
{
"data": [
{
"tenant_id": "7d9c1c1e-7a57-4f55-9a86-0f7f3f9c2b11",
"name": "Harbour Rentals",
"status": "active",
"source": "referral_link",
"attributed_at": "2026-08-02T15:04:05+00:00",
"month_to_date_cents": 1840
}
],
"has_more": false
}
There is no total. Ask again with offset plus
limit until has_more is false. A business
that becomes yours while you walk the list appears at the front and moves the
rest along by one.
statusisactive,suspendedorclosed.sourceis how it became yours:referral_link(it signed up through your link),oauth_install(it installed your app in its first thirty days),promotion(it redeemed a promotion code made for you) orops(SignSealer set it).month_to_date_centsis what it has added this month after card fees and the carrier's cost of its texts. It counts from the latest of the start of the month, when the business became yours and when your account was approved, and it is0until you are approved. Credit SignSealer gave away earns nothing.
GET /v1/partner/statements
Every closed month, newest first, under data. The list is not
paged. A month closes on the first run of our sweep after midnight UTC on the
1st, and only for an approved account, so there are none while yours is under
review. A statement is written once: afterwards only paid and
paid_at change, when SignSealer records the payment.
| Field | Means |
|---|---|
month | YYYY-MM. |
businesses | How many businesses added something that month: one entry each in lines. |
gross_cents | What they paid for and used. |
fees_cents | The card fees on that, in proportion. |
sms_cost_cents | The carrier's cost of their texts, in proportion. |
revenue_cents | gross_cents less the fees and the texts' cost: what your share is of. |
share_percent | Your share at the rate in force when the month closed. |
share_cents | That share of revenue_cents, truncated to the cent once, on the month's total. |
carried_in_cents | A shortfall from your previous statement: 0 or negative. |
payable_cents | What you are owed for the month: 0 or more. |
carried_out_cents | A shortfall carried into the next month: 0 or negative. |
paid, paid_at | Whether SignSealer has recorded the payment, and when. |
lines | One per business that added something: tenant_id, name, gross_cents, fees_cents, sms_cost_cents and revenue_cents. A refund can make a line negative, and it is kept. |
The sums always meet: payable_cents plus carried_out_cents
is share_cents plus carried_in_cents. A month that comes
to less than nothing is carried into the next rather than invoiced.
Errors
The body is the API's usual { "error": { "code": …, "message": … } },
and every answer says Cache-Control: no-store.
| Status | Code | When |
|---|---|---|
| 401 | unauthorised | No Basic credentials, or ones that do not open a partner account. |
| 400 | invalid_limit | limit on / is not a whole number from 1 to 200. |
| 400 | invalid_offset | offset is not a whole number, 0 or more. |
| 404 | not_found | A path under /v1/partner that is not one of the three. |
| 405 | method_not_allowed | Anything but GET (or HEAD), with Allow: GET. |
From the Node client
SignSealerPartner in @signsealer/sdk,
from version 1.8.0, which reached npm in 1.10.0. It takes your client id and secret and refuses a
business's API key (anything starting mk_) when it is built. Every
call only reads, so a network failure, a 429 or a 5xx
is tried again as it is, up to maxAttempts (3, the first included);
anything else throws a SignSealerError at once. Keep the secret on
a server.
import { SignSealerPartner } from "@signsealer/sdk";
const partner = new SignSealerPartner({
clientId: process.env.SIGNSEALER_CLIENT_ID!,
clientSecret: process.env.SIGNSEALER_CLIENT_SECRET!,
});
const me = await partner.profile(); // GET /v1/partner
if (!me.earning) console.log("under review:", me.review);
for (let offset = 0; ; offset += 200) { // GET /v1/partner/businesses
const page = await partner.businesses({ limit: 200, offset });
for (const b of page.data) console.log(b.name, b.source, b.month_to_date_cents);
if (!page.has_more) break;
}
const months = await partner.statements(); // GET /v1/partner/statements: the data array
SignSealerPartner.signupUrl(me.referral_code); // me.signup_url, built without a call
In the OpenAPI description
The three routes are in /openapi.json under the
Partners tag, with a security scheme of their own,
partnerAuth (HTTP Basic), in place of the API key the document
declares by default. The API reference draws them
from the same declarations.
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