Skip to main content

API Fundamentals

Essential concepts and conventions for working with the Paysera Delivery API.

Base URL

All API requests should be made to:

https://delivery-api.paysera.com/rest/v1/
Service Availability

Delivery API is currently available only in Lithuania (LT), Latvia (LV), and Estonia (EE).

REST Principles

The Paysera API follows REST principles using HTTP verbs:

  • GET - Retrieve resources
  • POST - Create new resources
  • PUT - Update entire resource
  • DELETE - Remove resources

Request bodies in POST/PUT operations use JSON format with UTF-8 encoding. All responses return JSON-encoded data.

Response Handling

HTTP status code 200 is returned for successful requests.

Optional Elements

Optional elements in responses are omitted entirely rather than returned as null values. This reduces payload size and simplifies parsing.

Authentication

The API uses MAC (Message Authentication Code) access authentication based on the OAuth 2.0 specification.

Credential Mapping

Map your Paysera credentials to MAC authentication parameters:

  • project_id → MAC identifier (mac_id)
  • sign_password → MAC secret (mac_secret)

These credentials are obtained from your Project Settings in the Paysera dashboard (see Obtaining Credentials).

MAC Header Format

Authorization: MAC id="123456",
ts="1700574800",
nonce="n123abc",
mac="computed_signature_here"

MAC Authentication Parameters

ParameterDescriptionRequired
idYour project_id✅ Yes
tsCurrent Unix timestamp in seconds✅ Yes
nonceUnique random string for this request✅ Yes
macHMAC-SHA256 signature of the request✅ Yes

Generating MAC Signature

The MAC signature is computed as follows:

  1. Create the signature base string:

    {timestamp}\n{nonce}\n{http_method}\n{request_uri}\n{host}\n{port}\n\n
  2. Compute HMAC-SHA256:

    mac = Base64(HMAC-SHA256(mac_secret, signature_base_string))

Example MAC Authentication Implementation

const crypto = require('crypto');

function generateMacAuth(method, uri, host, port, projectId, signPassword) {
const timestamp = Math.floor(Date.now() / 1000);
const nonce = crypto.randomBytes(8).toString('hex');

// Create signature base string
const baseString = [
timestamp,
nonce,
method.toUpperCase(),
uri,
host,
port,
'',
''
].join('\n');

// Compute HMAC-SHA256 signature
const mac = crypto
.createHmac('sha256', signPassword)
.update(baseString)
.digest('base64');

// Return Authorization header value
return `MAC id="${projectId}", ts="${timestamp}", nonce="${nonce}", mac="${mac}"`;
}

// Usage example
const authHeader = generateMacAuth(
'POST',
'/rest/v1/orders',
'delivery-api.paysera.com',
443,
'123456', // your project_id
'your_sign_password' // your sign_password
);

console.log('Authorization:', authHeader);
Security Notes
  • Keep your sign_password secure and never expose it in client-side code
  • Store credentials as environment variables in your application
  • Generate a new unique nonce for each request
  • The timestamp must be current (servers may reject requests with timestamps too far in the past or future)

REST Principles

The API follows REST architectural principles:

  • Uses standard HTTP verbs (GET, POST, PUT, DELETE)
  • Resource-based URLs
  • Stateless operations
  • JSON for data exchange

Request Format

Required Headers

All requests must include these headers:

Authorization: MAC id="123456", ts="1700574800", nonce="n123abc", mac="signature_here"
Content-Type: application/json; charset=utf-8
Accept: application/json

Optional Headers

X-Request-ID: unique-request-id-123
Accept-Language: en
User-Agent: YourApp/1.0
Character Encoding

All requests must use UTF-8 encoding for proper handling of international characters

Response Format

Response Convention

Optional elements in responses are omitted entirely rather than returned as null values. This reduces payload size and simplifies parsing.

Successful Response

Single Resource

{
"id": "DEL-789456",
"order_id": "ORD-12345",
"status": "in_transit",
"tracking_number": "PS123456789LT",
"courier": "dpd",
"created_at": "2025-11-21T10:00:00Z",
"updated_at": "2025-11-21T14:30:00Z",
"estimated_delivery": "2025-11-22T18:00:00Z",
"pickup": {
"name": "Sender Name",
"address": "Street 1",
"city": "Vilnius",
"postal_code": "01001",
"country": "LT"
},
"delivery": {
"name": "Recipient Name",
"address": "Street 2",
"city": "Kaunas",
"postal_code": "44001",
"country": "LT"
}
}

