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
| Code | Meaning | When It Happens |
|---|---|---|
200 | OK | Request succeeded |
201 | Created | Resource created successfully |
400 | Bad Request | Invalid input or validation error |
401 | Unauthorized | Missing or invalid API key |
403 | Forbidden | No permission or wrong subscription tier |
404 | Not Found | Resource doesn’t exist |
429 | Too Many Requests | Rate limit exceeded |
500 | Internal Server Error | Server-side error |
Error Codes
| Code | Description |
|---|---|
UNAUTHORIZED | Invalid or missing API key |
FORBIDDEN | Insufficient permissions or tier |
NOT_FOUND | Resource not found |
VALIDATION_ERROR | Request validation failed |
RATE_LIMITED | Rate limit exceeded |
QUOTA_EXCEEDED | Monthly quota exceeded |
INVALID_STATE | Operation not valid for current state |
INTERNAL_ERROR | Server-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
Authorizationheader 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:
- Wait a moment and retry
- Check status.penvio.io for outages
- 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
- Check the error code - Use the code to determine how to handle the error
- Read the message - Error messages are descriptive
- Check validation details - For validation errors, check which fields failed
- Verify API key - Ensure your key is valid and not revoked
- Check rate limits - Review headers for remaining requests
- 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
- Authentication - Verify your API key setup
- Rate Limits - Understand rate limit errors
Last updated on