CT Signature

Developer Integration Manual

Complete guide to API integration, authentication, webhooks, templates, multi-signer workflows, billing, and dashboard features.

Version 1.0 — April 2026

CozziTech LLC

Table of Contents

  1. Getting Started
    1. Platform Overview
    2. Base URLs & Environments
    3. Quick Start (5 Minutes)
  2. Authentication
    1. API Keys
    2. JWT Bearer Tokens
    3. Platform Admin Tokens
    4. Signing & Placement Tokens
  3. Documents API
    1. Create a Document
    2. Create a Multi-Signer Document
    3. List Documents
    4. Get Document Details
    5. Check Document Status
    6. Delete a Document
    7. Download Signed PDF
    8. Verify a Signed Document
    9. Embedded Signing Sessions
  4. Templates API
    1. Create a Template
    2. Send from a Template
    3. Batch Send
  5. Recipients API (Address Book)
  6. Webhooks
    1. Setting Up Webhooks
    2. Event Types
    3. Payload Format
    4. Verifying Signatures
    5. Retries & Deliveries
  7. Signing Workflow
    1. Single-Signer Flow
    2. Multi-Signer Flow
    3. Consent & OTP
  8. Billing & Subscriptions
  9. Dashboard Guide
    1. Documents
    2. Templates
    3. User Management
    4. Settings & Branding
    5. Analytics
  10. Configuration Reference
  11. Error Handling
  12. Security & Compliance

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:

Base URLs & Environments

EnvironmentBase URLNotes
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:

1
Create an account
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.

2
Generate an API key
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...

3
Upload a document
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).

4
Place signature fields

Open the placementUrl in a browser. Drag and drop the signature, printed name, date, and initials fields onto the document. Click Save & Send.

5
Signer signs

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.

6
Download the signed PDF
GET /api/v1/documents/{documentId}
Authorization: Bearer ctds_aBcDeFgHiJkLm...

The response includes signedPdfUrl. The signed PDF has a Certificate of Completion appended.

You're up and running!
That's the core flow. Read on for multi-signer support, templates, webhooks, and embedded signing.

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"
}
Important
The full key is only returned at creation time. Store it securely (e.g., in environment variables or a secrets manager). If you lose it, revoke and create a new one.

Revoking a Key

DELETE /api/dashboard/api-keys/{keyId}
Authorization: Bearer <jwt-token>

Endpoints That Accept API Keys

Path PrefixDescription
/api/v1/documentsCreate, list, get, delete documents
/api/v1/templatesCreate, send, batch-send templates
/api/v1/recipientsManage address book
/api/v1/webhooksManage 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

PropertyValue
AlgorithmHMAC-SHA256
Lifetime24 hours (configurable)
IssuerJwt:Issuer setting
AudienceJwt:Audience setting

JWT Claims

ClaimDescription
subTenant ID (number as string)
user_idTenant user ID (if multi-user tenant)
roleUser role: Admin, User, or Reviewer
emailUser's email address
iatIssued-at timestamp
expExpiration 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 TypeUsed ForURL PatternExpires
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)

POST /api/v1/documents API Key

Upload a PDF and specify who should sign it.

Request

Content-Type: multipart/form-data

FieldTypeRequiredDescription
FilefileYesPDF file (max 10 MB)
RecipientNamestringYesSigner's full name
RecipientEmailstringYesSigner's email address
ExpirationHoursintegerNoHours 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"
}
Next step
Open the 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

POST /api/v1/documents/multi-signer API Key

Create a document that requires two or more signers.

Request

Content-Type: multipart/form-data

FieldTypeRequiredDescription
FilefileYesPDF file (max 10 MB)
SignersJSON arrayYesArray of signer objects (see below)
WorkflowTypestringYessequential or parallel
ExpirationHoursintegerNoHours until expiration (default: 72)

Signer Object

FieldTypeRequiredDescription
SignerNamestringYesSigner's full name
SignerEmailstringYesSigner's email
SignerRolestringNoRole label (e.g., "Manager", "Legal")
SignOrderintegerNoSigning 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..."
    }
  ]
}
Sequential vs. Parallel
Sequential: signers must sign in order. Signer 2 cannot sign until Signer 1 is done. Each signer is emailed when it's their turn.
Parallel: all signers can sign at the same time. Everyone gets their link immediately.

List Documents

GET /api/v1/documents API Key

Query Parameters

ParameterTypeDefaultDescription
statusstring(all)Filter: pending, sent, signed, expired
pageinteger1Page number
pageSizeinteger20Results 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

GET /api/v1/documents/{id} API Key

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

GET /api/v1/documents/{id}/status API Key

A lightweight endpoint to check the current status of a document and its signers.

