The Gentleman Agreement

API reference

Getting started

The API does one thing: it puts a document in front of somebody and proves who signed it. You raise a document with one call and learn the outcome from a webhook. Everything in between — the photo ID, the camera check, the emails — happens on our side.

Gammahttps://api-gamma.thegentlemanagreement.comTest environment. Nothing here is a real signature.
Signer clienthttps://gamma.thegentlemanagreement.comWhere a signer is sent. There is no API for signing on somebody's behalf.

There is no sandbox mode on a production key. Use gamma, whose data is disposable and whose evidence bucket is not under a compliance lock.

Accounts and approval

Anyone can create an account at the console and start raising documents immediately. Nothing reaches the outside world until a person here has approved the account.

This is not a formality. An unapproved account that could email strangers would be a way to send mail from our domain, with our sending reputation, to any address it chose — a phishing kit with a signature ceremony attached. So the gate is on delivery and visibility, not on the API.

What a pending account can and cannot do

While pendingOnce approved
Create documents through the APISame
Buy credits and subscribeSame
No invitation email reaches a signerInvitations are delivered
No signed or completed PDF is emailed to anyoneBoth are delivered to every party
Signers cannot see or open the document at allEveryone in the thread can
No API key is issuedKeys can be issued and revoked

Held mail is kept, not dropped. Approval releases everything written while the account was waiting, in the order it was written, with fresh download links — a presigned URL minted a week earlier would arrive expired. The signer's first sight of the document is an ordinary invitation rather than one that never came.

A signer asking for a document from an unapproved owner is told it does not exist, in the same words an imaginary document gets. Saying "pending approval" would confirm to a stranger that this address was sent something, and by whom.

Signing up

POST /owners/signupemail, organisationName, password. The organisation name is what signers read: "X has asked you to sign".
POST /owners/verify-emailemail, code. Returns a console session.
POST /owners/loginEmail and password. An address that is not an owner fails exactly like a wrong password.
POST /owners/me/proofA photograph of a licence or a utility bill showing name and address. We read the text and keep that; the image is deleted after 30 days, like a signer's.
GET /owners/meStatus, plan, credit balance, and whether proof has been sent.
PUT /owners/mename and webhookUrl. The URL must be https: the events carry hashes of signed documents.
GET /owners/me/documentsEverything you raised, each party, and who has signed.
POST /owners/me/api-keysIssues a key, once approved. Shown once — only a hash is stored.
DELETE /owners/me/api-keys/{keyId}Revokes it immediately.
POST /owners/me/billing/checkoutStripe checkout for credits or a plan. Allowed while pending.

Authentication

Five kinds of caller, five credentials, and they are not interchangeable.

CallerCredentialReaches
You, the document ownerx-api-keyRaising documents, templates, billing
A signerAuthorization: Bearer, minted after an identity checkTheir own documents only
An account holderAuthorization: Bearer, console sessionTheir own account, keys, documents and billing
Somebody reading laterAuthorization: Bearer, reader token from a passwordDocuments they already signed — never signing
An operatorAuthorization: Bearer, operator tokenThe review queues, for accounts and for identity checks

An API key is unique to one account, is shown once when issued, and is stored as a hash — a lost key is replaced, never recovered. Revoking one takes effect on the next request.

An API key identifies an owner and nothing else. It cannot sign, cannot read a signer's other documents, and cannot see an identity photograph — those are deleted on a schedule we do not let callers change.

Raising a document

POST /documents

Send one of three sources. Exactly one.

FieldTypeMeaning
titlestringWhat the signer sees in their list and in the subject line.
signerEmailsstring[]Everyone who must sign. Each signs separately and each is verified separately.
pdfstring, base64A finished PDF. Under 4.5 MB.
markdownstringRendered by us. Simpler than shipping a PDF, and the source is kept with the evidence.
templateIdstringAn approved template, filled with values.
requiresFreshCheckbooleanIgnore any cached verification and make this signer prove identity again today.
curl -X POST https://api-gamma.thegentlemanagreement.com/documents \
  -H "x-api-key: $TGA_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "title": "Coaching agreement 2026",
    "signerEmails": ["alex@example.com"],
    "pdf": "JVBERi0xLjQKJ...",
    "requiresFreshCheck": false
  }'
{
  "documentId": "doc_9f3c...",
  "status": "open",
  "billable": true,
  "billingReason": "standard"
}

201, and the signer has an email. billable is decided at this moment and does not change afterwards: a signer already verified this calendar month costs nothing extra, whatever happens next.

