Sovereign e-signature and contract lifecycle for recurring services.
GewapeSign keeps documents, signer details and audit records in the customer's chosen Gewape region (Rwanda first). Two halves:
- Documents - send any PDF for signature, follow it, download the signed copy and its certificate.
- Lifecycle - your billing system tells GewapeSign what was paid and what renews; GewapeSign sends the right contract at the right time and, on an explicit decline, proposes the billing actions (void the renewal invoice, end the service at term end) for your approval. Nothing destructive runs without your approval and audit.
Conventions
- Auth:
Authorization: Bearer <key>. Keys are per organisation, created by an owner or admin in the console (Developers): gsk_test_... (test mode: documents are created but no email is ever sent) and gsk_live_.... OAuth 2.0 for add-ins is planned. - Every POST accepts
Idempotency-Key (any unique string, kept 24 h). Repeating a request with the same key returns the first result and never creates a second document. - Errors are
application/problem+json (RFC 9457). Rate limits with RateLimit-* headers are planned. - Timestamps are RFC 3339 UTC. IDs are opaque strings with a type prefix (
doc_, ctr_, whk_, cmd_). - Webhooks are signed:
GewapeSign-Signature: t=<unix>,v1=<hex HMAC-SHA256 of "<t>.<raw body>"> with your endpoint secret; reject if the signature does not match or t is older than 5 minutes. Each delivery also carries GewapeSign-Event and GewapeSign-Delivery (the event id: use it to ignore repeats). Any non-2xx answer is retried with exponential backoff (30 s, doubling, up to 6 h between attempts); after 10 attempts the delivery is marked dead and can be redelivered from the delivery log.
Base URL
https://api.gewapesign.com/api/v1
Create keys in the GewapeSign console under Developers. Use a gsk_test_ key while you build: test documents are real but no email is sent.
Documents
Send a PDF for signature and follow it.
GET/documents
List documents
Parameters
status | query · DocumentStatus | |
external_ref | query · string | Your own reference, exact match |
cursor | query · string | |
limit | query · integer | |
Responses
curl -X GET https://api.gewapesign.com/api/v1/documents \
-H "Authorization: Bearer $GEWAPESIGN_KEY"
POST/documents
Create a document (and optionally send it)
Upload a PDF (multipart: file + a JSON payload) and place each field by position: x/y are the field's top-left corner and width/height its size, all as a percentage of the page. At least one field is required. Creating from a template and text tags written in the document (e.g. {{signature:1}}) are planned.
Parameters
Idempotency-Key | header · string | |
Request · multipart/form-data
| Field | Type | Notes |
|---|
file required | string (binary) | PDF, max 20 MB |
payload required | DocumentCreate | |
payload (JSON)
| Field | Type | Notes |
|---|
title required | string | maxLength 250 |
external_ref | string | Your reference (invoice, deal, ticket); returned in webhooks maxLength 200 |
signers required | array of Signer | minItems 1, maxItems 20 |
fields required | array of Field | Where each signer signs minItems 1 |
sequential | boolean | default true |
message | string | maxLength 2000 |
send | boolean | Send immediately after creation default false |
expires_in_days | integer | minimum 1, maximum 365 |
reminders | object | |
Responses
201 Created · Document400 Error (RFC 9457) · Problem409 Error (RFC 9457) · Problem
curl -X POST https://api.gewapesign.com/api/v1/documents \
-H "Authorization: Bearer $GEWAPESIGN_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-F file=@contract.pdf \
-F 'payload={"title":"Service agreement","signers":[{"name":"A. Uwase","email":"a.uwase@example.com"}],
"fields":[{"signer":1,"type":"signature","page":1,"x":10,"y":80,"width":30,"height":6}],"send":true}'POST/documents/detect-fields
Propose signature fields for a PDF (nothing is stored)
Finds signature blocks in the PDF's text (a "Signature" cue and the blank next to it, with the "Name" and "Date" lines of the same block, per column) and matches each block to a signer: by the signer's name, then by the company in their email domain, then by elimination. Blocks the rules cannot settle are matched by our own language model, which runs in Rwanda. The document is never sent outside Gewape. Scanned PDFs without a text layer return no fields and a note. Review the proposal, then pass the fields to POST /documents.
Request · multipart/form-data
| Field | Type | Notes |
|---|
file required | string (binary) | PDF, max 20 MB |
payload required | object | |
payload (JSON)
| Field | Type | Notes |
|---|
signers required | array of Signer | minItems 1, maxItems 20 |
Responses
200 Proposed fields · object400 Error (RFC 9457) · Problem422 Error (RFC 9457) · Problem
curl -X POST https://api.gewapesign.com/api/v1/documents/detect-fields \
-H "Authorization: Bearer $GEWAPESIGN_KEY" \
-H "Content-Type: application/json" \
-d '{}'GET/documents/{id}
Get a document and its signers' status
Parameters
Responses
200 OK · Document404 Error (RFC 9457) · Problem
curl -X GET https://api.gewapesign.com/api/v1/documents/doc_... \
-H "Authorization: Bearer $GEWAPESIGN_KEY"
POST/documents/{id}/send
Send a draft document to its signers
Parameters
id required | path · string | |
Idempotency-Key | header · string | |
Responses
200 Sent · Document409 Error (RFC 9457) · Problem
curl -X POST https://api.gewapesign.com/api/v1/documents/doc_.../send \
-H "Authorization: Bearer $GEWAPESIGN_KEY"
POST/documents/{id}/remind
Remind signers who have not signed yet
Parameters
id required | path · string | |
Idempotency-Key | header · string | |
Responses
curl -X POST https://api.gewapesign.com/api/v1/documents/doc_.../remind \
-H "Authorization: Bearer $GEWAPESIGN_KEY"
POST/documents/{id}/void
Withdraw a document that is not yet completed
Parameters
id required | path · string | |
Idempotency-Key | header · string | |
Request · application/json
| Field | Type | Notes |
|---|
reason required | string | maxLength 500 |
Responses
200 Voided · Document409 Error (RFC 9457) · Problem
curl -X POST https://api.gewapesign.com/api/v1/documents/doc_.../void \
-H "Authorization: Bearer $GEWAPESIGN_KEY" \
-H "Content-Type: application/json" \
-d '{"reason": "Replaced by a new version"}'GET/documents/{id}/signed.pdf
Download the signed PDF (completed documents only)
Parameters
Responses
200 The signed PDF. `Digest` header carries its SHA-256. · string (binary)409 Error (RFC 9457) · Problem
curl -X GET https://api.gewapesign.com/api/v1/documents/doc_.../signed.pdf \
-H "Authorization: Bearer $GEWAPESIGN_KEY"
GET/documents/{id}/certificate.pdf
Download the signing certificate (who signed, when, from where, against which document hash)
Parameters
Responses
200 PDF · string (binary)
curl -X GET https://api.gewapesign.com/api/v1/documents/doc_.../certificate.pdf \
-H "Authorization: Bearer $GEWAPESIGN_KEY"
GET/documents/{id}/proof
Independent proof for the document (tamper-evident, independently verifiable)
For each fingerprint (original, and signed once completed): the salted commitment, an RFC 3161 timestamp token from the GewapeSign time-stamping authority, and the Bitcoin anchor via OpenTimestamps (pending until a block confirms it; download the proof again later). Check it without contacting Gewape at https://gewapesign.com/verify/ or with the offline verifier published there. Proves the file existed in exactly this form no later than the stated times; it does not by itself identify the signers.
Parameters
Responses
200 The proof (JSON) · object404 Error (RFC 9457) · Problem
curl -X GET https://api.gewapesign.com/api/v1/documents/doc_.../proof \
-H "Authorization: Bearer $GEWAPESIGN_KEY"
GET/documents/{id}/proof.zip
The same proof as a zip (proof.json, .tsr tokens, .ots proofs, TSA certificates) for standard tools
Parameters
Responses
200 Zip archive · string (binary)
curl -X GET https://api.gewapesign.com/api/v1/documents/doc_.../proof.zip \
-H "Authorization: Bearer $GEWAPESIGN_KEY"
GET/documents/{id}/audit
The audit trail as data (hash-chained; each entry carries the hash of the previous one)
Parameters
Responses
200 OK · array of AuditEntry
curl -X GET https://api.gewapesign.com/api/v1/documents/doc_.../audit \
-H "Authorization: Bearer $GEWAPESIGN_KEY"
Webhooks
Get notified when documents and contracts change.
GET/webhooks
List webhook endpoints
Responses
curl -X GET https://api.gewapesign.com/api/v1/webhooks \
-H "Authorization: Bearer $GEWAPESIGN_KEY"
POST/webhooks
Register a webhook endpoint (HTTPS only). The secret is returned once.
Parameters
Idempotency-Key | header · string | |
Request · application/json
| Field | Type | Notes |
|---|
url required | string (uri) | |
events required | array of EventType | minItems 1 |
Responses
201 Created · WebhookEndpoint + object
curl -X POST https://api.gewapesign.com/api/v1/webhooks \
-H "Authorization: Bearer $GEWAPESIGN_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/hooks/gewapesign", "events": ["document.completed"]}'GET/webhooks/{id}/deliveries
Delivery log for an endpoint (newest first)
Parameters
id required | path · string | |
status | query · pending | delivered | dead | |
limit | query · integer | |
Responses
200 OK · object404 Error (RFC 9457) · Problem
curl -X GET https://api.gewapesign.com/api/v1/webhooks/doc_.../deliveries \
-H "Authorization: Bearer $GEWAPESIGN_KEY"
POST/webhooks/{id}/deliveries/{delivery_id}/redeliver
Queue a delivery again (for example after fixing your endpoint)
Parameters
id required | path · string | |
delivery_id required | path · string | |
Responses
202 Queued404 Error (RFC 9457) · Problem
curl -X POST https://api.gewapesign.com/api/v1/webhooks/doc_.../deliveries/<delivery_id>/redeliver \
-H "Authorization: Bearer $GEWAPESIGN_KEY"
DELETE/webhooks/{id}
Remove a webhook endpoint
Parameters
Responses
curl -X DELETE https://api.gewapesign.com/api/v1/webhooks/doc_... \
-H "Authorization: Bearer $GEWAPESIGN_KEY"
Lifecycle
Billing events in, contracts and approved commands out.
POST/lifecycle/events
Tell GewapeSign what was paid, what renews or what ended
Send one event per billing fact. GewapeSign bundles services paid the same day for the same customer and contracting entity into one contract (configurable), sends renewal contracts before renewals, and keeps chasing unsigned contracts. A payment is never blocked by a missing signature. Duplicate event_ids are ignored (safe to retry).
Parameters
Idempotency-Key | header · string | |
Request · application/json
| Field | Type | Notes |
|---|
event_id required | string | Unique in your system; duplicates are ignored |
type required | payment.confirmed | subscription.renewal_upcoming | subscription.terminated | |
occurred_at required | string (date-time) | |
customer required | object | |
entity | string | Your contracting entity code (one contract never spans two entities) |
payment | object | |
services | array of object | |
Responses
202 Accepted · object400 Error (RFC 9457) · Problem
curl -X POST https://api.gewapesign.com/api/v1/lifecycle/events \
-H "Authorization: Bearer $GEWAPESIGN_KEY" \
-H "Content-Type: application/json" \
-d '{}'GET/lifecycle/contracts
Contracts for a customer of yours
Parameters
customer_ref required | query · string | |
status | query · ContractStatus | |
Responses
curl -X GET https://api.gewapesign.com/api/v1/lifecycle/contracts \
-H "Authorization: Bearer $GEWAPESIGN_KEY"
GET/lifecycle/contracts/{id}
One contract with its lines (services, invoices) and document
Parameters
Responses
curl -X GET https://api.gewapesign.com/api/v1/lifecycle/contracts/doc_... \
-H "Authorization: Bearer $GEWAPESIGN_KEY"
GET/lifecycle/commands
Approved actions for your billing system to carry out (pull)
Your connector polls this (or reacts to the command.ready webhook) and executes each command in your billing system, then reports the result. Commands appear here only after approval in GewapeSign, never while your organisation is in dry run, and never while the kill switch is on. Physical services (colocation) always require approval.
Parameters
Responses
curl -X GET https://api.gewapesign.com/api/v1/lifecycle/commands \
-H "Authorization: Bearer $GEWAPESIGN_KEY"
POST/lifecycle/commands/{id}/result
Report the outcome of a command (idempotent)
Parameters
id required | path · string | |
Idempotency-Key | header · string | |
Request · application/json
| Field | Type | Notes |
|---|
ok required | boolean | |
error | string | maxLength 2000 |
external_ref | string | e.g. the voided invoice id in your system |
Responses
curl -X POST https://api.gewapesign.com/api/v1/lifecycle/commands/doc_.../result \
-H "Authorization: Bearer $GEWAPESIGN_KEY" \
-H "Content-Type: application/json" \
-d '{"ok": true}'Account
GET/me
The organisation and mode this key belongs to
Responses
200 OK · Me401 Error (RFC 9457) · Problem
curl -X GET https://api.gewapesign.com/api/v1/me \
-H "Authorization: Bearer $GEWAPESIGN_KEY"
Schemas
Problem
| Field | Type | Notes |
|---|
type | string | |
title | string | |
status | integer | |
detail | string | |
request_id | string | |
Me
| Field | Type | Notes |
|---|
organisation | string | |
mode | test | live | |
region | string | |
dry_run | boolean | Lifecycle commands are recorded but never released |
Signer
| Field | Type | Notes |
|---|
name required | string | maxLength 200 |
email required | string (email) | |
role | signer | approver | viewer | cc | default "signer" |
order | integer | Signing order; equal numbers sign in parallel minimum 1 |
status | pending | sent | viewed | signed | declined | expired | |
signed_at | string | null (date-time) | |
decline_reason | string | null | |
Field
| Field | Type | Notes |
|---|
signer required | integer | Signer order this field belongs to |
type required | signature | initials | name | date | text | checkbox | |
page required | integer | minimum 1 |
x required | number | percent of page width minimum 0, maximum 100 |
y required | number | minimum 0, maximum 100 |
width required | number | minimum 0, maximum 100 |
height required | number | minimum 0, maximum 100 |
DocumentCreate
| Field | Type | Notes |
|---|
title required | string | maxLength 250 |
external_ref | string | Your reference (invoice, deal, ticket); returned in webhooks maxLength 200 |
signers required | array of Signer | minItems 1, maxItems 20 |
fields required | array of Field | Where each signer signs minItems 1 |
sequential | boolean | default true |
message | string | maxLength 2000 |
send | boolean | Send immediately after creation default false |
expires_in_days | integer | minimum 1, maximum 365 |
reminders | object | |
Document
| Field | Type | Notes |
|---|
id | string | |
title | string | |
external_ref | string | null | |
status | DocumentStatus | |
signers | array of Signer | |
original_sha256 | string | |
signed_sha256 | string | null | |
region | string | |
created_at | string (date-time) | |
completed_at | string | null (date-time) | |
Template
| Field | Type | Notes |
|---|
id | string | |
name | string | |
version | string | |
variables | array of string | |
WebhookEndpoint
| Field | Type | Notes |
|---|
id | string | |
url | string | |
events | array of EventType | |
created_at | string (date-time) | |
AuditEntry
| Field | Type | Notes |
|---|
at | string (date-time) | |
action | string | |
actor | string | |
ip | string | null | |
document_sha256 | string | null | |
prev_hash | string | |
hash | string | |
LifecycleEvent
| Field | Type | Notes |
|---|
event_id required | string | Unique in your system; duplicates are ignored |
type required | payment.confirmed | subscription.renewal_upcoming | subscription.terminated | |
occurred_at required | string (date-time) | |
customer required | object | |
entity | string | Your contracting entity code (one contract never spans two entities) |
payment | object | |
services | array of object | |
Contract
| Field | Type | Notes |
|---|
id | string | |
customer_ref | string | |
type | initial | renewal | |
status | ContractStatus | |
document_id | string | null | |
lines | array of object | |
signed_sha256 | string | null | |
Command
| Field | Type | Notes |
|---|
id | string | |
action | void_invoice | mark_non_renewing | terminate_at_term_end | reinstate | |
target | object | |
contract_id | string | |
approved_by | string | |
approved_at | string (date-time) | |