Section 1
Getting Started
Platform Overview
CT Signature is a multi-tenant, API-first document signing platform. It lets you send PDFs for legally binding electronic signatures, track signer progress, and store signed documents with a full audit trail.
Key capabilities:
- Single and multi-signer documents — sequential or parallel signing order
- Reusable templates — pre-place fields once, send to many recipients
- Embedded signing — host the signing experience inside your own app via iframe
- Webhooks — get notified in real time when documents are viewed, signed, or completed
- ESIGN/UETA compliance — consent capture, OTP verification, device fingerprinting, audit log, and certificate of completion
- White-label branding — custom logo, colors, and brand name on signing pages
- Address book — save frequent signers for quick reuse
Base URLs & Environments
| Environment | Base URL | Notes |
|---|---|---|
| Production | https://api.yourdomain.com |
Set in DocumentSigning:ProductionBaseUrl |
| Development | http://localhost:8080 |
Test mode enabled, email sending skipped |
All API paths in this manual are relative to the base URL.
Quick Start (5 Minutes)
Follow these steps to send your first document for signing:
POST /api/auth/register
Content-Type: application/json
{
"companyName": "Acme Corp",
"name": "Jane Developer",
"email": "jane@acme.com",
"password": "SecureP@ss123"
}
Save the token from the response. This is your JWT for the dashboard.
POST /api/dashboard/api-keys Authorization: Bearer <jwt-token>
Save the full API key returned. It is only shown once. The key looks like: ctds_aBcDeFgHiJkLm...
POST /api/v1/documents Authorization: Bearer ctds_aBcDeFgHiJkLm... Content-Type: multipart/form-data RecipientName: John Smith RecipientEmail: john@example.com File: contract.pdf
You get back a placementUrl (to position signature fields) and a signingUrl (to send to the signer).
Open the placementUrl in a browser. Drag and drop the signature, printed name, date, and initials fields onto the document. Click Save & Send.
The signer receives an email with a link (or you send them the signingUrl directly). They review the document, give consent, type their signature, and submit.
GET /api/v1/documents/{documentId}
Authorization: Bearer ctds_aBcDeFgHiJkLm...
The response includes signedPdfUrl. The signed PDF has a Certificate of Completion appended.
Section 2
Authentication
CT Signature supports four types of authentication, each for a different purpose.
API Keys (Programmatic Access)
API keys are the primary way to call the REST API from your backend code. Each key is tied to a single tenant and gives full read/write access to that tenant's data.
Key Format
Keys follow the pattern ctds_<random-characters>. Example:
ctds_aB3cDeFgH1iJkLmN2oPqRsT3uVwXy
How to Send
Include the key in the Authorization header using the Bearer scheme:
Authorization: Bearer ctds_aB3cDeFgH1iJkLmN2oPqRsT3uVwXy
Generating a Key
POST /api/dashboard/api-keys
Authorization: Bearer <jwt-token>
Content-Type: application/json
{
"name": "Production Server" // optional label
}
// Response
{
"id": 1,
"key": "ctds_aB3cDeFgH1iJkLmN2oPqRsT3uVwXy", // shown only once!
"prefix": "ctds_aB3",
"name": "Production Server",
"createdDate": "2026-04-17T10:00:00Z"
}
Revoking a Key
DELETE /api/dashboard/api-keys/{keyId}
Authorization: Bearer <jwt-token>
Endpoints That Accept API Keys
| Path Prefix | Description |
|---|---|
/api/v1/documents | Create, list, get, delete documents |
/api/v1/templates | Create, send, batch-send templates |
/api/v1/recipients | Manage address book |
/api/v1/webhooks | Manage webhooks |
JWT Bearer Tokens (Dashboard & User Sessions)
JWT tokens are used by the web dashboard and by applications that need user-level authentication (for example, if your app lets users log in to manage their own signing settings).
Getting a Token
Register a New Account
POST /api/auth/register
Content-Type: application/json
{
"companyName": "Acme Corp",
"name": "Jane Developer",
"email": "jane@acme.com",
"password": "SecureP@ss123"
}
// Response
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"tenant": {
"id": 1,
"companyName": "Acme Corp",
"email": "jane@acme.com",
"isTrial": true
},
"user": {
"id": 1,
"name": "Jane Developer",
"role": "Admin"
}
}
Log In to an Existing Account
POST /api/auth/login
Content-Type: application/json
{
"email": "jane@acme.com",
"password": "SecureP@ss123"
}
// Response — same shape as register
How to Send
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Token Details
| Property | Value |
|---|---|
| Algorithm | HMAC-SHA256 |
| Lifetime | 24 hours (configurable) |
| Issuer | Jwt:Issuer setting |
| Audience | Jwt:Audience setting |
JWT Claims
| Claim | Description |
|---|---|
sub | Tenant ID (number as string) |
user_id | Tenant user ID (if multi-user tenant) |
role | User role: Admin, User, or Reviewer |
email | User's email address |
iat | Issued-at timestamp |
exp | Expiration timestamp |
Check Current User
GET /api/auth/me
Authorization: Bearer <jwt-token>
// Response
{
"tenant": { "id": 1, "companyName": "Acme Corp", ... },
"user": { "id": 1, "name": "Jane Developer", "role": "Admin" }
}
Platform Admin Tokens
Platform admin tokens give access to system-wide management endpoints. These credentials are set in the server configuration, not in the database.
POST /api/admin/auth/login
Content-Type: application/json
{
"email": "<PlatformAdmin:Email from config>",
"password": "<PlatformAdmin:Password from config>"
}
// Response
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"email": "admin@ctsignature.com",
"role": "platform_admin"
}
Use this token with /api/admin/* endpoints only.
Signing & Placement Tokens
These are single-use, document-specific tokens embedded in URLs. They do not require any auth header — the token in the URL is the authentication.
| Token Type | Used For | URL Pattern | Expires |
|---|---|---|---|
| Placement Token | Positioning signature fields on the PDF | /sign/place/{token} |
Same as document expiration (default 72 hours) |
| Secure Token (Signing) | Signing the document | /sign/document/{token} |
Same as document expiration (default 72 hours) |
For multi-signer documents, each signer gets their own unique secure token and signing URL.
Section 3
Documents API
The Documents API lets you create, track, and manage documents for signing. All endpoints below use API key authentication unless noted otherwise.
Create a Document (Single Signer)
Upload a PDF and specify who should sign it.
Request
Content-Type: multipart/form-data
| Field | Type | Required | Description |
|---|---|---|---|
File | file | Yes | PDF file (max 10 MB) |
RecipientName | string | Yes | Signer's full name |
RecipientEmail | string | Yes | Signer's email address |
ExpirationHours | integer | No | Hours until signing link expires (default: 72) |
Example
curl -X POST https://api.example.com/api/v1/documents \ -H "Authorization: Bearer ctds_yourApiKey" \ -F "RecipientName=John Smith" \ -F "RecipientEmail=john@example.com" \ -F "ExpirationHours=48" \ -F "File=@contract.pdf"
Response (201 Created)
{
"documentId": 42,
"placementUrl": "https://api.example.com/sign/place/abc123...",
"signingUrl": "https://api.example.com/sign/document/xyz789...",
"placementToken": "abc123...",
"secureToken": "xyz789...",
"expirationDate": "2026-04-19T10:00:00Z"
}
placementUrl to position signature fields on the PDF. Once placed, the signing link becomes active and the signer receives an email.
Create a Multi-Signer Document
Create a document that requires two or more signers.
Request
Content-Type: multipart/form-data
| Field | Type | Required | Description |
|---|---|---|---|
File | file | Yes | PDF file (max 10 MB) |
Signers | JSON array | Yes | Array of signer objects (see below) |
WorkflowType | string | Yes | sequential or parallel |
ExpirationHours | integer | No | Hours until expiration (default: 72) |
Signer Object
| Field | Type | Required | Description |
|---|---|---|---|
SignerName | string | Yes | Signer's full name |
SignerEmail | string | Yes | Signer's email |
SignerRole | string | No | Role label (e.g., "Manager", "Legal") |
SignOrder | integer | No | Signing order (for sequential workflow) |
Example
curl -X POST https://api.example.com/api/v1/documents/multi-signer \
-H "Authorization: Bearer ctds_yourApiKey" \
-F "WorkflowType=sequential" \
-F 'Signers=[{"SignerName":"Alice","SignerEmail":"alice@acme.com","SignOrder":1},{"SignerName":"Bob","SignerEmail":"bob@acme.com","SignOrder":2}]' \
-F "File=@agreement.pdf"
Response (201 Created)
{
"documentId": 43,
"workflowType": "sequential",
"placementUrl": "https://api.example.com/sign/place/abc...",
"signers": [
{
"signerId": 1,
"signerName": "Alice",
"signerEmail": "alice@acme.com",
"signOrder": 1,
"status": "pending",
"signingUrl": "https://api.example.com/sign/document/token_alice..."
},
{
"signerId": 2,
"signerName": "Bob",
"signerEmail": "bob@acme.com",
"signOrder": 2,
"status": "pending",
"signingUrl": "https://api.example.com/sign/document/token_bob..."
}
]
}
Parallel: all signers can sign at the same time. Everyone gets their link immediately.
List Documents
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
status | string | (all) | Filter: pending, sent, signed, expired |
page | integer | 1 | Page number |
pageSize | integer | 20 | Results per page (1–100) |
Response (200)
{
"items": [
{
"documentId": 42,
"originalFilename": "contract.pdf",
"recipientName": "John Smith",
"recipientEmail": "john@example.com",
"status": "signed",
"createdDate": "2026-04-17T10:00:00Z",
"signedDate": "2026-04-17T14:30:00Z"
}
],
"page": 1,
"pageSize": 20,
"totalCount": 1,
"totalPages": 1
}
Get Document Details
Returns full details including field placements, signer information, and audit signatures.
Response (200)
{
"documentId": 42,
"publicId": "550e8400-e29b-41d4-a716-446655440000",
"originalFilename": "contract.pdf",
"recipientName": "John Smith",
"recipientEmail": "john@example.com",
"status": "signed",
"workflowType": "single",
"expirationDate": "2026-04-19T10:00:00Z",
"createdDate": "2026-04-17T10:00:00Z",
"signedDate": "2026-04-17T14:30:00Z",
"signedPdfHash": "a1b2c3d4e5f6...",
"fieldPlacements": {
"signatureField": { "x": 100, "y": 200, "width": 200, "height": 50, "page": 1 },
"signerNameField": { "x": 100, "y": 260, "width": 200, "height": 30, "page": 1 },
"dateTimeField": { "x": 100, "y": 300, "width": 150, "height": 25, "page": 1 }
},
"initialsFields": [
{ "id": 1, "x": 450, "y": 700, "width": 60, "height": 30, "page": 2, "isRequired": true }
],
"signatures": [
{
"signatureText": "John Smith",
"signatureFont": "Dancing Script",
"ipAddress": "203.0.113.42",
"platform": "Windows",
"timezone": "America/New_York",
"createdDate": "2026-04-17T14:30:00Z"
}
]
}
Check Document Status
A lightweight endpoint to check the current status of a document and its signers.
Document Status Values
| Status | Meaning |
|---|---|
pending | Created but fields not yet placed |
sent | Fields placed, signing link is active |
partially_signed | Some (but not all) signers have signed (multi-signer only) |
signed | All signers have signed |
expired | Signing link expired before completion |
Delete a Document
Delete a pending document. Signed documents cannot be deleted through the API.
Download Signed PDF
Downloads the fully signed PDF with the Certificate of Completion appended. The download is logged in the audit trail.
From the dashboard, use:
Verify a Signed Document
CT Signature provides a public verification system. Anyone with the document's public ID can verify its authenticity.
Look Up Verification Info
// Response
{
"documentId": "550e8400-e29b-41d4-a716-446655440000",
"status": "signed",
"signedPdfHash": "a1b2c3d4e5f6...",
"signers": [
{ "name": "John Smith", "signedDate": "2026-04-17T14:30:00Z" }
]
}
Verify a File's Hash
Compute the SHA-256 hash of your PDF file and submit it to check whether it matches the original signed document.
POST /api/documents/verify/550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json
{
"hash": "a1b2c3d4e5f6..."
}
// Response
{
"match": true,
"documentId": "550e8400-e29b-41d4-a716-446655440000",
"completedAt": "2026-04-17T14:30:00Z"
}
Embedded Signing Sessions
If your subscription includes embedded signing, you can host the signing experience inside your app using an iframe.
Step 1: Enable Embedded Signing
PUT /api/dashboard/embedded-signing
Authorization: Bearer <jwt-token>
Content-Type: application/json
{
"enabled": true,
"allowedDomains": ["https://app.yourdomain.com"]
}
Step 2: Create an Embedded Session
// Response
{
"signingUrl": "https://api.example.com/sign/document/xyz...?embed=true",
"expiresAt": "2026-04-17T11:00:00Z"
}
Step 3: Embed in Your Page
<iframe
src="https://api.example.com/sign/document/xyz...?embed=true"
width="100%"
height="800"
frameborder="0"
></iframe>
<script>
window.addEventListener('message', function(event) {
// Verify origin matches your CT Signature domain
if (event.origin !== 'https://api.example.com') return;
if (event.data.type === 'signing-complete') {
console.log('Document signed!', event.data.documentId);
// Redirect user, show confirmation, etc.
}
});
</script>
Section 4
Templates API
Templates let you upload a PDF once, position the signature fields, and then reuse it for many recipients without re-uploading or re-placing fields each time.
Create a Template
| Field | Type | Required | Description |
|---|---|---|---|
File | file | Yes | PDF file |
Name | string | Yes | Template name (e.g., "Employee NDA") |
Description | string | No | Optional description |
You can also create a template from an existing signed document:
This copies the original (unsigned) PDF and all field placements into a new template.
Other Template Endpoints
| Method | Path | Description |
|---|---|---|
GET | /api/v1/templates | List all active templates |
GET | /api/v1/templates/{id} | Get template details with field coordinates |
PUT | /api/v1/templates/{id} | Update name or description |
DELETE | /api/v1/templates/{id} | Deactivate template (soft delete) |
Send from a Template
Creates a new document from the template with pre-placed fields and sends the signing link directly. No placement step needed.
POST /api/v1/templates/5/send
Authorization: Bearer ctds_yourApiKey
Content-Type: application/json
{
"recipientName": "Sarah Johnson",
"recipientEmail": "sarah@example.com",
"expirationHours": 48
}
// Response
{
"documentId": 44,
"signingUrl": "https://api.example.com/sign/document/xyz...",
"templateId": 5,
"skippedPlacement": true
}
Batch Send
Send a template to up to 100 recipients in a single call. One document is created per recipient.
POST /api/v1/templates/5/batch-send
Authorization: Bearer ctds_yourApiKey
Content-Type: application/json
{
"recipients": [
{ "name": "Alice Brown", "email": "alice@acme.com" },
{ "name": "Bob Green", "email": "bob@acme.com" },
{ "name": "Carol White", "email": "carol@acme.com" }
]
}
// Response
{
"templateId": 5,
"totalSent": 3,
"documents": [
{ "documentId": 45, "recipientEmail": "alice@acme.com", "signingUrl": "..." },
{ "documentId": 46, "recipientEmail": "bob@acme.com", "signingUrl": "..." },
{ "documentId": 47, "recipientEmail": "carol@acme.com", "signingUrl": "..." }
]
}
Section 5
Recipients API (Address Book)
Save frequently-used signers so you don't have to re-enter their info each time. Recipients are automatically added when you create multi-signer documents from the dashboard.
| Method | Path | Description |
|---|---|---|
GET | /api/v1/recipients | List recipients (supports ?search= and ?includeInactive=true) |
GET | /api/v1/recipients/{id} | Get recipient details |
POST | /api/v1/recipients | Create recipient |
PUT | /api/v1/recipients/{id} | Update recipient |
DELETE | /api/v1/recipients/{id} | Deactivate recipient |
POST | /api/v1/recipients/{id}/reactivate | Reactivate a deactivated recipient |
Create Recipient
POST /api/v1/recipients
Authorization: Bearer ctds_yourApiKey
Content-Type: application/json
{
"name": "John Smith",
"email": "john@example.com",
"company": "Example Inc",
"role": "VP of Sales",
"phone": "+1-555-123-4567",
"notes": "Prefers signing on mobile"
}
// Response
{
"id": 1,
"name": "John Smith",
"email": "john@example.com",
"company": "Example Inc",
"role": "VP of Sales",
"phone": "+1-555-123-4567",
"notes": "Prefers signing on mobile",
"isActive": true,
"createdDate": "2026-04-17T10:00:00Z"
}
Section 6
Webhooks
Webhooks let your application receive real-time notifications when events happen in CT Signature — like when a document is signed, viewed, or completed.
Setting Up Webhooks
POST /api/v1/webhooks
Authorization: Bearer ctds_yourApiKey
Content-Type: application/json
{
"url": "https://yourapp.com/webhooks/ctsignature",
"events": ["document.completed", "signer.signed"],
"description": "Production webhook"
}
// Response
{
"id": 1,
"url": "https://yourapp.com/webhooks/ctsignature",
"events": ["document.completed", "signer.signed"],
"secret": "a1b2c3d4e5f6...64-hex-chars...", // shown only once!
"isActive": true,
"createdDate": "2026-04-17T10:00:00Z"
}
secret is only returned when the webhook is created. Store it securely — you need it to verify incoming webhook payloads. If you lose it, delete the webhook and create a new one.
In production, the URL must use HTTPS.
Event Types
Subscribe to any combination of these events. If you don't specify events, you receive all of them.
| Event | Fires When |
|---|---|
document.created | A new document is uploaded |
document.sent | Signature fields are placed, signing link becomes active |
document.placed | Signature fields are positioned on the PDF |
signer.viewed | A signer opens the signing page |
signer.consent-given | A signer accepts the disclosure |
otp.sent | An OTP code is emailed to the signer |
otp.verified | The signer enters a correct OTP code |
signer.signed | A signer submits their signature |
document.completed | All signers have signed the document |
signer.failed | Signing failed (e.g., too many OTP attempts) |
email.delivered | Signing email was delivered to the signer |
email.bounced | Email bounced |
email.failed | Email was marked as spam |
email.opened | Signer opened the email |
Payload Format
Webhooks are sent as HTTP POST requests with a JSON body:
{
"id": "whd_123",
"timestamp": "2026-04-17T14:30:00Z",
"event_type": "signer.signed",
"data": {
"documentId": 42,
"publicId": "550e8400-e29b-41d4-a716-446655440000",
"filename": "contract.pdf",
"signerName": "John Smith",
"signerEmail": "john@example.com",
"signedAt": "2026-04-17T14:30:00Z"
}
}
Headers Sent
| Header | Description |
|---|---|
Content-Type | application/json |
X-Signature | sha256=<hex-encoded HMAC-SHA256> |
User-Agent | CTSignature-Webhook/1.0 |
Verifying Webhook Signatures
Always verify the X-Signature header to make sure the request actually came from CT Signature.
How it works:
- Read the raw request body (as bytes or a UTF-8 string)
- Compute HMAC-SHA256 of the body using your webhook secret
- Compare your computed signature to the
X-Signatureheader value
Node.js Example
const crypto = require('crypto');
function verifyWebhook(req, secret) {
const payload = JSON.stringify(req.body);
const signature = req.headers['x-signature'];
const expected = 'sha256=' +
crypto.createHmac('sha256', Buffer.from(secret, 'hex'))
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
Python Example
import hmac, hashlib
def verify_webhook(body: bytes, secret: str, signature: str) -> bool:
expected = 'sha256=' + hmac.new(
bytes.fromhex(secret),
body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
C# Example
using System.Security.Cryptography;
bool VerifyWebhook(string payload, string secret, string signature)
{
var key = Convert.FromHexString(secret);
using var hmac = new HMACSHA256(key);
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload));
var expected = "sha256=" + Convert.ToHexString(hash).ToLower();
return CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(expected),
Encoding.UTF8.GetBytes(signature)
);
}
Retries & Deliveries
If your endpoint returns a non-2xx status code (or times out after 10 seconds), CT Signature retries delivery with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | ~1 minute |
| 3 | ~5 minutes |
| 4 | ~30 minutes |
| 5 | ~2 hours |
After 5 failed attempts, the delivery is marked as failed. You can view delivery history:
To test your webhook endpoint without creating a real document:
Section 7
Signing Workflow
Single-Signer Flow
This is the complete lifecycle of a single-signer document:
pending. You receive a placementUrl and signingUrl.
sent. An email is sent to the signer automatically.
signed.
document.completed webhook. The signed PDF is available for download.
Multi-Signer Flow
Sequential Workflow
- Create multi-signer document with
workflowType: "sequential" - Place fields for all signers on the placement page
- Signer 1 receives an email. They sign.
- Only after Signer 1 completes does Signer 2 receive their email
- This continues through all signers in order
- When the last signer signs, the document status becomes
signed
Parallel Workflow
- Create multi-signer document with
workflowType: "parallel" - Place fields for all signers
- All signers receive emails at the same time
- Signers can sign in any order
- When the last remaining signer completes, the document is fully signed
Consent & OTP Verification
These features are configured per-tenant in the dashboard settings.
Consent Disclosure
When enabled, signers must accept a legal disclosure before they can sign. This is required for ESIGN Act compliance.
- Modal mode: The disclosure appears as a popup on the signing page
- Page mode: The signer is taken to a full-page disclosure before seeing the document
You can publish custom disclosure text. Each change creates a new version so you have a record of which version each signer accepted.
OTP (One-Time Password)
When enabled, signers must verify their identity by entering a 6-digit code sent to their email before they can sign. This adds an extra layer of identity verification.
- OTP codes expire after 10 minutes
- Maximum 5 verification attempts per code
- Each new code invalidates any previous unverified codes
Section 8
Billing & Subscriptions
Free Trial
New accounts start with a free trial that includes 3 documents. All features are available during the trial.
Subscription Tiers
Returns available subscription plans with pricing and included features.
// Response
[
{
"id": 1,
"name": "Starter",
"monthlyPriceCents": 2999,
"includedDocuments": 50,
"overagePriceCents": 150,
"featureFlags": { "webhooks": true, "templates": true }
},
{
"id": 2,
"name": "Professional",
"monthlyPriceCents": 7999,
"includedDocuments": 200,
"overagePriceCents": 100,
"featureFlags": { "webhooks": true, "templates": true, "embedding": true }
}
]
Check Usage
// Response
{
"documentsUsedThisCycle": 37,
"includedDocuments": 50,
"trialRemaining": 0,
"billingPeriodStart": "2026-04-01T00:00:00Z",
"billingPeriodEnd": "2026-04-30T23:59:59Z"
}
Upgrade / Manage Subscription
To start or change a subscription:
POST /api/billing/checkout
Authorization: Bearer <jwt-token>
Content-Type: application/json
{ "tierId": 2 }
// Response
{ "checkoutUrl": "https://checkout.stripe.com/pay/cs_..." }
Redirect the user to checkoutUrl to complete payment on Stripe.
To manage payment methods, view invoices, or cancel:
Returns a Stripe Customer Portal URL.
Section 9
Dashboard Guide
The web dashboard gives tenant admins and users a visual interface for managing documents, templates, users, settings, and billing.
Documents
The Documents section lets you:
- Create documents — upload a PDF or select a template, enter recipient info
- Create multi-signer documents — add multiple signers with roles and ordering
- Track status — see pending, sent, signed, and expired documents
- Resend emails — re-send signing request emails to recipients
- Download signed PDFs — download the completed document with certificate
- View audit trail — see who signed, when, from where, and with what device
Dashboard Document Creation Endpoint
Same as the API v1 endpoint, but also supports creating documents from templates (when a template has pre-placed fields, it skips the placement step and sends immediately).
Stats Overview
Returns real-time document counts by status and trial information.
Templates
Templates are managed through the same API endpoints described in Section 4. From the dashboard, you can:
- Upload new templates
- Create templates from previously signed documents
- Send individual or batch documents from templates
- Edit template names and descriptions
- Deactivate templates you no longer need
User Management
Tenant admins can invite team members and control their access.
User Roles
| Role | Permissions |
|---|---|
Admin | Full access: documents, templates, settings, billing, users, webhooks, API keys |
User | Create and manage documents and templates. Cannot change settings, billing, or users. |
Reviewer | View documents only. Cannot create or modify. |
User Management Endpoints
| Method | Path | Description |
|---|---|---|
GET | /api/dashboard/users | List all users in the tenant |
POST | /api/dashboard/users | Invite a new user (by email) |
PUT | /api/dashboard/users/{id}/role | Change a user's role |
POST | /api/dashboard/users/{id}/deactivate | Disable a user's access |
POST | /api/dashboard/users/{id}/activate | Re-enable a disabled user |
POST | /api/dashboard/users/{id}/reset-password | Force a password reset |
Settings & Branding
Account Settings
Update company name, contact email, or password.
Consent Settings
Manage consent disclosure flow type (modal or page), publish custom disclosure text, and enable/disable OTP verification.
Branding
| Method | Path | Description |
|---|---|---|
GET | /api/dashboard/branding | Get current branding settings |
PUT | /api/dashboard/branding | Update colors and brand name |
POST | /api/dashboard/branding/logo | Upload logo (PNG, JPG, SVG; max 2 MB) |
DELETE | /api/dashboard/branding/logo | Remove logo |
Document Retention
Set how long signed documents are kept before automatic deletion. Range: 30 to 36,500 days (default: 2,555 days / ~7 years).
Embedded Signing Settings
Enable or disable iframe-based embedded signing and set allowed domains.
Analytics
Returns daily document creation and signing counts for charting. Supports 7, 30, 60, and 90 day windows.
Returns aggregate statistics: total documents, completion rate, average time to sign, and template usage count.
Section 10
Configuration Reference
CT Signature is configured through appsettings.json (and environment-specific overrides). Below are all the settings you need to know.
Database
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=ctDocSign;User=ctdocsign;Password=YOUR_PASSWORD;Port=3306;"
}
Requires MySQL 8.0 or later. Entity Framework Core handles migrations automatically.
JWT Settings
| Setting | Default | Description |
|---|---|---|
Jwt:Secret | — | Signing key for JWT tokens. Must be at least 32 characters. Change this in production! |
Jwt:Issuer | ctDocSign | Issuer claim in JWT tokens |
Jwt:Audience | ctDocSign-dashboard | Audience claim in JWT tokens |
Jwt:ExpirationHours | 24 | How long JWT tokens are valid |
Stripe Settings
| Setting | Description |
|---|---|
Stripe:SecretKey | Stripe API secret key (sk_live_... or sk_test_...) |
Stripe:PublishableKey | Stripe publishable key (for frontend) |
Stripe:WebhookSecret | Stripe webhook endpoint signing secret (whsec_...) |
Platform Admin
| Setting | Description |
|---|---|
PlatformAdmin:Email | Email for platform admin login |
PlatformAdmin:Password | Password for platform admin login |
PlatformAdmin:Secret | Secret for admin endpoint validation |
Document Storage & Security
| Setting | Default | Description |
|---|---|---|
DocumentSigning:Storage:BasePath | ./Documents/Signing | Root directory for stored PDFs |
DocumentSigning:Storage:MaxFileSize | 10485760 | Max upload size in bytes (10 MB) |
DocumentSigning:Security:TokenExpirationHours | 72 | Signing link lifetime |
DocumentSigning:Security:AllowedOrigins | [] | CORS allowed origins |
DocumentSigning:ProductionBaseUrl | — | Base URL for signing links in emails |
Testing & Development
| Setting | Default | Description |
|---|---|---|
DocumentSigning:Testing:EnableTestMode | true | Enables the /api/test/ endpoints |
DocumentSigning:Testing:SkipEmailSending | true | Skips actual email delivery in dev |
DocumentSigning:Testing:LocalhostBaseUrl | http://localhost:8080 | Base URL used in dev mode |
Email Providers
Set one of these environment variables to enable email delivery:
| Variable | Provider |
|---|---|
POSTMARK_API_TOKEN | Postmark (recommended for delivery tracking) |
RESEND_API_TOKEN | Resend (alternative provider) |
Rate Limits
| Scope | Limit |
|---|---|
| General API | 60 requests/minute |
| Signature submission | 10 requests/minute |
| Test endpoint | 5 requests/minute |
| Tenant API (per key) | 100 requests/minute |
Section 11
Error Handling
All errors return a JSON object with an error field containing a human-readable message.
// Example error response
{
"error": "Document not found"
}
HTTP Status Codes
| Code | Meaning | Common Causes |
|---|---|---|
200 | OK | Request succeeded |
201 | Created | Resource created successfully |
400 | Bad Request | Missing required field, invalid file format, validation error |
401 | Unauthorized | Missing or invalid API key / JWT token |
402 | Payment Required | Billing quota exceeded (trial or subscription limit reached) |
403 | Forbidden | Valid auth but not allowed (wrong tenant, non-admin user) |
404 | Not Found | Document, template, or recipient doesn't exist |
409 | Conflict | Email already registered, duplicate recipient |
429 | Too Many Requests | Rate limit exceeded — slow down and retry |
500 | Internal Server Error | Server-side error (these are logged and monitored) |
Common Error Scenarios
Authentication Errors
// Missing API key 401: { "error": "Authorization header is required" } // Invalid API key 401: { "error": "Invalid API key" } // Expired JWT 401: { "error": "Token has expired" }
Document Errors
// File too large 400: { "error": "File size exceeds the maximum allowed (10 MB)" } // Not a PDF 400: { "error": "Only PDF files are accepted" } // Trying to delete a signed document 400: { "error": "Signed documents cannot be deleted" } // Trial expired 402: { "error": "Trial document limit reached. Please upgrade." }
Signing Errors
// Token expired 400: { "error": "This signing link has expired" } // Consent not given 400: { "error": "Consent must be given before signing" } // OTP not verified 400: { "error": "OTP verification is required before signing" } // Sequential order violation 400: { "error": "Previous signer has not yet signed" } // Too many OTP attempts 400: { "error": "TooManyAttempts" }
Section 12
Security & Compliance
Data Security
- Passwords — hashed with BCrypt (tenant passwords, user passwords, API keys)
- Tokens — compared using timing-safe operations to prevent timing attacks
- File storage — path traversal protection on all file access
- Tenant isolation — every query is scoped to the authenticated tenant; no cross-tenant data access is possible
- Rate limiting — prevents brute-force and abuse
ESIGN Act & UETA Compliance
CT Signature includes the following features to support ESIGN Act and UETA compliance:
| Requirement | How CT Signature Meets It |
|---|---|
| Consent to use electronic signatures | Configurable consent disclosure (modal or full-page) with versioned history. Each signer's consent is timestamped and IP-logged. |
| Intent to sign | Explicit "I intend to sign" confirmation checkbox. Timestamp recorded as IntentToSignAt. |
| Signer identity | Email-based identification. Optional OTP verification for additional identity assurance. Device fingerprinting (canvas, user agent, IP, geolocation). |
| Record retention | Configurable retention period (default 7 years). Signed PDFs include SHA-256 hash for integrity verification. Certificate of Completion documents the full signing process. |
| Delivery evidence | Postmark integration tracks email delivery, bounces, opens, and spam complaints. All events logged in the audit trail. |
| Audit trail | Every action is logged: document creation, field placement, signer views, consent, OTP, signature submission, email events. All entries include actor, timestamp, IP, and metadata. |
Certificate of Completion
Every signed document has a Certificate of Completion automatically appended as the last page. It includes:
- Document public ID (for verification lookup)
- Original filename
- List of all signers with their signed dates
- SHA-256 hash of the signed document
- Audit trail summary
Document Verification
Anyone can verify a signed document's authenticity using the public verification endpoint:
// Look up by public ID (printed on the certificate) GET /api/documents/verify/{publicId} // Or verify a specific file's hash POST /api/documents/verify/{publicId} { "hash": "sha256-hex-string-of-your-file" }
This does not require any authentication.
Security Best Practices for Integrators
- Store API keys in environment variables or a secrets manager — never in source code
- Always verify webhook signatures before processing payloads
- Use HTTPS for all webhook endpoint URLs
- Rotate API keys periodically and revoke unused ones
- Set the shortest reasonable expiration time for signing links
- Enable OTP verification for high-value documents
- Publish a custom consent disclosure that matches your legal requirements
Appendix
Quick Reference — All API Endpoints
Authentication
| Method | Path | Auth | Description |
|---|---|---|---|
POST | /api/auth/register | None | Create tenant account |
POST | /api/auth/login | None | Log in, get JWT |
GET | /api/auth/me | JWT | Get current user |
Documents (API v1)
| Method | Path | Auth | Description |
|---|---|---|---|
POST | /api/v1/documents | API Key | Create single-signer document |
POST | /api/v1/documents/multi-signer | API Key | Create multi-signer document |
GET | /api/v1/documents | API Key | List documents (paginated) |
GET | /api/v1/documents/{id} | API Key | Get document details |
GET | /api/v1/documents/{id}/status | API Key | Check status |
DELETE | /api/v1/documents/{id} | API Key | Delete pending document |
POST | /api/v1/documents/{id}/embedded-session | API Key | Create embedded signing URL |
Templates (API v1)
| Method | Path | Auth | Description |
|---|---|---|---|
POST | /api/v1/templates | API Key | Create template from PDF |
POST | /api/v1/templates/from-document/{id} | API Key | Create from signed doc |
GET | /api/v1/templates | API Key | List templates |
GET | /api/v1/templates/{id} | API Key | Get template details |
PUT | /api/v1/templates/{id} | API Key | Update metadata |
DELETE | /api/v1/templates/{id} | API Key | Deactivate template |
POST | /api/v1/templates/{id}/send | API Key | Send to one recipient |
POST | /api/v1/templates/{id}/batch-send | API Key | Send to up to 100 recipients |
Recipients (API v1)
| Method | Path | Auth | Description |
|---|---|---|---|
POST | /api/v1/recipients | API Key | Create recipient |
GET | /api/v1/recipients | API Key | List recipients |
GET | /api/v1/recipients/{id} | API Key | Get recipient |
PUT | /api/v1/recipients/{id} | API Key | Update recipient |
DELETE | /api/v1/recipients/{id} | API Key | Deactivate recipient |
POST | /api/v1/recipients/{id}/reactivate | API Key | Reactivate |
Webhooks (API v1)
| Method | Path | Auth | Description |
|---|---|---|---|
POST | /api/v1/webhooks | API Key | Create webhook endpoint |
GET | /api/v1/webhooks | API Key | List webhooks |
DELETE | /api/v1/webhooks/{id} | API Key | Delete webhook |
GET | /api/v1/webhooks/{id}/deliveries | API Key | Delivery history |
POST | /api/v1/webhooks/{id}/test | API Key | Send test event |
Billing
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/billing/tiers | None | List subscription plans |
GET | /api/billing/usage | JWT | Current usage stats |
POST | /api/billing/checkout | JWT (Admin) | Create Stripe checkout |
POST | /api/billing/portal | JWT (Admin) | Open Stripe portal |
Signing Flow (Token-Based)
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/documents/placement/{token} | Token | Load placement page data |
POST | /api/documents/place/{token} | Token | Submit field positions |
POST | /api/documents/place-multi/{token} | Token | Submit multi-signer positions |
GET | /api/documents/sign/{token} | Token | Load signing page data |
POST | /api/documents/sign/{token} | Token | Submit signature |
GET | /api/documents/consent/{token} | Token | Get consent disclosure |
POST | /api/documents/consent/{token} | Token | Record consent |
GET | /api/documents/otp/status/{token} | Token | Check OTP status |
POST | /api/documents/otp/send/{token} | Token | Send OTP code |
POST | /api/documents/otp/verify/{token} | Token | Verify OTP code |
GET | /api/documents/signed/{id}?token= | Token | Download signed PDF |
GET | /api/documents/verify/{publicId} | None | Public verification lookup |
POST | /api/documents/verify/{publicId} | None | Verify file hash |
Dashboard
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /api/dashboard/stats | JWT | Document counts |
POST | /api/dashboard/documents | JWT | Create document |
POST | /api/dashboard/documents/multi-signer | JWT | Create multi-signer |
GET | /api/dashboard/documents | JWT | List documents |
GET | /api/dashboard/documents/{id} | JWT | Document details |
GET | /api/dashboard/documents/{id}/download | JWT | Download signed PDF |
POST | /api/dashboard/documents/{id}/resend | JWT | Resend signing email |
GET/POST | /api/dashboard/api-keys | JWT (Admin) | List/create API keys |
DELETE | /api/dashboard/api-keys/{id} | JWT (Admin) | Revoke API key |
GET/PUT | /api/dashboard/account | JWT (Admin) | Account settings |
GET/PUT | /api/dashboard/consent | JWT (Admin) | Consent settings |
GET/PUT | /api/dashboard/branding | JWT (Admin) | Branding |
GET/PUT | /api/dashboard/retention | JWT (Admin) | Retention policy |
GET/PUT | /api/dashboard/embedded-signing | JWT (Admin) | Embedded signing |
GET | /api/dashboard/users | JWT (Admin) | List users |
POST | /api/dashboard/users | JWT (Admin) | Invite user |
GET | /api/dashboard/analytics/trends | JWT | Daily trends |
GET | /api/dashboard/analytics/summary | JWT | Aggregate stats |
CT Signature Developer Manual — Version 1.0
© 2026 CozziTech LLC. All rights reserved.