List Response

{
"data": [
{
"id": "DEL-789456",
"order_id": "ORD-12345",
"status": "delivered"
},
{
"id": "DEL-789457",
"order_id": "ORD-12346",
"status": "in_transit"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 150,
"pages": 8
}
}
Error Response

Error Structure

Error responses contain three possible fields:

{
"error": "invalid_request",
"error_description": "The request contains invalid data",
"error_uri": "https://developers.paysera.com/errors/invalid_request"
}

Error Response Fields

FieldRequiredDescription
error✅ YesRequired code identifying the error type
error_description❌ NoOptional human-readable explanation
error_uri❌ NoOptional reference link to error documentation

HTTP Status Codes

Common HTTP status codes returned by the API:

CodeMeaningCommon Causes
200SuccessRequest processed successfully
400Bad RequestInvalid request format or malformed parameters
401UnauthorizedAuthentication failed or missing credentials
403ForbiddenValid credentials but insufficient permissions
404Not FoundResource doesn't exist or endpoint not found
409ConflictInvalid state transition for resource
500Internal Server ErrorServer-side error, temporary issue

HTTP Methods

MethodUsageIdempotent
GETRetrieve resources✅ Yes
POSTCreate new resources❌ No
PUTUpdate entire resource✅ Yes
PATCHUpdate partial resource❌ No
DELETERemove resources✅ Yes

Method Examples

# Get single order
GET /orders/DEL-789456

# List orders
GET /orders

# Track package
GET /track/PS123456789LT

# Get order events
GET /orders/DEL-789456/events

HTTP Status Codes

Status Code Checking

Always check the HTTP status code first to determine whether the response contains success data or an error object. Success responses return 200 OK for most operations.

Success Codes

CodeMeaningUsage
200OKRequest succeeded, data returned
201CreatedResource created successfully
204No ContentRequest succeeded, no content to return

Client Error Codes

CodeMeaningCommon Causes
400Bad RequestInvalid request format, malformed JSON
401UnauthorizedMissing or invalid authentication credentials
403ForbiddenValid credentials but insufficient permissions
404Not FoundResource doesn't exist or wrong endpoint
409ConflictInvalid state transition or duplicate resource
422Unprocessable EntityRequest valid but contains semantic errors
429Too Many RequestsRate limit exceeded

Server Error Codes

CodeMeaningAction
500Internal Server ErrorTemporary issue, retry with exponential backoff
502Bad GatewayUpstream service issue, retry later
503Service UnavailableService maintenance or overload
504Gateway TimeoutRequest processing timeout, retry

Idempotency

Using Idempotency Keys

Prevent duplicate operations by including an idempotency key:

POST /orders
Idempotency-Key: unique-key-123
Content-Type: application/json

{
"order_id": "ORD-12345",
...
}

Key Guidelines

  1. Generate unique keys for each distinct operation
  2. Reuse the same key when retrying failed requests
  3. Keys are valid for 24 hours after first use
  4. Use UUIDs or similar unique identifiers

Example Implementation

const { v4: uuidv4 } = require('uuid');

async function createOrderWithIdempotency(orderData) {
const idempotencyKey = uuidv4();

// Generate MAC authentication header (see authentication section above)
const authHeader = generateMacAuth('POST', '/rest/v1/orders', 'delivery-api.paysera.com', 443, projectId, signPassword);

const response = await fetch('https://delivery-api.paysera.com/rest/v1/orders', {
method: 'POST',
headers: {
'Authorization': authHeader,
'Content-Type': 'application/json',
'Idempotency-Key': idempotencyKey
},
body: JSON.stringify(orderData)
});

// If request fails, retry with same idempotency key
if (!response.ok && response.status >= 500) {
return retryWithSameKey(idempotencyKey, orderData);
}

return response.json();
}

Webhooks

Webhook Configuration

Webhook Payload

{
"id": "evt_123456",
"type": "delivery.status_changed",
"created_at": "2025-11-21T14:30:00Z",
"data": {
"id": "DEL-789456",
"order_id": "ORD-12345",
"old_status": "in_transit",
"new_status": "delivered",
"tracking_number": "PS123456789LT"
}
}

Webhook Headers

POST /your-webhook-endpoint
Content-Type: application/json
X-Paysera-Signature: sha256=abc123...
X-Paysera-Event: delivery.status_changed
X-Paysera-Delivery-ID: evt_123456

Event Types

EventDescription
delivery.createdNew delivery order created
delivery.status_changedDelivery status updated
delivery.deliveredPackage delivered successfully
delivery.failedDelivery attempt failed
delivery.returnedPackage returned to sender
delivery.cancelledOrder cancelled

Signature Verification

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');

return `sha256=${expectedSignature}` === signature;
}

