Skip to main content

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"
}
info

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

Important

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 CodeMeaningWhen It Occurs
200SuccessRequest completed successfully
400Bad RequestInvalid request format or missing required parameters
401UnauthorizedAuthentication failed (invalid credentials)
403ForbiddenAuthenticated but insufficient permissions
404Not FoundRequested resource does not exist
406Not AcceptableUnsupported response format requested
409ConflictInvalid resource state (e.g., payment already processed)
500Internal Server ErrorServer-side error occurred
Important

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 CodeHTTP StatusDescription
invalid_request400Malformed request syntax
invalid_parameters400Missing or invalid request parameters
invalid_state409Resource in invalid state for requested operation
unauthorized401Authentication credentials missing or invalid
forbidden403Authenticated but lacking required permissions
not_found404Requested resource does not exist
not_acceptable406Requested response format not supported
internal_server_error500Unexpected server-side error

Error Handling Best Practices

  1. 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}`);
}
  1. 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);
}
  1. 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:

  1. Status Code
if (response.status !== 200) {
throw new Error('Request failed');
}
  1. Required Fields
if (!data.id || !data.status) {
throw new Error('Invalid response structure');
}
  1. 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

Note

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