Document Status Values

StatusMeaning
pendingCreated but fields not yet placed
sentFields placed, signing link is active
partially_signedSome (but not all) signers have signed (multi-signer only)
signedAll signers have signed
expiredSigning link expired before completion

Delete a Document

DELETE /api/v1/documents/{id} API Key

Delete a pending document. Signed documents cannot be deleted through the API.

Download Signed PDF

GET /api/documents/signed/{id}?token=<secureToken> Token

Downloads the fully signed PDF with the Certificate of Completion appended. The download is logged in the audit trail.

From the dashboard, use:

GET /api/dashboard/documents/{id}/download JWT

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

GET /api/documents/verify/{publicId} Public
// 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

POST /api/documents/verify/{publicId} Public

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

POST /api/v1/documents/{id}/embedded-session API Key
// 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

POST /api/v1/templates API Key
FieldTypeRequiredDescription
FilefileYesPDF file
NamestringYesTemplate name (e.g., "Employee NDA")
DescriptionstringNoOptional description

You can also create a template from an existing signed document:

POST /api/v1/templates/from-document/{documentId} API Key

This copies the original (unsigned) PDF and all field placements into a new template.

Other Template Endpoints

MethodPathDescription
GET/api/v1/templatesList 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

POST /api/v1/templates/{id}/send API Key

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

POST /api/v1/templates/{id}/batch-send API Key

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.

MethodPathDescription
GET/api/v1/recipientsList recipients (supports ?search= and ?includeInactive=true)
GET/api/v1/recipients/{id}Get recipient details
POST/api/v1/recipientsCreate recipient
PUT/api/v1/recipients/{id}Update recipient
DELETE/api/v1/recipients/{id}Deactivate recipient
POST/api/v1/recipients/{id}/reactivateReactivate 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 API Key
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"
}
Save the secret
The 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.

EventFires When
document.createdA new document is uploaded
document.sentSignature fields are placed, signing link becomes active
document.placedSignature fields are positioned on the PDF
signer.viewedA signer opens the signing page
signer.consent-givenA signer accepts the disclosure
otp.sentAn OTP code is emailed to the signer
otp.verifiedThe signer enters a correct OTP code
signer.signedA signer submits their signature
document.completedAll signers have signed the document
signer.failedSigning failed (e.g., too many OTP attempts)
email.deliveredSigning email was delivered to the signer
email.bouncedEmail bounced
email.failedEmail was marked as spam
email.openedSigner 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

HeaderDescription
Content-Typeapplication/json
X-Signaturesha256=<hex-encoded HMAC-SHA256>
User-AgentCTSignature-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:

  1. Read the raw request body (as bytes or a UTF-8 string)
  2. Compute HMAC-SHA256 of the body using your webhook secret
  3. Compare your computed signature to the X-Signature header 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:

AttemptDelay
1Immediate
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:

GET /api/v1/webhooks/{id}/deliveries?limit=25 API Key

To test your webhook endpoint without creating a real document:

POST /api/v1/webhooks/{id}/test API Key

Section 7

Signing Workflow

Single-Signer Flow

This is the complete lifecycle of a single-signer document:

1
Create Document — Upload PDF via API. Status: pending. You receive a placementUrl and signingUrl.
2
Place Fields — Open the placement URL in a browser. Drag signature, printed name, date, and initials fields onto the PDF. Click Save. Status changes to sent. An email is sent to the signer automatically.
3
Signer Opens Link — The signer clicks the link in their email. If consent is enabled, they see a disclosure and must accept it. If OTP is enabled, they verify their identity via a code sent to their email.
4
Signer Signs — The signer types their signature, chooses a font, fills in required initials fields, confirms their intent, and submits. Status changes to signed.
5
Completion — The signature is applied to the PDF. A SHA-256 hash is computed. A Certificate of Completion is appended. You receive a document.completed webhook. The signed PDF is available for download.

Multi-Signer Flow

Sequential Workflow

  1. Create multi-signer document with workflowType: "sequential"
  2. Place fields for all signers on the placement page
  3. Signer 1 receives an email. They sign.
  4. Only after Signer 1 completes does Signer 2 receive their email
  5. This continues through all signers in order
  6. When the last signer signs, the document status becomes signed

Parallel Workflow

  1. Create multi-signer document with workflowType: "parallel"
  2. Place fields for all signers
  3. All signers receive emails at the same time
  4. Signers can sign in any order
  5. When the last remaining signer completes, the document is fully signed

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.

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.

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

GET /api/billing/tiers Public

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

GET /api/billing/usage JWT
// 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 JWT (Admin)
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:

POST /api/billing/portal JWT (Admin)

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:

Dashboard Document Creation Endpoint

