| name | documenso-sdk-patterns |
| description | Apply production-ready Documenso SDK patterns for TypeScript and Python.
Use when implementing Documenso integrations, refactoring SDK usage,
or establishing team coding standards for Documenso.
Trigger with phrases like "documenso SDK patterns", "documenso best practices",
"documenso code patterns", "idiomatic documenso".
|
| allowed-tools | Read, Write, Edit |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
Documenso SDK Patterns
Overview
Production-ready patterns for Documenso SDK usage in TypeScript and Python.
Prerequisites
- Completed
documenso-install-auth setup
- Familiarity with async/await patterns
- Understanding of error handling best practices
Instructions
Step 1: Singleton Client Pattern
import { Documenso } from "@documenso/sdk-typescript";
let instance: Documenso | null = null;
export interface DocumensoClientConfig {
apiKey?: string;
baseUrl?: string;
timeout?: number;
}
export function getDocumensoClient(config?: DocumensoClientConfig): Documenso {
if (!instance) {
const apiKey = config?.apiKey ?? process.env.DOCUMENSO_API_KEY;
if (!apiKey) {
throw new Error(
"DOCUMENSO_API_KEY environment variable is required"
);
}
instance = new Documenso({
apiKey,
serverURL: config?.baseUrl ?? process.env.DOCUMENSO_BASE_URL,
timeoutMs: config?.timeout ?? 30000,
});
}
return instance;
}
export function resetDocumensoClient(): void {
instance = null;
}
Step 2: Type-Safe Error Handling
import { SDKError } from "@documenso/sdk-typescript";
export class DocumensoError extends Error {
constructor(
message: string,
public readonly code: string,
public readonly statusCode: number,
public readonly retryable: boolean,
public readonly originalError?: Error
) {
super(message);
this.name = "DocumensoError";
}
}
export function wrapDocumensoError(error: unknown): DocumensoError {
if (error instanceof SDKError) {
const retryable = error.statusCode >= 500 || error.statusCode === 429;
return new DocumensoError(
error.,
,
error.,
retryable,
error
);
}
(error ) {
(
error.,
,
,
,
error
);
}
(
(error),
,
,
);
}
safeDocumensoCall<T>(
: <T>
): <{ : T | ; : | }> {
{
data = ();
{ data, : };
} (err) {
error = (err);
.({
: error.,
: error.,
: error.,
: error.,
});
{ : , error };
}
}
Step 3: Retry Logic with Exponential Backoff
interface RetryConfig {
maxRetries?: number;
baseDelayMs?: number;
maxDelayMs?: number;
jitterMs?: number;
}
const DEFAULT_RETRY_CONFIG: Required<RetryConfig> = {
maxRetries: 3,
baseDelayMs: 1000,
maxDelayMs: 30000,
jitterMs: 500,
};
export async function withRetry<T>(
operation: () => Promise<T>,
config: RetryConfig = {}
): Promise<T> {
const { maxRetries, baseDelayMs, maxDelayMs, jitterMs } = {
...DEFAULT_RETRY_CONFIG,
...config,
};
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error: any) {
const statusCode = error.statusCode ?? error.status ?? 0;
const isRetryable = statusCode === || statusCode >= ;
(attempt === maxRetries || !isRetryable) {
error;
}
exponentialDelay = baseDelayMs * .(, attempt);
jitter = .() * jitterMs;
delay = .(exponentialDelay + jitter, maxDelayMs);
.(
);
( (r, delay));
}
}
();
}
Step 4: Document Service Facade
import { getDocumensoClient } from "../documenso/client";
import { withRetry } from "../documenso/retry";
import { safeDocumensoCall, DocumensoError } from "../documenso/errors";
export interface CreateDocumentInput {
title: string;
file?: Blob;
recipients: Array<{
email: string;
name: string;
role?: "SIGNER" | "VIEWER" | "APPROVER";
}>;
fields?: Array<{
recipientIndex: number;
type: "SIGNATURE" | "INITIALS" | "NAME" | "EMAIL" | "DATE" | "TEXT";
page: number;
x: number;
y: number;
width?: number;
height?: number;
}>;
sendImmediately?: boolean;
}
export interface {
: ;
: ;
: <{ : ; : }>;
}
{
client = ();
(: ): <> {
doc = (
...({
: input.,
: input.,
})
);
documentId = doc.!;
: <{ : ; : }> = [];
( recipient input.) {
result = (
...({
documentId,
: recipient.,
: recipient.,
: recipient. ?? ,
})
);
recipientIds.({
: result.!,
: recipient.,
});
}
(input.) {
( field input.) {
recipientId = recipientIds[field.]?.;
(!recipientId) ;
(
...({
documentId,
recipientId,
: field.,
: field.,
: field.,
: field.,
: field. ?? ,
: field. ?? ,
})
);
}
}
(input.) {
(
...({ documentId })
);
}
{
documentId,
: input. ? : ,
: recipientIds,
};
}
(: ): < | > {
{ data, error } = (
...({ documentId })
);
(error) {
(error. === ) ;
error;
}
{
: data!.!,
: data!.!,
:
data!.?.( ({
: r.!,
: r.!,
})) ?? [],
};
}
(: ): <> {
{ error } = (
...({ documentId })
);
error === ;
}
}
: | = ;
(): {
(!documentService) {
documentService = ();
}
documentService;
}
Step 5: Response Validation with Zod
import { z } from "zod";
export const DocumentStatusSchema = z.enum([
"DRAFT",
"PENDING",
"COMPLETED",
"REJECTED",
"CANCELLED",
]);
export const RecipientSchema = z.object({
id: z.string(),
email: z.string().email(),
name: z.string(),
role: z.enum(["SIGNER", "VIEWER", "APPROVER", "CC"]),
signingStatus: z.enum(["NOT_SIGNED", "SIGNED", "REJECTED"]).optional(),
});
export const DocumentSchema = z.object({
id: z.string(),
title: z.string(),
status: DocumentStatusSchema,
createdAt: z.string().datetime(),
updatedAt: z.string().datetime(),
: z.().(),
});
= z.< >;
= z.< >;
(): {
.(data);
}
Python Patterns
import os
from typing import Optional
from functools import lru_cache
from documenso_sdk import Documenso
@lru_cache(maxsize=1)
def get_documenso_client(
api_key: Optional[str] = None,
base_url: Optional[str] = None,
) -> Documenso:
"""Get singleton Documenso client."""
key = api_key or os.environ.get("DOCUMENSO_API_KEY")
if not key:
raise ValueError("DOCUMENSO_API_KEY is required")
return Documenso(
api_key=key,
server_url=base_url or os.environ.get("DOCUMENSO_BASE_URL"),
)
import asyncio
import random
from typing import TypeVar, Callable, Awaitable
T = TypeVar("T")
async def with_retry(
operation: Callable[[], Awaitable[T]],
max_retries: int = 3,
base_delay: float = 1.0,
max_delay: float = 30.0,
) -> T:
attempt (max_retries + ):
:
operation()
Exception e:
status = (e, , )
is_retryable = status == status >=
attempt == max_retries is_retryable:
delay = (base_delay * ( ** attempt) + random.uniform(, ), max_delay)
()
asyncio.sleep(delay)
RuntimeError()
Output
- Type-safe client singleton
- Robust error handling with retryable classification
- Automatic retry with exponential backoff
- Service facade for common operations
- Runtime validation for API responses
Error Handling Patterns
| Pattern | Use Case | Benefit |
|---|
| Safe wrapper | All API calls | Prevents uncaught exceptions |
| Retry logic | Transient failures | Improves reliability |
| Type guards | Response validation | Catches API changes |
| Service facade | Complex workflows | Encapsulates multi-step operations |
Resources
Next Steps
Apply patterns in documenso-core-workflow-a for document creation workflows.