Rate Limiting

Understanding Rate Limits

Rate Limit Headers

Every response includes rate limit information:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 85
X-RateLimit-Reset: 1700574800

Limits by Tier

TierRequests/minRequests/hourDaily
Standard1003,00050,000
Business30010,000200,000
EnterpriseCustomCustomCustom

Handling Rate Limits

async function handleRateLimitedRequest(requestFn) {
const response = await requestFn();

if (response.status === 429) {
const resetTime = response.headers.get('X-RateLimit-Reset');
const waitTime = (resetTime * 1000) - Date.now();

console.log(`Rate limited. Waiting ${waitTime}ms`);
await new Promise(resolve => setTimeout(resolve, waitTime));

// Retry the request
return requestFn();
}

return response;
}

Best Practices

  1. Cache responses when possible
  2. Batch operations to reduce API calls
  3. Implement exponential backoff for retries
  4. Monitor rate limit headers proactively
  5. Use webhooks instead of polling

Data Formats

Standard Formats

Address Format

{
"name": "John Doe",
"company": "Optional Company Name",
"address": "Street Name 123",
"address2": "Apartment 4B",
"city": "Vilnius",
"state": "Optional State/Province",
"postal_code": "01001",
"country": "LT",
"phone": "+37060000001",
"email": "john@example.com"
}

Parcel Specifications

{
"weight": 2.5, // kg
"dimensions": {
"length": 30, // cm
"width": 20, // cm
"height": 15 // cm
},
"value": 99.99, // EUR
"description": "Electronics",
"fragile": true,
"insurance": true,
"reference": "SKU-12345"
}

Date/Time Format

All dates use ISO 8601 format with UTC timezone:

2025-11-21T14:30:00Z

For date ranges:

{
"from": "2025-11-01T00:00:00Z",
"to": "2025-11-30T23:59:59Z"
}

Versioning

The API uses URL versioning:

https://delivery-api.paysera.com/rest/v1/...

Version Policy

  • Current version: v1
  • Deprecation notice: 6 months minimum
  • Sunset period: 12 months after deprecation
  • Breaking changes: New version only
  • Non-breaking changes: Added to current version

Checking API Version

curl -X GET 'https://delivery-api.paysera.com/version' \
-H 'Authorization: MAC id="123456", ts="1700574800", nonce="abc123", mac="..."'

Response:

{
"version": "v1",
"released": "2024-01-01",
"deprecated": false,
"sunset_date": null
}

Testing

Testing Strategy

Sample Test

describe('Delivery API', () => {
test('should create order with valid data', async () => {
const order = await api.createOrder(validOrderData);

expect(order).toHaveProperty('id');
expect(order).toHaveProperty('tracking_number');
expect(order.status).toBe('created');
});

test('should handle validation errors', async () => {
const invalidData = { ...validOrderData, phone: '123' };

await expect(api.createOrder(invalidData))
.rejects.toThrow('validation_error');
});

test('should retry on server errors', async () => {
// Mock server error then success
mockAPI.onFirstCall().returns(500);
mockAPI.onSecondCall().returns(200);

const order = await api.createOrder(validOrderData);
expect(order).toBeDefined();
expect(mockAPI.callCount).toBe(2);
});
});

Support

Need help with complex integrations?

Contact: tech_support@paysera.com