Skip to Content

Error Handling

Learn how to handle errors from the Penvio E-Sign API.

API access requires a Business plan or higher.

Error Response Format

All error responses follow a consistent JSON format:

{ "error": { "code": "ERROR_CODE", "message": "Human-readable error message" } }

For validation errors, field-level details are included:

{ "error": { "code": "VALIDATION_ERROR", "message": "Invalid request", "details": { "signers": ["At least one signer is required"], "email": ["Invalid email format"] } } }

HTTP Status Codes

CodeMeaningWhen It Happens
200OKRequest succeeded
201CreatedResource created successfully
400Bad RequestInvalid input or validation error
401UnauthorizedMissing or invalid API key
403ForbiddenNo permission or wrong subscription tier
404Not FoundResource doesn’t exist
429Too Many RequestsRate limit exceeded
500Internal Server ErrorServer-side error

Error Codes

CodeDescription
UNAUTHORIZEDInvalid or missing API key
FORBIDDENInsufficient permissions or tier
NOT_FOUNDResource not found
VALIDATION_ERRORRequest validation failed
RATE_LIMITEDRate limit exceeded
QUOTA_EXCEEDEDMonthly quota exceeded
INVALID_STATEOperation not valid for current state
INTERNAL_ERRORServer-side error

Common Errors

400 Bad Request

Validation Error

{ "error": { "code": "VALIDATION_ERROR", "message": "Invalid request", "details": { "signers": ["At least one signer is required"] } } }

Invalid State

{ "error": { "code": "INVALID_STATE", "message": "Envelope is not in DRAFT status" } }

401 Unauthorized

{ "error": { "code": "UNAUTHORIZED", "message": "Invalid or missing API key" } }

Possible causes:

  • No Authorization header provided
  • Invalid API key format
  • API key has been revoked

403 Forbidden

{ "error": { "code": "FORBIDDEN", "message": "Enterprise subscription required" } }

Possible causes:

  • Organization doesn’t have an Enterprise subscription
  • BYOB storage is not configured
  • API key doesn’t have permission for the resource

Quota Exceeded

{ "error": { "code": "QUOTA_EXCEEDED", "message": "Monthly signature quota exceeded" } }

404 Not Found

{ "error": { "code": "NOT_FOUND", "message": "Template not found" } }

Possible causes:

  • Resource ID doesn’t exist
  • Resource was deleted
  • Resource belongs to another organization

429 Too Many Requests

{ "error": { "code": "RATE_LIMITED", "message": "Too many requests" } }

Headers include:

  • X-RateLimit-Reset: Unix timestamp when you can retry

500 Internal Server Error

{ "error": { "code": "INTERNAL_ERROR", "message": "Internal server error" } }

If you receive this error:

  1. Wait a moment and retry
  2. Check status.penvio.io  for outages
  3. Contact support if the issue persists

Error Handling Best Practices

JavaScript/TypeScript

interface ApiError { error: { code: string; message: string; details?: Record<string, string[]>; }; } async function apiRequest<T>(path: string, options?: RequestInit): Promise<T> { const response = await fetch(`https://penvio.io/api/v1${path}`, { ...options, headers: { 'Authorization': `Bearer ${process.env.PENVIO_API_KEY}`, 'Content-Type': 'application/json', ...options?.headers, }, }); if (!response.ok) { const { error }: ApiError = await response.json(); switch (error.code) { case 'UNAUTHORIZED': throw new Error('Authentication failed. Check your API key.'); case 'FORBIDDEN': throw new Error('Access denied. Enterprise subscription required.'); case 'NOT_FOUND': throw new Error(`Resource not found: ${error.message}`); case 'RATE_LIMITED': const resetTime = response.headers.get('X-RateLimit-Reset'); throw new Error(`Rate limited. Retry after ${resetTime}`); case 'VALIDATION_ERROR': const details = Object.entries(error.details || {}) .map(([field, msgs]) => `${field}: ${msgs.join(', ')}`) .join('; '); throw new Error(`Validation failed: ${details}`); default: throw new Error(error.message || 'An error occurred'); } } return response.json(); }

Python

import os import requests from typing import Optional, Dict, List class ApiError(Exception): def __init__( self, code: str, message: str, details: Optional[Dict[str, List[str]]] = None ): self.code = code self.message = message self.details = details super().__init__(message) def api_request(method: str, path: str, **kwargs): response = requests.request( method, f'https://penvio.io/api/v1{path}', headers={ 'Authorization': f'Bearer {os.environ["PENVIO_API_KEY"]}', 'Content-Type': 'application/json', }, **kwargs ) if not response.ok: data = response.json() error = data.get('error', {}) raise ApiError( code=error.get('code', 'UNKNOWN'), message=error.get('message', 'Unknown error'), details=error.get('details') ) return response.json()

Debugging Tips

  1. Check the error code - Use the code to determine how to handle the error
  2. Read the message - Error messages are descriptive
  3. Check validation details - For validation errors, check which fields failed
  4. Verify API key - Ensure your key is valid and not revoked
  5. Check rate limits - Review headers for remaining requests
  6. Check status page - Look for ongoing incidents at status.penvio.io 

Include the X-Request-ID header value when contacting support. This helps us trace your specific request.

Next Steps

Last updated on