[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6F6E43EVPRFZJAR8249PV79":3,"skill-same:01M6F6E43EVPRFZJAR8249PV79":75},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":43,"linkOnly":44,"markdownContent":45,"markdownBody":46,"frontMatter":47,"refName":52,"commitSha":53,"skillDirectory":54,"forksCount":55,"repositoryHtmlUrl":56,"repositoryHomepageUrl":6,"ownerHtmlUrl":57,"ownerBlogUrl":58,"ownerDescription":59,"updatedAt":60,"install":61,"resources":74},"01M6F6E43EVPRFZJAR8249PV79","documenso-sdk-patterns",null,"Apply production-ready Documenso SDK patterns for TypeScript and Python.\nUse when implementing Documenso integrations, refactoring SDK usage,\nor establishing team coding standards for Documenso.\nTrigger with phrases like \"documenso SDK patterns\", \"documenso best practices\",\n\"documenso code patterns\", \"idiomatic documenso\".","Dicklesworthstone\u002Fpi_agent_rust","Dicklesworthstone","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F35050222?v=4","01M6CG26AR942CSVMN1M5E3DSE","Jeff Emanuel","01M6CG26EPP71PSHW1VDS71121",1991,"MIT","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Fplugins-community\u002Fplugins\u002Fsaas-packs\u002Fdocumenso-pack\u002Fskills\u002Fdocumenso-sdk-patterns\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42],"sdk","documenso","documenso sdk patterns","patterns","use","when","implementing","production-ready","code patterns","trigger","io","standards","ready","typescript","edit","write","read","coding","integrations","refactoring","team","coding standards","code","python","2026-11-10 10:12:30",false,"---\nname: documenso-sdk-patterns\ndescription: |\n  Apply production-ready Documenso SDK patterns for TypeScript and Python.\n  Use when implementing Documenso integrations, refactoring SDK usage,\n  or establishing team coding standards for Documenso.\n  Trigger with phrases like \"documenso SDK patterns\", \"documenso best practices\",\n  \"documenso code patterns\", \"idiomatic documenso\".\nallowed-tools: Read, Write, Edit\nversion: 1.0.0\nlicense: MIT\nauthor: Jeremy Longshore \u003Cjeremy@intentsolutions.io>\n---\n\n# Documenso SDK Patterns\n\n## Overview\nProduction-ready patterns for Documenso SDK usage in TypeScript and Python.\n\n## Prerequisites\n- Completed `documenso-install-auth` setup\n- Familiarity with async\u002Fawait patterns\n- Understanding of error handling best practices\n\n## Instructions\n\n### Step 1: Singleton Client Pattern\n\n```typescript\n\u002F\u002F src\u002Fdocumenso\u002Fclient.ts\nimport { Documenso } from \"@documenso\u002Fsdk-typescript\";\n\nlet instance: Documenso | null = null;\n\nexport interface DocumensoClientConfig {\n  apiKey?: string;\n  baseUrl?: string;\n  timeout?: number;\n}\n\nexport function getDocumensoClient(config?: DocumensoClientConfig): Documenso {\n  if (!instance) {\n    const apiKey = config?.apiKey ?? process.env.DOCUMENSO_API_KEY;\n\n    if (!apiKey) {\n      throw new Error(\n        \"DOCUMENSO_API_KEY environment variable is required\"\n      );\n    }\n\n    instance = new Documenso({\n      apiKey,\n      serverURL: config?.baseUrl ?? process.env.DOCUMENSO_BASE_URL,\n      timeoutMs: config?.timeout ?? 30000,\n    });\n  }\n  return instance;\n}\n\n\u002F\u002F For testing - allows resetting the singleton\nexport function resetDocumensoClient(): void {\n  instance = null;\n}\n```\n\n### Step 2: Type-Safe Error Handling\n\n```typescript\n\u002F\u002F src\u002Fdocumenso\u002Ferrors.ts\nimport { SDKError } from \"@documenso\u002Fsdk-typescript\";\n\nexport class DocumensoError extends Error {\n  constructor(\n    message: string,\n    public readonly code: string,\n    public readonly statusCode: number,\n    public readonly retryable: boolean,\n    public readonly originalError?: Error\n  ) {\n    super(message);\n    this.name = \"DocumensoError\";\n  }\n}\n\nexport function wrapDocumensoError(error: unknown): DocumensoError {\n  if (error instanceof SDKError) {\n    const retryable = error.statusCode >= 500 || error.statusCode === 429;\n    return new DocumensoError(\n      error.message,\n      `DOCUMENSO_${error.statusCode}`,\n      error.statusCode,\n      retryable,\n      error\n    );\n  }\n\n  if (error instanceof Error) {\n    return new DocumensoError(\n      error.message,\n      \"DOCUMENSO_UNKNOWN\",\n      0,\n      false,\n      error\n    );\n  }\n\n  return new DocumensoError(\n    String(error),\n    \"DOCUMENSO_UNKNOWN\",\n    0,\n    false\n  );\n}\n\n\u002F\u002F Safe wrapper for API calls\nexport async function safeDocumensoCall\u003CT>(\n  operation: () => Promise\u003CT>\n): Promise\u003C{ data: T | null; error: DocumensoError | null }> {\n  try {\n    const data = await operation();\n    return { data, error: null };\n  } catch (err) {\n    const error = wrapDocumensoError(err);\n    console.error({\n      code: error.code,\n      message: error.message,\n      statusCode: error.statusCode,\n      retryable: error.retryable,\n    });\n    return { data: null, error };\n  }\n}\n```\n\n### Step 3: Retry Logic with Exponential Backoff\n\n```typescript\n\u002F\u002F src\u002Fdocumenso\u002Fretry.ts\ninterface RetryConfig {\n  maxRetries?: number;\n  baseDelayMs?: number;\n  maxDelayMs?: number;\n  jitterMs?: number;\n}\n\nconst DEFAULT_RETRY_CONFIG: Required\u003CRetryConfig> = {\n  maxRetries: 3,\n  baseDelayMs: 1000,\n  maxDelayMs: 30000,\n  jitterMs: 500,\n};\n\nexport async function withRetry\u003CT>(\n  operation: () => Promise\u003CT>,\n  config: RetryConfig = {}\n): Promise\u003CT> {\n  const { maxRetries, baseDelayMs, maxDelayMs, jitterMs } = {\n    ...DEFAULT_RETRY_CONFIG,\n    ...config,\n  };\n\n  for (let attempt = 0; attempt \u003C= maxRetries; attempt++) {\n    try {\n      return await operation();\n    } catch (error: any) {\n      const statusCode = error.statusCode ?? error.status ?? 0;\n\n      \u002F\u002F Only retry on 429 (rate limit) and 5xx (server errors)\n      const isRetryable = statusCode === 429 || statusCode >= 500;\n\n      if (attempt === maxRetries || !isRetryable) {\n        throw error;\n      }\n\n      const exponentialDelay = baseDelayMs * Math.pow(2, attempt);\n      const jitter = Math.random() * jitterMs;\n      const delay = Math.min(exponentialDelay + jitter, maxDelayMs);\n\n      console.log(\n        `Attempt ${attempt + 1} failed. Retrying in ${delay.toFixed(0)}ms...`\n      );\n      await new Promise((r) => setTimeout(r, delay));\n    }\n  }\n  throw new Error(\"Unreachable\");\n}\n```\n\n### Step 4: Document Service Facade\n\n```typescript\n\u002F\u002F src\u002Fservices\u002Fdocument-service.ts\nimport { getDocumensoClient } from \"..\u002Fdocumenso\u002Fclient\";\nimport { withRetry } from \"..\u002Fdocumenso\u002Fretry\";\nimport { safeDocumensoCall, DocumensoError } from \"..\u002Fdocumenso\u002Ferrors\";\n\nexport interface CreateDocumentInput {\n  title: string;\n  file?: Blob;\n  recipients: Array\u003C{\n    email: string;\n    name: string;\n    role?: \"SIGNER\" | \"VIEWER\" | \"APPROVER\";\n  }>;\n  fields?: Array\u003C{\n    recipientIndex: number;\n    type: \"SIGNATURE\" | \"INITIALS\" | \"NAME\" | \"EMAIL\" | \"DATE\" | \"TEXT\";\n    page: number;\n    x: number;\n    y: number;\n    width?: number;\n    height?: number;\n  }>;\n  sendImmediately?: boolean;\n}\n\nexport interface DocumentResult {\n  documentId: string;\n  status: string;\n  recipients: Array\u003C{ id: string; email: string }>;\n}\n\nexport class DocumentService {\n  private client = getDocumensoClient();\n\n  async createDocument(input: CreateDocumentInput): Promise\u003CDocumentResult> {\n    \u002F\u002F Step 1: Create document\n    const doc = await withRetry(() =>\n      this.client.documents.createV0({\n        title: input.title,\n        file: input.file,\n      })\n    );\n\n    const documentId = doc.documentId!;\n    const recipientIds: Array\u003C{ id: string; email: string }> = [];\n\n    \u002F\u002F Step 2: Add recipients\n    for (const recipient of input.recipients) {\n      const result = await withRetry(() =>\n        this.client.documentsRecipients.createV0({\n          documentId,\n          email: recipient.email,\n          name: recipient.name,\n          role: recipient.role ?? \"SIGNER\",\n        })\n      );\n      recipientIds.push({\n        id: result.recipientId!,\n        email: recipient.email,\n      });\n    }\n\n    \u002F\u002F Step 3: Add fields\n    if (input.fields) {\n      for (const field of input.fields) {\n        const recipientId = recipientIds[field.recipientIndex]?.id;\n        if (!recipientId) continue;\n\n        await withRetry(() =>\n          this.client.documentsFields.createV0({\n            documentId,\n            recipientId,\n            type: field.type,\n            page: field.page,\n            positionX: field.x,\n            positionY: field.y,\n            width: field.width ?? 200,\n            height: field.height ?? 60,\n          })\n        );\n      }\n    }\n\n    \u002F\u002F Step 4: Send if requested\n    if (input.sendImmediately) {\n      await withRetry(() =>\n        this.client.documents.sendV0({ documentId })\n      );\n    }\n\n    return {\n      documentId,\n      status: input.sendImmediately ? \"SENT\" : \"DRAFT\",\n      recipients: recipientIds,\n    };\n  }\n\n  async getDocument(documentId: string): Promise\u003CDocumentResult | null> {\n    const { data, error } = await safeDocumensoCall(() =>\n      this.client.documents.getV0({ documentId })\n    );\n\n    if (error) {\n      if (error.statusCode === 404) return null;\n      throw error;\n    }\n\n    return {\n      documentId: data!.id!,\n      status: data!.status!,\n      recipients:\n        data!.recipients?.map((r) => ({\n          id: r.id!,\n          email: r.email!,\n        })) ?? [],\n    };\n  }\n\n  async deleteDocument(documentId: string): Promise\u003Cboolean> {\n    const { error } = await safeDocumensoCall(() =>\n      this.client.documents.deleteV0({ documentId })\n    );\n    return error === null;\n  }\n}\n\n\u002F\u002F Singleton instance\nlet documentService: DocumentService | null = null;\n\nexport function getDocumentService(): DocumentService {\n  if (!documentService) {\n    documentService = new DocumentService();\n  }\n  return documentService;\n}\n```\n\n### Step 5: Response Validation with Zod\n\n```typescript\n\u002F\u002F src\u002Fdocumenso\u002Ftypes.ts\nimport { z } from \"zod\";\n\nexport const DocumentStatusSchema = z.enum([\n  \"DRAFT\",\n  \"PENDING\",\n  \"COMPLETED\",\n  \"REJECTED\",\n  \"CANCELLED\",\n]);\n\nexport const RecipientSchema = z.object({\n  id: z.string(),\n  email: z.string().email(),\n  name: z.string(),\n  role: z.enum([\"SIGNER\", \"VIEWER\", \"APPROVER\", \"CC\"]),\n  signingStatus: z.enum([\"NOT_SIGNED\", \"SIGNED\", \"REJECTED\"]).optional(),\n});\n\nexport const DocumentSchema = z.object({\n  id: z.string(),\n  title: z.string(),\n  status: DocumentStatusSchema,\n  createdAt: z.string().datetime(),\n  updatedAt: z.string().datetime(),\n  recipients: z.array(RecipientSchema).optional(),\n});\n\nexport type Document = z.infer\u003Ctypeof DocumentSchema>;\nexport type DocumentStatus = z.infer\u003Ctypeof DocumentStatusSchema>;\n\n\u002F\u002F Validate API response\nexport function validateDocument(data: unknown): Document {\n  return DocumentSchema.parse(data);\n}\n```\n\n### Python Patterns\n\n```python\n# src\u002Fdocumenso\u002Fclient.py\nimport os\nfrom typing import Optional\nfrom functools import lru_cache\nfrom documenso_sdk import Documenso\n\n@lru_cache(maxsize=1)\ndef get_documenso_client(\n    api_key: Optional[str] = None,\n    base_url: Optional[str] = None,\n) -> Documenso:\n    \"\"\"Get singleton Documenso client.\"\"\"\n    key = api_key or os.environ.get(\"DOCUMENSO_API_KEY\")\n    if not key:\n        raise ValueError(\"DOCUMENSO_API_KEY is required\")\n\n    return Documenso(\n        api_key=key,\n        server_url=base_url or os.environ.get(\"DOCUMENSO_BASE_URL\"),\n    )\n\n\n# src\u002Fdocumenso\u002Fretry.py\nimport asyncio\nimport random\nfrom typing import TypeVar, Callable, Awaitable\n\nT = TypeVar(\"T\")\n\nasync def with_retry(\n    operation: Callable[[], Awaitable[T]],\n    max_retries: int = 3,\n    base_delay: float = 1.0,\n    max_delay: float = 30.0,\n) -> T:\n    \"\"\"Execute operation with exponential backoff retry.\"\"\"\n    for attempt in range(max_retries + 1):\n        try:\n            return await operation()\n        except Exception as e:\n            status = getattr(e, \"status_code\", 0)\n            is_retryable = status == 429 or status >= 500\n\n            if attempt == max_retries or not is_retryable:\n                raise\n\n            delay = min(base_delay * (2 ** attempt) + random.uniform(0, 0.5), max_delay)\n            print(f\"Attempt {attempt + 1} failed. Retrying in {delay:.1f}s...\")\n            await asyncio.sleep(delay)\n\n    raise RuntimeError(\"Unreachable\")\n```\n\n## Output\n- Type-safe client singleton\n- Robust error handling with retryable classification\n- Automatic retry with exponential backoff\n- Service facade for common operations\n- Runtime validation for API responses\n\n## Error Handling Patterns\n| Pattern | Use Case | Benefit |\n|---------|----------|---------|\n| Safe wrapper | All API calls | Prevents uncaught exceptions |\n| Retry logic | Transient failures | Improves reliability |\n| Type guards | Response validation | Catches API changes |\n| Service facade | Complex workflows | Encapsulates multi-step operations |\n\n## Resources\n- [Documenso SDK Documentation](https:\u002F\u002Fgithub.com\u002Fdocumenso\u002Fsdk-typescript)\n- [Zod Documentation](https:\u002F\u002Fzod.dev\u002F)\n- [Error Handling Best Practices](https:\u002F\u002Fdocs.documenso.com\u002Fdevelopers)\n\n## Next Steps\nApply patterns in `documenso-core-workflow-a` for document creation workflows.\n","# Documenso SDK Patterns\n\n## Overview\nProduction-ready patterns for Documenso SDK usage in TypeScript and Python.\n\n## Prerequisites\n- Completed `documenso-install-auth` setup\n- Familiarity with async\u002Fawait patterns\n- Understanding of error handling best practices\n\n## Instructions\n\n### Step 1: Singleton Client Pattern\n\n```typescript\n\u002F\u002F src\u002Fdocumenso\u002Fclient.ts\nimport { Documenso } from \"@documenso\u002Fsdk-typescript\";\n\nlet instance: Documenso | null = null;\n\nexport interface DocumensoClientConfig {\n  apiKey?: string;\n  baseUrl?: string;\n  timeout?: number;\n}\n\nexport function getDocumensoClient(config?: DocumensoClientConfig): Documenso {\n  if (!instance) {\n    const apiKey = config?.apiKey ?? process.env.DOCUMENSO_API_KEY;\n\n    if (!apiKey) {\n      throw new Error(\n        \"DOCUMENSO_API_KEY environment variable is required\"\n      );\n    }\n\n    instance = new Documenso({\n      apiKey,\n      serverURL: config?.baseUrl ?? process.env.DOCUMENSO_BASE_URL,\n      timeoutMs: config?.timeout ?? 30000,\n    });\n  }\n  return instance;\n}\n\n\u002F\u002F For testing - allows resetting the singleton\nexport function resetDocumensoClient(): void {\n  instance = null;\n}\n```\n\n### Step 2: Type-Safe Error Handling\n\n```typescript\n\u002F\u002F src\u002Fdocumenso\u002Ferrors.ts\nimport { SDKError } from \"@documenso\u002Fsdk-typescript\";\n\nexport class DocumensoError extends Error {\n  constructor(\n    message: string,\n    public readonly code: string,\n    public readonly statusCode: number,\n    public readonly retryable: boolean,\n    public readonly originalError?: Error\n  ) {\n    super(message);\n    this.name = \"DocumensoError\";\n  }\n}\n\nexport function wrapDocumensoError(error: unknown): DocumensoError {\n  if (error instanceof SDKError) {\n    const retryable = error.statusCode >= 500 || error.statusCode === 429;\n    return new DocumensoError(\n      error.message,\n      `DOCUMENSO_${error.statusCode}`,\n      error.statusCode,\n      retryable,\n      error\n    );\n  }\n\n  if (error instanceof Error) {\n    return new DocumensoError(\n      error.message,\n      \"DOCUMENSO_UNKNOWN\",\n      0,\n      false,\n      error\n    );\n  }\n\n  return new DocumensoError(\n    String(error),\n    \"DOCUMENSO_UNKNOWN\",\n    0,\n    false\n  );\n}\n\n\u002F\u002F Safe wrapper for API calls\nexport async function safeDocumensoCall\u003CT>(\n  operation: () => Promise\u003CT>\n): Promise\u003C{ data: T | null; error: DocumensoError | null }> {\n  try {\n    const data = await operation();\n    return { data, error: null };\n  } catch (err) {\n    const error = wrapDocumensoError(err);\n    console.error({\n      code: error.code,\n      message: error.message,\n      statusCode: error.statusCode,\n      retryable: error.retryable,\n    });\n    return { data: null, error };\n  }\n}\n```\n\n### Step 3: Retry Logic with Exponential Backoff\n\n```typescript\n\u002F\u002F src\u002Fdocumenso\u002Fretry.ts\ninterface RetryConfig {\n  maxRetries?: number;\n  baseDelayMs?: number;\n  maxDelayMs?: number;\n  jitterMs?: number;\n}\n\nconst DEFAULT_RETRY_CONFIG: Required\u003CRetryConfig> = {\n  maxRetries: 3,\n  baseDelayMs: 1000,\n  maxDelayMs: 30000,\n  jitterMs: 500,\n};\n\nexport async function withRetry\u003CT>(\n  operation: () => Promise\u003CT>,\n  config: RetryConfig = {}\n): Promise\u003CT> {\n  const { maxRetries, baseDelayMs, maxDelayMs, jitterMs } = {\n    ...DEFAULT_RETRY_CONFIG,\n    ...config,\n  };\n\n  for (let attempt = 0; attempt \u003C= maxRetries; attempt++) {\n    try {\n      return await operation();\n    } catch (error: any) {\n      const statusCode = error.statusCode ?? error.status ?? 0;\n\n      \u002F\u002F Only retry on 429 (rate limit) and 5xx (server errors)\n      const isRetryable = statusCode === 429 || statusCode >= 500;\n\n      if (attempt === maxRetries || !isRetryable) {\n        throw error;\n      }\n\n      const exponentialDelay = baseDelayMs * Math.pow(2, attempt);\n      const jitter = Math.random() * jitterMs;\n      const delay = Math.min(exponentialDelay + jitter, maxDelayMs);\n\n      console.log(\n        `Attempt ${attempt + 1} failed. Retrying in ${delay.toFixed(0)}ms...`\n      );\n      await new Promise((r) => setTimeout(r, delay));\n    }\n  }\n  throw new Error(\"Unreachable\");\n}\n```\n\n### Step 4: Document Service Facade\n\n```typescript\n\u002F\u002F src\u002Fservices\u002Fdocument-service.ts\nimport { getDocumensoClient } from \"..\u002Fdocumenso\u002Fclient\";\nimport { withRetry } from \"..\u002Fdocumenso\u002Fretry\";\nimport { safeDocumensoCall, DocumensoError } from \"..\u002Fdocumenso\u002Ferrors\";\n\nexport interface CreateDocumentInput {\n  title: string;\n  file?: Blob;\n  recipients: Array\u003C{\n    email: string;\n    name: string;\n    role?: \"SIGNER\" | \"VIEWER\" | \"APPROVER\";\n  }>;\n  fields?: Array\u003C{\n    recipientIndex: number;\n    type: \"SIGNATURE\" | \"INITIALS\" | \"NAME\" | \"EMAIL\" | \"DATE\" | \"TEXT\";\n    page: number;\n    x: number;\n    y: number;\n    width?: number;\n    height?: number;\n  }>;\n  sendImmediately?: boolean;\n}\n\nexport interface DocumentResult {\n  documentId: string;\n  status: string;\n  recipients: Array\u003C{ id: string; email: string }>;\n}\n\nexport class DocumentService {\n  private client = getDocumensoClient();\n\n  async createDocument(input: CreateDocumentInput): Promise\u003CDocumentResult> {\n    \u002F\u002F Step 1: Create document\n    const doc = await withRetry(() =>\n      this.client.documents.createV0({\n        title: input.title,\n        file: input.file,\n      })\n    );\n\n    const documentId = doc.documentId!;\n    const recipientIds: Array\u003C{ id: string; email: string }> = [];\n\n    \u002F\u002F Step 2: Add recipients\n    for (const recipient of input.recipients) {\n      const result = await withRetry(() =>\n        this.client.documentsRecipients.createV0({\n          documentId,\n          email: recipient.email,\n          name: recipient.name,\n          role: recipient.role ?? \"SIGNER\",\n        })\n      );\n      recipientIds.push({\n        id: result.recipientId!,\n        email: recipient.email,\n      });\n    }\n\n    \u002F\u002F Step 3: Add fields\n    if (input.fields) {\n      for (const field of input.fields) {\n        const recipientId = recipientIds[field.recipientIndex]?.id;\n        if (!recipientId) continue;\n\n        await withRetry(() =>\n          this.client.documentsFields.createV0({\n            documentId,\n            recipientId,\n            type: field.type,\n            page: field.page,\n            positionX: field.x,\n            positionY: field.y,\n            width: field.width ?? 200,\n            height: field.height ?? 60,\n          })\n        );\n      }\n    }\n\n    \u002F\u002F Step 4: Send if requested\n    if (input.sendImmediately) {\n      await withRetry(() =>\n        this.client.documents.sendV0({ documentId })\n      );\n    }\n\n    return {\n      documentId,\n      status: input.sendImmediately ? \"SENT\" : \"DRAFT\",\n      recipients: recipientIds,\n    };\n  }\n\n  async getDocument(documentId: string): Promise\u003CDocumentResult | null> {\n    const { data, error } = await safeDocumensoCall(() =>\n      this.client.documents.getV0({ documentId })\n    );\n\n    if (error) {\n      if (error.statusCode === 404) return null;\n      throw error;\n    }\n\n    return {\n      documentId: data!.id!,\n      status: data!.status!,\n      recipients:\n        data!.recipients?.map((r) => ({\n          id: r.id!,\n          email: r.email!,\n        })) ?? [],\n    };\n  }\n\n  async deleteDocument(documentId: string): Promise\u003Cboolean> {\n    const { error } = await safeDocumensoCall(() =>\n      this.client.documents.deleteV0({ documentId })\n    );\n    return error === null;\n  }\n}\n\n\u002F\u002F Singleton instance\nlet documentService: DocumentService | null = null;\n\nexport function getDocumentService(): DocumentService {\n  if (!documentService) {\n    documentService = new DocumentService();\n  }\n  return documentService;\n}\n```\n\n### Step 5: Response Validation with Zod\n\n```typescript\n\u002F\u002F src\u002Fdocumenso\u002Ftypes.ts\nimport { z } from \"zod\";\n\nexport const DocumentStatusSchema = z.enum([\n  \"DRAFT\",\n  \"PENDING\",\n  \"COMPLETED\",\n  \"REJECTED\",\n  \"CANCELLED\",\n]);\n\nexport const RecipientSchema = z.object({\n  id: z.string(),\n  email: z.string().email(),\n  name: z.string(),\n  role: z.enum([\"SIGNER\", \"VIEWER\", \"APPROVER\", \"CC\"]),\n  signingStatus: z.enum([\"NOT_SIGNED\", \"SIGNED\", \"REJECTED\"]).optional(),\n});\n\nexport const DocumentSchema = z.object({\n  id: z.string(),\n  title: z.string(),\n  status: DocumentStatusSchema,\n  createdAt: z.string().datetime(),\n  updatedAt: z.string().datetime(),\n  recipients: z.array(RecipientSchema).optional(),\n});\n\nexport type Document = z.infer\u003Ctypeof DocumentSchema>;\nexport type DocumentStatus = z.infer\u003Ctypeof DocumentStatusSchema>;\n\n\u002F\u002F Validate API response\nexport function validateDocument(data: unknown): Document {\n  return DocumentSchema.parse(data);\n}\n```\n\n### Python Patterns\n\n```python\n# src\u002Fdocumenso\u002Fclient.py\nimport os\nfrom typing import Optional\nfrom functools import lru_cache\nfrom documenso_sdk import Documenso\n\n@lru_cache(maxsize=1)\ndef get_documenso_client(\n    api_key: Optional[str] = None,\n    base_url: Optional[str] = None,\n) -> Documenso:\n    \"\"\"Get singleton Documenso client.\"\"\"\n    key = api_key or os.environ.get(\"DOCUMENSO_API_KEY\")\n    if not key:\n        raise ValueError(\"DOCUMENSO_API_KEY is required\")\n\n    return Documenso(\n        api_key=key,\n        server_url=base_url or os.environ.get(\"DOCUMENSO_BASE_URL\"),\n    )\n\n\n# src\u002Fdocumenso\u002Fretry.py\nimport asyncio\nimport random\nfrom typing import TypeVar, Callable, Awaitable\n\nT = TypeVar(\"T\")\n\nasync def with_retry(\n    operation: Callable[[], Awaitable[T]],\n    max_retries: int = 3,\n    base_delay: float = 1.0,\n    max_delay: float = 30.0,\n) -> T:\n    \"\"\"Execute operation with exponential backoff retry.\"\"\"\n    for attempt in range(max_retries + 1):\n        try:\n            return await operation()\n        except Exception as e:\n            status = getattr(e, \"status_code\", 0)\n            is_retryable = status == 429 or status >= 500\n\n            if attempt == max_retries or not is_retryable:\n                raise\n\n            delay = min(base_delay * (2 ** attempt) + random.uniform(0, 0.5), max_delay)\n            print(f\"Attempt {attempt + 1} failed. Retrying in {delay:.1f}s...\")\n            await asyncio.sleep(delay)\n\n    raise RuntimeError(\"Unreachable\")\n```\n\n## Output\n- Type-safe client singleton\n- Robust error handling with retryable classification\n- Automatic retry with exponential backoff\n- Service facade for common operations\n- Runtime validation for API responses\n\n## Error Handling Patterns\n| Pattern | Use Case | Benefit |\n|---------|----------|---------|\n| Safe wrapper | All API calls | Prevents uncaught exceptions |\n| Retry logic | Transient failures | Improves reliability |\n| Type guards | Response validation | Catches API changes |\n| Service facade | Complex workflows | Encapsulates multi-step operations |\n\n## Resources\n- [Documenso SDK Documentation](https:\u002F\u002Fgithub.com\u002Fdocumenso\u002Fsdk-typescript)\n- [Zod Documentation](https:\u002F\u002Fzod.dev\u002F)\n- [Error Handling Best Practices](https:\u002F\u002Fdocs.documenso.com\u002Fdevelopers)\n\n## Next Steps\nApply patterns in `documenso-core-workflow-a` for document creation workflows.\n",{"name":5,"author":48,"license":15,"version":49,"description":50,"allowed-tools":51},"Jeremy Longshore \u003Cjeremy@intentsolutions.io>","1.0.0","Apply production-ready Documenso SDK patterns for TypeScript and Python.\nUse when implementing Documenso integrations, refactoring SDK usage,\nor establishing team coding standards for Documenso.\nTrigger with phrases like \"documenso SDK patterns\", \"documenso best practices\",\n\"documenso code patterns\", \"idiomatic documenso\".\n","Read, Write, Edit","main","90b50ed0778745a80fd5bfb13b1843d45b9e82f7","tests\u002Fext_conformance\u002Fartifacts\u002Fplugins-community\u002Fplugins\u002Fsaas-packs\u002Fdocumenso-pack\u002Fskills\u002Fdocumenso-sdk-patterns",234,"https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust","https:\u002F\u002Fgithub.com\u002FDicklesworthstone","https:\u002F\u002Fwww.jeffreyemanuel.com\u002F","Building in NY","2026-11-13 11:50:27",{"source":8,"skillName":5,"commands":62,"prompt":72,"zipAvailable":73},[63,66,69],{"tool":64,"command":65},"npx","npx skills add Dicklesworthstone\u002Fpi_agent_rust --skill documenso-sdk-patterns",{"tool":67,"command":68},"bunx","bunx skills add Dicklesworthstone\u002Fpi_agent_rust --skill documenso-sdk-patterns",{"tool":70,"command":71},"pnpm","pnpm dlx skills add Dicklesworthstone\u002Fpi_agent_rust --skill documenso-sdk-patterns","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Fplugins-community\u002Fplugins\u002Fsaas-packs\u002Fdocumenso-pack\u002Fskills\u002Fdocumenso-sdk-patterns\u002FSKILL.md",true,[],[76,116,141,167,193,214],{"skillNo":77,"name":78,"nameZh":6,"description":79,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":80,"licenseFileUrl":81,"sourceHtmlUrl":82,"categories":83,"keywords":89,"publishedAt":43},"01M6FEGPPK0ZTKCCM0R3E3KS33","web-artifacts-builder","Suite of tools for creating elaborate, multi-component claude.ai HTML artifacts using modern frontend web technologies (React, Tailwind CSS, shadcn\u002Fui). Use for complex artifacts requiring state management, routing, or shadcn\u002Fui components - not for simple single-file HTML\u002FJSX artifacts.","Apache-2.0","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Futilities\u002Fweb-artifacts-builder\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Futilities\u002Fweb-artifacts-builder\u002FSKILL.md",[84],{"categoryNo":85,"categoryName":86,"taxonomyType":87,"taxonomyTypeDesc":88},"01JBSEEDCAT000000000000009","前端开发","SCENARIO","使用场景",[90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,23,108,109,110,111,112,113,114,115],"web artifacts builder","web","artifacts","artifacts builder","builder","frontend","css","components","react","component","routing","state management","state","shadcn","file","ui","Tools","Tailwind","html","single-file","simple","ui components","multi","for","jsx","suite",{"skillNo":117,"name":118,"nameZh":6,"description":119,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":80,"licenseFileUrl":120,"sourceHtmlUrl":121,"categories":122,"keywords":127,"publishedAt":43},"01M6FEE9VTK1W4GY21AKREJXRG","webapp-testing","Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fsecurity\u002Fwebapp-testing\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fsecurity\u002Fwebapp-testing\u002FSKILL.md",[123,126],{"categoryNo":124,"categoryName":125,"taxonomyType":87,"taxonomyTypeDesc":88},"01JBSEEDCAT000000000000008","测试与质量",{"categoryNo":85,"categoryName":86,"taxonomyType":87,"taxonomyTypeDesc":88},[128,129,130,95,131,132,91,133,134,135,105,136,137,138,139,140,113],"testing","webapp testing","webapp","screenshots","playwright","debugging","toolkit","browser","logs","applications","behavior","local","with",{"skillNo":142,"name":143,"nameZh":6,"description":144,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":145,"sourceHtmlUrl":146,"categories":147,"keywords":148,"publishedAt":43},"01M6FE20F7ZTQBFWKF4MPJ2SBX","neuropixels-analysis","Neuropixels neural recording analysis. Load SpikeGLX\u002FOpenEphys data, preprocess, motion correction, Kilosort4 spike sorting, quality metrics, Allen\u002FIBL curation, AI-assisted visual analysis, for Neuropixels 1.0\u002F2.0 extracellular electrophysiology. Use when working with neural recordings, spike sorting, extracellular electrophysiology, or when the user mentions Neuropixels, SpikeGLX, Open Ephys, Kilosort, quality metrics, or unit curation.","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fscientific\u002Fneuropixels-analysis\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fscientific\u002Fneuropixels-analysis\u002FSKILL.md",[],[149,150,151,152,153,154,155,156,23,157,158,159,160,161,162,163,140,164,165,166,113,24],"analysis","neuropixels analysis","spike","metrics","data","open","quality","user","recordings","Neural","motion","visual","quality metrics","unit","sorting","curation","recording","load",{"skillNo":168,"name":169,"nameZh":6,"description":170,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":80,"licenseFileUrl":171,"sourceHtmlUrl":172,"categories":173,"keywords":177,"publishedAt":43},"01M6FDP0PDK6F1GGX395C7625Z","skill-creator","Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Claude's capabilities with specialized knowledge, workflows, or tool integrations.","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fproductivity\u002Fskill-creator\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fproductivity\u002Fskill-creator\u002FSKILL.md",[174],{"categoryNo":175,"categoryName":176,"taxonomyType":87,"taxonomyTypeDesc":88},"01JBSEEDCAT000000000000023","Skill与智能体开发",[178,179,180,181,182,183,184,185,37,186,187,188,189,190,140,113,191,24,192],"skill creator","creator","workflows","create","update","capabilities","knowledge","tool","specialized","guide","new skill","existing skill","users","Existing","new",{"skillNo":194,"name":195,"nameZh":6,"description":196,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":197,"sourceHtmlUrl":198,"categories":199,"keywords":201,"publishedAt":43},"01M6FDM8Y7VK7NYG9SM7QPA3PW","notebooklm","Use this skill to query your Google NotebookLM notebooks directly from Claude Code for source-grounded, citation-backed answers from Gemini. Browser automation, library management, persistent auth. Drastically reduced hallucinations through document-only responses.","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fproductivity\u002Fnotebooklm\u002FLICENSE","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fproductivity\u002Fnotebooklm\u002FSKILL.md",[200],{"categoryNo":175,"categoryName":176,"taxonomyType":87,"taxonomyTypeDesc":88},[195,202,203,204,205,206,41,135,207,208,23,209,210,211,212,113,213],"google","browser automation","gemini","automation","auth","library","management","only","document","query","Citation","source",{"skillNo":215,"name":216,"nameZh":6,"description":217,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":80,"licenseFileUrl":218,"sourceHtmlUrl":219,"categories":220,"keywords":227,"publishedAt":43},"01M6FDHPXE10J3XGTCH5HBHS1M","slack-gif-creator","Knowledge and utilities for creating animated GIFs optimized for Slack. Provides constraints, validation tools, and animation concepts. Use when users request animated GIFs for Slack like \"make me a GIF of X doing Y for Slack.\"","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fenterprise-communication\u002Fslack-gif-creator\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fenterprise-communication\u002Fslack-gif-creator\u002FSKILL.md",[221,224],{"categoryNo":222,"categoryName":223,"taxonomyType":87,"taxonomyTypeDesc":88},"01JBSEEDCAT000000000000005","邮件与沟通",{"categoryNo":225,"categoryName":226,"taxonomyType":87,"taxonomyTypeDesc":88},"01JBSEEDCAT000000000000016","图像与多媒体",[228,229,230,179,231,106,232,233,184,23,234,190,235,236,237,113,238,24],"slack gif creator","slack","gif","validation","constraints","Animation","me","make","Utilities","concepts","request"]