API Fundamentals
This guide covers the fundamental concepts of working with the Paysera Recurring Billing API, including REST principles, data formats, response handling, and error management.
REST Architecture
The Recurring Billing API is built on REST (Representational State Transfer) principles, using standard HTTP methods for all operations:
- GET - Retrieve resources (payment requests, notifications)
- POST - Create new resources (payment requests)
- PUT - Update resources (authorize payments, mark notifications as read)
- DELETE - Remove resources (if applicable)
All API endpoints follow a consistent URL structure:
https://checkout-eu-a.paysera.com/checkout/rest/v1/{resource}
https://checkout-eu-a.paysera.com/notification/rest/v1/{resource}
Request Format
Content Type
All POST and PUT requests must include:
- Content-Type:
application/json - Encoding: UTF-8
Request Body
Request body should be valid JSON:
{
"business_id": "Opb2XVb-gEh4aGcR09Ko5Wb8V_6vueDM",
"order_id": "ORDER_12345",
"price": {
"amount": 999,
"currency": "EUR"
},
"description": "Monthly subscription"
}
Authentication
All API requests require MAC (Message Authentication Code) authentication. See Getting Started for credential setup.
Response Format
Successful Responses
Successful requests return:
- HTTP Status Code:
200 - Content-Type:
application/json - Body: JSON-encoded data
Example response:
{
"id": "abc123payment",
"status": "captured",
"price_paid": {
"amount": 999,
"currency": "EUR"
},
"issued_token": "vLUGdTtnDjQs7Yv0fjYQyYfG60m"
}
The recurring billing token is delivered as the issued_token field on the Payment Request resource. It becomes available after the initial Payment Request (created with token_strategy: "required") is captured. In notification payloads it lives at data.payment_request.issued_token, since the notification wraps the Payment Request inside data.payment_request.
Optional Elements
The API omits optional elements entirely rather than returning null values. Always check if a field exists before accessing it.
Example:
// ❌ Wrong - may cause errors if token doesn't exist
const token = response.token;
// ✅ Correct - safely handle optional fields
const token = response.token || null;
// or
if (response.hasOwnProperty('token')) {
const token = response.token;
}
HTTP Status Codes
The API uses standard HTTP status codes to indicate the success or failure of requests:
| Status Code | Meaning | When It Occurs |
|---|---|---|
| 200 | Success | Request completed successfully |
| 400 | Bad Request | Invalid request format or missing required parameters |
| 401 | Unauthorized | Authentication failed (invalid credentials) |
| 403 | Forbidden | Authenticated but insufficient permissions |
| 404 | Not Found | Requested resource does not exist |
| 406 | Not Acceptable | Unsupported response format requested |
| 409 | Conflict | Invalid resource state (e.g., payment already processed) |
| 500 | Internal Server Error | Server-side error occurred |
Always check the response status code before processing the response body. The status code determines whether the response contains data or an error.
Error Handling
Error Response Structure
When an error occurs (status code 4xx or 5xx), the response contains an error object:
{
"error": "invalid_parameters",
"error_description": "Invalid price amount specified",
"error_uri": "https://developers.paysera.com/en/checkout/errors"
}
Fields:
- error (required) - Machine-readable error code
- error_description (optional) - Human-readable error message
- error_uri (optional) - URL to documentation about the error
Common Error Codes
The following error codes are available across all API methods:
| Error Code | HTTP Status | Description |
|---|---|---|
invalid_request | 400 | Malformed request syntax |
invalid_parameters | 400 | Missing or invalid request parameters |
invalid_state | 409 | Resource in invalid state for requested operation |
unauthorized | 401 | Authentication credentials missing or invalid |
forbidden | 403 | Authenticated but lacking required permissions |
not_found | 404 | Requested resource does not exist |
not_acceptable | 406 | Requested response format not supported |
internal_server_error | 500 | Unexpected server-side error |
Error Handling Best Practices
- Check Status Code First
if (response.status === 200) {
// Process successful response
const data = await response.json();
} else {
// Handle error
const error = await response.json();
console.error(`Error ${error.error}: ${error.error_description}`);
}
- Handle Specific Errors
try {
const response = await fetch(url, options);
const data = await response.json();
if (response.status === 401) {
// Authentication failed - refresh credentials
} else if (response.status === 409) {
// Conflict - payment already processed
} else if (response.status === 400) {
// Validation error - check parameters
console.error(data.error_description);
}
} catch (error) {
// Network error
console.error('Network error:', error);
}
- Log Error Details
Always log error details for debugging:
- Request URL and method
- Request parameters
- Response status code
- Error code and description
Response Validation
Always Validate
Before using response data, validate:
- Status Code
if (response.status !== 200) {
throw new Error('Request failed');
}
- Required Fields
if (!data.id || !data.status) {
throw new Error('Invalid response structure');
}
- Field Types
if (typeof data.price.amount !== 'number') {
throw new Error('Invalid price format');
}
Handle Missing Optional Fields
// Safe access to optional fields
const token = data.token || null;
const description = data.error_description || 'Unknown error';
const capturedAt = data.captured_at ? new Date(data.captured_at) : null;
API Versioning
The API uses URL-based versioning:
https://checkout-eu-a.paysera.com/checkout/rest/v1/...
Current version: v1
Breaking changes will result in a new API version. Non-breaking changes may be added to the current version.
Rate Limiting
While not strictly enforced, please follow these best practices:
- Don't make excessive parallel requests
- Implement exponential backoff for retries
- Cache responses when appropriate
- Use webhooks/callbacks instead of polling