Several signers

Each party signs the version the previous one produced, so the chain of sealed copies is the record of what each person actually read. There is no "send to all at once" that hides this.

Templates

A template is markdown with {{placeholders}}, versioned and approved before use. Raising from an unapproved version is refused — a document nobody signed off is not a document you want in evidence.

GET /templatesList yours.
POST /templatesCreate one.
PUT /templates/{templateId}New draft version.
POST /templates/{id}/versions/{n}/previewRender without raising anything.
POST /templates/{id}/versions/{n}/approveMake a version usable.

Webhooks

Webhooks are how you learn anything. They are queued and retried with backoff, so an endpoint that is down for an hour loses nothing.

EventWhen
document.signedA party signed. Carries both hashes.
document.completedEvery party has signed.
verification.reviewA signer's identity needs a human. Usually clears within a business day.

Every delivery carries x-tga-signature, an HMAC-SHA256 of the raw body. Verify it before acting, and compare in constant time. An unverified webhook is a stranger telling you a contract was signed.

Evidence

GET /documents/{documentId}/evidence

What a dispute actually needs, and what a caller cannot alter:

  • presentedSha256 — the bytes the signer read.
  • sealedSha256 — the bytes after their signature was written in.
  • The date they chose, and the true server time they confirmed it.
  • The identity decision behind the signature, and which kind of document was shown.

Never the photographs and never the video. Those are deleted on a schedule, which is the point: what survives is proof that a check happened, not the biometrics that made it.

GET /ops/owners/{ownerId}/evidence exports everything you own, including instructions for checking a hash without us. That export exists so leaving is possible.

Billing

You pay per distinct signer per calendar month, never per document and never per seat. A signer is never charged.

PlanPriceIncluded
Prepaid credits$1.00 per signer-monthFour documents for that signer
Small$5 / month10 signer-months
Busier$15 / month100 signer-months
Largest$25 / month1000 signer-months, fair use above 150 new verifications

A verification is cached for 36 months, so a returning signer costs us almost nothing and is not billed again in the same month. GET /billing/balance shows where you are.

Management

The management view at /ops.html is for the people running the platform. It shows revenue by month, signatures billed against total, where the money comes from, and every account with what its proof of identity said.

POST /ops/loginEmail and password. The address must be on the list the environment was deployed with, and that is checked after the password so this cannot enumerate operators.
GET /ops/reports/revenueTwelve months of revenue and signature counts, read from the billing ledger.
GET /ops/ownersAccounts waiting for review.
POST /ops/owners/{ownerId}/decisionApprove or reject. Approving releases every message held while the account waited.
POST /ops/owners/{ownerId}/blockBlock or unblock a live account.

Blocking is not deletion. It stops new mail, hides the account's documents from signers and revokes its API keys immediately. It does not touch anything already signed: a signature is evidence, and evidence does not stop being true because we fell out with the person who asked for it. Unblocking restores the account to approved; the revoked keys stay revoked, and reissuing one is a deliberate act.

Operator sign-in is an interim. The design calls for a Cognito pool with mandatory MFA and that is still the right answer; what exists today is a password held to the same rules as everybody else's, usable only from a server-side list, and claimable only with a secret held in Secrets Manager. It is written down here rather than left to be discovered.

Errors and limits

Errors are {"error": "a sentence"}. The status carries the meaning.

StatusMeans
400The request is wrong. Repeating it unchanged will not help.
401No credential, or one we do not recognise.
403A valid credential of the wrong kind.
409Already done. Signing is idempotent on x-idempotency-key.
429Rate limited. Identity calls cost us real money, so they are limited per email, per IP and per day.
502Something we depend on failed. You did nothing wrong; retry.

A request body is capped at about 4.5 MB, which is an API Gateway limit rather than a policy.

What the signer does

Nothing to install, and no account.

  1. Photograph a driver's licence or passport.
  2. A short camera check, matched against the photo on the ID.
  3. A six-digit code to their email, which is what ties the identity to the address you gave us.
  4. Read, type their name, confirm.

No camera on their computer? The page offers a QR code that carries the whole check to their phone and hands the thread back when it is done. There is no weaker path: if the identity check cannot run, no signature is collected.

After signing, a signer can set a password and come back to read what they signed. That password mints a reader token and nothing more: it opens documents already signed and is refused by the signing route. A password proves somebody knows a secret, which is exactly the evidence this product exists to replace — so signing always means an ID and a live face, every time.

Signers must be 18 or over. A parent or guardian signs for a minor, and the record says so.