POST /api/dashboard/documents JWT

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

GET /api/dashboard/stats JWT

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:

User Management

Tenant admins can invite team members and control their access.

User Roles

RolePermissions
AdminFull access: documents, templates, settings, billing, users, webhooks, API keys
UserCreate and manage documents and templates. Cannot change settings, billing, or users.
ReviewerView documents only. Cannot create or modify.

User Management Endpoints

MethodPathDescription
GET/api/dashboard/usersList all users in the tenant
POST/api/dashboard/usersInvite a new user (by email)
PUT/api/dashboard/users/{id}/roleChange a user's role
POST/api/dashboard/users/{id}/deactivateDisable a user's access
POST/api/dashboard/users/{id}/activateRe-enable a disabled user
POST/api/dashboard/users/{id}/reset-passwordForce a password reset

Settings & Branding

Account Settings

PUT /api/dashboard/account JWT (Admin)

Update company name, contact email, or password.

Consent Settings

GET /api/dashboard/consent JWT (Admin)

Manage consent disclosure flow type (modal or page), publish custom disclosure text, and enable/disable OTP verification.

Branding

MethodPathDescription
GET/api/dashboard/brandingGet current branding settings
PUT/api/dashboard/brandingUpdate colors and brand name
POST/api/dashboard/branding/logoUpload logo (PNG, JPG, SVG; max 2 MB)
DELETE/api/dashboard/branding/logoRemove logo

Document Retention

PUT /api/dashboard/retention JWT (Admin)

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

PUT /api/dashboard/embedded-signing JWT (Admin)

Enable or disable iframe-based embedded signing and set allowed domains.

Analytics

GET /api/dashboard/analytics/trends?days=30 JWT

Returns daily document creation and signing counts for charting. Supports 7, 30, 60, and 90 day windows.

GET /api/dashboard/analytics/summary JWT

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

SettingDefaultDescription
Jwt:SecretSigning key for JWT tokens. Must be at least 32 characters. Change this in production!
Jwt:IssuerctDocSignIssuer claim in JWT tokens
Jwt:AudiencectDocSign-dashboardAudience claim in JWT tokens
Jwt:ExpirationHours24How long JWT tokens are valid

Stripe Settings

SettingDescription
Stripe:SecretKeyStripe API secret key (sk_live_... or sk_test_...)
Stripe:PublishableKeyStripe publishable key (for frontend)
Stripe:WebhookSecretStripe webhook endpoint signing secret (whsec_...)

Platform Admin

SettingDescription
PlatformAdmin:EmailEmail for platform admin login
PlatformAdmin:PasswordPassword for platform admin login
PlatformAdmin:SecretSecret for admin endpoint validation

Document Storage & Security

SettingDefaultDescription
DocumentSigning:Storage:BasePath./Documents/SigningRoot directory for stored PDFs
DocumentSigning:Storage:MaxFileSize10485760Max upload size in bytes (10 MB)
DocumentSigning:Security:TokenExpirationHours72Signing link lifetime
DocumentSigning:Security:AllowedOrigins[]CORS allowed origins
DocumentSigning:ProductionBaseUrlBase URL for signing links in emails

Testing & Development

SettingDefaultDescription
DocumentSigning:Testing:EnableTestModetrueEnables the /api/test/ endpoints
DocumentSigning:Testing:SkipEmailSendingtrueSkips actual email delivery in dev
DocumentSigning:Testing:LocalhostBaseUrlhttp://localhost:8080Base URL used in dev mode

Email Providers

Set one of these environment variables to enable email delivery:

VariableProvider
POSTMARK_API_TOKENPostmark (recommended for delivery tracking)
RESEND_API_TOKENResend (alternative provider)

Rate Limits

ScopeLimit
General API60 requests/minute
Signature submission10 requests/minute
Test endpoint5 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

CodeMeaningCommon Causes
200OKRequest succeeded
201CreatedResource created successfully
400Bad RequestMissing required field, invalid file format, validation error
401UnauthorizedMissing or invalid API key / JWT token
402Payment RequiredBilling quota exceeded (trial or subscription limit reached)
403ForbiddenValid auth but not allowed (wrong tenant, non-admin user)
404Not FoundDocument, template, or recipient doesn't exist
409ConflictEmail already registered, duplicate recipient
429Too Many RequestsRate limit exceeded — slow down and retry
500Internal Server ErrorServer-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

ESIGN Act & UETA Compliance

CT Signature includes the following features to support ESIGN Act and UETA compliance:

RequirementHow 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 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

Recommendations
  • 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

MethodPathAuthDescription
POST/api/auth/registerNoneCreate tenant account
POST/api/auth/loginNoneLog in, get JWT
GET/api/auth/meJWTGet current user

