Documenso SDK Patterns
Overview
Production-ready patterns for the Documenso TypeScript SDK (@documenso/sdk-typescript) and Python SDK. Covers singleton clients, typed wrappers, error handling, retry logic, and testing patterns.
Prerequisites
- Completed
documenso-install-auth setup
- Familiarity with async/await and TypeScript generics
- Understanding of error handling best practices
Instructions
Pattern 1: Singleton Client with Configuration
import { Documenso } from "@documenso/sdk-typescript";
interface DocumensoConfig {
apiKey: string;
baseUrl?: string;
timeout?: number;
}
let instance: Documenso | null = null;
export function getDocumensoClient(config?: DocumensoConfig): Documenso {
if (!instance) {
const apiKey = config?.apiKey ?? process.env.DOCUMENSO_API_KEY;
if (!apiKey) throw new Error("DOCUMENSO_API_KEY is required");
instance = new Documenso({
apiKey,
...(config?.baseUrl && { serverURL: config.baseUrl }),
});
}
return instance;
}
export function resetClient(): void {
instance = null;
}
Pattern 2: Typed Document Service
import { getDocumensoClient } from "./client";
export interface CreateDocumentInput {
title: string;
pdfPath: string;
signers: Array<{
email: string;
name: string;
fields: Array<{
type: "SIGNATURE" | "INITIALS" | "NAME" | "EMAIL" | "DATE" | "TEXT";
pageNumber: number;
pageX: number;
pageY: number;
pageWidth?: number;
pageHeight?: number;
}>;
}>;
}
export interface DocumentResult {
documentId: number;
recipientIds: number[];
status: "DRAFT" | "PENDING" | "COMPLETED";
}
export async function createAndSendDocument(
input:
): <> {
client = ();
{ readFileSync } = ();
doc = client..({ : input. });
pdfBuffer = (input.);
client..(doc., {
: ([pdfBuffer], { : }),
});
: [] = [];
( signer input.) {
recipient = client..(doc., {
: signer.,
: signer.,
: ,
});
recipientIds.(recipient.);
( field signer.) {
client..(doc., {
: recipient.,
: field.,
: field.,
: field.,
: field.,
: field. ?? ,
: field. ?? ,
});
}
}
client..(doc.);
{ : doc., recipientIds, : };
}
Pattern 3: Error Handling Wrapper
export class DocumensoError extends Error {
constructor(
message: string,
public statusCode?: number,
public retryable: boolean = false
) {
super(message);
this.name = "DocumensoError";
}
}
export async function withErrorHandling<T>(
operation: string,
fn: () => Promise<T>
): Promise<T> {
try {
return await fn();
} catch (err: any) {
const status = err.statusCode ?? err.status;
switch (status) {
case 401:
throw new DocumensoError(`${operation}: Invalid API key`, 401, false);
case 403:
throw (
,
,
);
:
(, , );
:
(, , );
:
:
:
(
,
status,
);
:
(
,
status,
);
}
}
}
Pattern 4: Retry with Exponential Backoff
import { DocumensoError } from "./errors";
interface RetryConfig {
maxRetries: number;
baseDelayMs: number;
maxDelayMs: number;
}
const DEFAULT_RETRY: RetryConfig = {
maxRetries: 3,
baseDelayMs: 1000,
maxDelayMs: 30000,
};
export async function withRetry<T>(
fn: () => Promise<T>,
config: Partial<RetryConfig> = {}
): Promise<T> {
const { maxRetries, baseDelayMs, maxDelayMs } = { ...DEFAULT_RETRY, ...config };
let lastError: Error | undefined;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (err) {
lastError = err as Error;
if (err instanceof DocumensoError && !err.) err;
(attempt === maxRetries) ;
delay = .(baseDelayMs * ** attempt, maxDelayMs);
jitter = delay * ( + .() * );
( (r, jitter));
}
}
lastError;
}
Pattern 5: Python Service Pattern
from documenso_sdk_python import Documenso
from dataclasses import dataclass
from typing import Optional
import os
@dataclass
class SignerInput:
email: str
name: str
field_type: str = "SIGNATURE"
page: int = 1
x: float = 50.0
y: float = 80.0
class DocumensoService:
def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None):
self.client = Documenso(
api_key=api_key or os.environ["DOCUMENSO_API_KEY"],
**({"server_url": base_url} if base_url else {}),
)
def create_and_send(
self, title: str, pdf_path: str, signers: list[SignerInput]
) -> dict:
doc = self.client.documents.create_v0(title=title)
with open(pdf_path, "rb") f:
.client.documents.set_file_v0(doc.document_id, file=f.read())
recipient_ids = []
signer signers:
recip = .client.documents_recipients.create_v0(
doc.document_id, email=signer.email, name=signer.name, role=
)
recipient_ids.append(recip.recipient_id)
.client.documents_fields.create_v0(
doc.document_id,
recipient_id=recip.recipient_id,
=signer.field_type,
page_number=signer.page,
page_x=signer.x,
page_y=signer.y,
)
.client.documents.send_v0(doc.document_id)
{: doc.document_id, : recipient_ids}
Pattern 6: Testing with Mocks
import { vi } from "vitest";
export function createMockClient() {
return {
documents: {
createV0: vi.fn().mockResolvedValue({ documentId: 1 }),
setFileV0: vi.fn().mockResolvedValue(undefined),
findV0: vi.fn().mockResolvedValue({ documents: [] }),
sendV0: vi.fn().mockResolvedValue(undefined),
deleteV0: vi.fn().mockResolvedValue(undefined),
},
documentsRecipients: {
createV0: vi.fn().mockResolvedValue({ recipientId: 100 }),
},
documentsFields: {
createV0: vi.fn().mockResolvedValue({ fieldId: 200 }),
},
};
}
Error Handling
| Pattern Issue | Cause | Solution |
|---|
| Client not initialized | Missing env var | Check DOCUMENSO_API_KEY is set |
| Singleton stale after key rotation | Cached client | Call resetClient() |
| Retry loop on 401 | Non-retryable treated as retryable | Check retryable flag |
| Type mismatch on field type | Wrong enum string | Use union type from SDK |
Resources
Next Steps
Apply patterns in documenso-core-workflow-a for document creation workflows.