Documents (API v1)

MethodPathAuthDescription
POST/api/v1/documentsAPI KeyCreate single-signer document
POST/api/v1/documents/multi-signerAPI KeyCreate multi-signer document
GET/api/v1/documentsAPI KeyList documents (paginated)
GET/api/v1/documents/{id}API KeyGet document details
GET/api/v1/documents/{id}/statusAPI KeyCheck status
DELETE/api/v1/documents/{id}API KeyDelete pending document
POST/api/v1/documents/{id}/embedded-sessionAPI KeyCreate embedded signing URL

Templates (API v1)

MethodPathAuthDescription
POST/api/v1/templatesAPI KeyCreate template from PDF
POST/api/v1/templates/from-document/{id}API KeyCreate from signed doc
GET/api/v1/templatesAPI KeyList templates
GET/api/v1/templates/{id}API KeyGet template details
PUT/api/v1/templates/{id}API KeyUpdate metadata
DELETE/api/v1/templates/{id}API KeyDeactivate template
POST/api/v1/templates/{id}/sendAPI KeySend to one recipient
POST/api/v1/templates/{id}/batch-sendAPI KeySend to up to 100 recipients

Recipients (API v1)

MethodPathAuthDescription
POST/api/v1/recipientsAPI KeyCreate recipient
GET/api/v1/recipientsAPI KeyList recipients
GET/api/v1/recipients/{id}API KeyGet recipient
PUT/api/v1/recipients/{id}API KeyUpdate recipient
DELETE/api/v1/recipients/{id}API KeyDeactivate recipient
POST/api/v1/recipients/{id}/reactivateAPI KeyReactivate

Webhooks (API v1)

MethodPathAuthDescription
POST/api/v1/webhooksAPI KeyCreate webhook endpoint
GET/api/v1/webhooksAPI KeyList webhooks
DELETE/api/v1/webhooks/{id}API KeyDelete webhook
GET/api/v1/webhooks/{id}/deliveriesAPI KeyDelivery history
POST/api/v1/webhooks/{id}/testAPI KeySend test event

Billing

MethodPathAuthDescription
GET/api/billing/tiersNoneList subscription plans
GET/api/billing/usageJWTCurrent usage stats
POST/api/billing/checkoutJWT (Admin)Create Stripe checkout
POST/api/billing/portalJWT (Admin)Open Stripe portal

Signing Flow (Token-Based)

MethodPathAuthDescription
GET/api/documents/placement/{token}TokenLoad placement page data
POST/api/documents/place/{token}TokenSubmit field positions
POST/api/documents/place-multi/{token}TokenSubmit multi-signer positions
GET/api/documents/sign/{token}TokenLoad signing page data
POST/api/documents/sign/{token}TokenSubmit signature
GET/api/documents/consent/{token}TokenGet consent disclosure
POST/api/documents/consent/{token}TokenRecord consent
GET/api/documents/otp/status/{token}TokenCheck OTP status
POST/api/documents/otp/send/{token}TokenSend OTP code
POST/api/documents/otp/verify/{token}TokenVerify OTP code
GET/api/documents/signed/{id}?token=TokenDownload signed PDF
GET/api/documents/verify/{publicId}NonePublic verification lookup
POST/api/documents/verify/{publicId}NoneVerify file hash

Dashboard

MethodPathAuthDescription
GET/api/dashboard/statsJWTDocument counts
POST/api/dashboard/documentsJWTCreate document
POST/api/dashboard/documents/multi-signerJWTCreate multi-signer
GET/api/dashboard/documentsJWTList documents
GET/api/dashboard/documents/{id}JWTDocument details
GET/api/dashboard/documents/{id}/downloadJWTDownload signed PDF
POST/api/dashboard/documents/{id}/resendJWTResend signing email
GET/POST/api/dashboard/api-keysJWT (Admin)List/create API keys
DELETE/api/dashboard/api-keys/{id}JWT (Admin)Revoke API key
GET/PUT/api/dashboard/accountJWT (Admin)Account settings
GET/PUT/api/dashboard/consentJWT (Admin)Consent settings
GET/PUT/api/dashboard/brandingJWT (Admin)Branding
GET/PUT/api/dashboard/retentionJWT (Admin)Retention policy
GET/PUT/api/dashboard/embedded-signingJWT (Admin)Embedded signing
GET/api/dashboard/usersJWT (Admin)List users
POST/api/dashboard/usersJWT (Admin)Invite user
GET/api/dashboard/analytics/trendsJWTDaily trends
GET/api/dashboard/analytics/summaryJWTAggregate stats

CT Signature Developer Manual — Version 1.0

© 2026 CozziTech LLC. All rights reserved.