API Reference
REST API for integrating Deprixa Plus with external systems — e-commerce platforms, ERPs, mobile apps, or custom workflows.
API Tokens are scoped to a user account
API tokens inherit the permissions of the user account they belong to. To grant API access to an external system, create a dedicated user account with the minimum required role, then generate a token for that account. Revoking the token or deactivating the user immediately invalidates all access.
Base URL & Headers
https://yourdomain.com/api/v1
All requests must include the following headers:
| Header | Value | Required |
|---|---|---|
Authorization | Bearer {token} | Yes (all endpoints except token creation) |
Accept | application/json | Yes |
Content-Type | application/json | Yes (POST/PUT/PATCH requests) |
X-Branch-ID | Integer branch ID | Optional — defaults to the token owner's primary branch |
Authentication
Deprixa Plus uses Laravel Sanctum for API token authentication. Tokens are long-lived and must be stored securely by the integrating application. Tokens do not expire automatically — they are revoked by the user or an admin.
/api/v1/tokens
Create an API token
Returns a plaintext token. Store it immediately — it is only shown once.
Request body:
{
"email": "api-user@yourcompany.com",
"password": "secret",
"token_name": "e-commerce-integration",
"abilities": ["shipments:read", "shipments:write", "clients:read"]
}
Available abilities:
| Ability | Description |
|---|---|
* | Full access (all abilities) — requires Admin+ role |
shipments:read | List and retrieve shipments |
shipments:write | Create and update shipments |
clients:read | List and retrieve clients |
clients:write | Create and update clients |
invoices:read | List and retrieve invoices |
invoices:write | Create and update invoices |
tracking:read | Public tracking access (no auth required on the public endpoint) |
reports:read | Access report data endpoints |
Response 200:
{
"token": "1|eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
"token_id": 12,
"name": "e-commerce-integration",
"abilities": ["shipments:read", "shipments:write", "clients:read"],
"created_at": "2026-04-01T08:00:00Z",
"expires_at": null
}
/api/v1/tokens/{id}
Revoke a token
Permanently revokes the specified token. All subsequent requests using this token will receive 401 Unauthorized.
Response: 204 No Content
/api/v1/tokens
List all tokens for the authenticated user
Returns all active tokens. The plaintext token value is never returned — only metadata.
{
"data": [
{
"id": 12,
"name": "e-commerce-integration",
"abilities": ["shipments:read", "shipments:write", "clients:read"],
"last_used_at": "2026-04-03T14:22:00Z",
"created_at": "2026-04-01T08:00:00Z"
}
]
}
Shipments
/api/v1/shipments
List shipments
Required ability: shipments:read
Query parameters:
| Parameter | Type | Description |
|---|---|---|
status | string | Filter by status code: created, picked_up, in_transit, out_for_delivery, delivered, exception, on_hold, returned |
courier_id | integer | Filter by assigned courier |
client_id | integer | Filter by sender client |
branch_id | integer | Filter by branch |
date_from | date (Y-m-d) | Created at or after this date |
date_to | date (Y-m-d) | Created at or before this date |
search | string | Search by tracking number, sender name, or receiver name |
per_page | integer | Results per page (default: 25, max: 100) |
page | integer | Page number (default: 1) |
sort | string | Sort field: created_at, updated_at, status (prefix with - for descending, e.g. -created_at) |
Response 200:
{
"data": [
{
"id": 4821,
"tracking_number": "TRK00004821",
"status": "in_transit",
"status_label": "In Transit",
"service_type": "standard",
"sender": {
"client_id": 102,
"name": "Acme Corp",
"phone": "+1-555-0100",
"address": "123 Commerce St, New York, NY 10001"
},
"receiver": {
"name": "Jane Doe",
"phone": "+1-555-0200",
"address": "456 Oak Ave, Brooklyn, NY 11201"
},
"package": {
"weight_kg": 2.5,
"dimensions_cm": { "l": 30, "w": 20, "h": 15 },
"description": "Electronics",
"fragile": false,
"cod_amount": null
},
"courier": {
"id": 17,
"name": "Michael Torres"
},
"branch_id": 1,
"created_at": "2026-04-01T09:30:00Z",
"updated_at": "2026-04-02T14:15:00Z",
"estimated_delivery": "2026-04-04",
"delivered_at": null
}
],
"meta": {
"current_page": 1,
"per_page": 25,
"total": 3847,
"last_page": 154
},
"links": {
"first": "https://yourdomain.com/api/v1/shipments?page=1",
"last": "https://yourdomain.com/api/v1/shipments?page=154",
"prev": null,
"next": "https://yourdomain.com/api/v1/shipments?page=2"
}
}
/api/v1/shipments
Create a shipment
Required ability: shipments:write
{
"sender": {
"client_id": 102,
"name": "Acme Corp",
"phone": "+1-555-0100",
"email": "shipping@acme.com",
"address": "123 Commerce St",
"city": "New York",
"state": "NY",
"postal_code": "10001",
"country": "US"
},
"receiver": {
"name": "Jane Doe",
"phone": "+1-555-0200",
"email": "jane@example.com",
"address": "456 Oak Ave",
"city": "Brooklyn",
"state": "NY",
"postal_code": "11201",
"country": "US"
},
"package": {
"weight_kg": 2.5,
"length_cm": 30,
"width_cm": 20,
"height_cm": 15,
"description": "Electronics - 1x Tablet",
"fragile": false,
"cod_amount": null,
"declared_value": 350.00,
"pieces": 1
},
"service_type": "standard",
"branch_id": 1,
"courier_id": 17,
"notes": "Leave at front desk if no answer",
"reference_number": "ORD-2026-88821"
}
Response 201: Returns the created shipment object (same structure as the list item above), including the generated tracking_number.
/api/v1/shipments/{tracking_number}
Get a shipment by tracking number
Required ability: shipments:read
Returns the full shipment object including the complete activity_log array of all status changes.
{
"data": {
"id": 4821,
"tracking_number": "TRK00004821",
"status": "delivered",
"service_type": "standard",
"sender": { "...": "..." },
"receiver": { "...": "..." },
"package": { "...": "..." },
"courier": { "id": 17, "name": "Michael Torres" },
"activity_log": [
{
"status": "created",
"label": "Shipment Created",
"timestamp": "2026-04-01T09:30:00Z",
"user": "System",
"note": null
},
{
"status": "picked_up",
"label": "Picked Up",
"timestamp": "2026-04-01T11:00:00Z",
"user": "Michael Torres",
"note": null
},
{
"status": "delivered",
"label": "Delivered",
"timestamp": "2026-04-03T14:22:00Z",
"user": "Michael Torres",
"note": "Left with building reception"
}
],
"created_at": "2026-04-01T09:30:00Z",
"delivered_at": "2026-04-03T14:22:00Z"
}
}
/api/v1/shipments/{tracking_number}/status
Update shipment status
Required ability: shipments:write
Transitions a shipment to a new status. Only valid transitions are accepted — see the status transition table in the Shipments module documentation.
{
"status": "delivered",
"note": "Delivered to reception, signed by J. Smith",
"timestamp": "2026-04-03T14:22:00Z"
}
Response 200: Returns the updated shipment object.
Response 422: If the transition is not allowed from the current status.
/api/v1/shipments/{tracking_number}/exception
Record a delivery exception
Required ability: shipments:write
{
"exception_reason": "address_not_found",
"note": "Building number does not exist on this street",
"reattempt_date": "2026-04-05"
}
Exception reason codes: address_not_found, recipient_unavailable, refused_delivery, access_denied, damaged_in_transit, incorrect_address, business_closed, weather_delay, other
Response 200: Returns the updated shipment with status set to exception.
/api/v1/shipments/{tracking_number}/label
Download a shipment label as PDF
Required ability: shipments:read
Query parameters:
| Parameter | Value | Default |
|---|---|---|
format | a4_full, a4_half, thermal_4x6, thermal_4x4 | a4_full |
Response: 200 with Content-Type: application/pdf. The response body is the binary PDF content. Use Content-Disposition header to determine the suggested filename.
/api/v1/shipments/{tracking_number}
Cancel/delete a shipment
Required ability: shipments:write
Only shipments in created status can be deleted via the API. Shipments that have been picked up must be cancelled through the admin interface. Deleting a shipment with a linked invoice will not delete the invoice.
Response: 204 No Content
Public Tracking
No authentication required
The public tracking endpoint does not require an API token. It returns the same data available on the public tracking page — status history and estimated delivery — without exposing internal data like courier names, client IDs, or billing information.
/api/v1/track/{tracking_number}
Public shipment tracking — no authentication required
{
"tracking_number": "TRK00004821",
"status": "out_for_delivery",
"status_label": "Out for Delivery",
"estimated_delivery": "2026-04-04",
"receiver_city": "Brooklyn, NY",
"events": [
{
"status": "created",
"label": "Shipment Created",
"timestamp": "2026-04-01T09:30:00Z",
"location": "New York, NY"
},
{
"status": "picked_up",
"label": "Picked Up by Courier",
"timestamp": "2026-04-01T11:00:00Z",
"location": "New York, NY"
},
{
"status": "in_transit",
"label": "In Transit",
"timestamp": "2026-04-02T08:30:00Z",
"location": "Distribution Center"
},
{
"status": "out_for_delivery",
"label": "Out for Delivery",
"timestamp": "2026-04-04T07:45:00Z",
"location": "Brooklyn, NY"
}
]
}
Response 404: If tracking number is not found or the company has disabled public tracking.
Clients
/api/v1/clients
List clients
Required ability: clients:read
Query parameters:
| Parameter | Type | Description |
|---|---|---|
search | string | Search by name, email, or phone |
type | string | individual or company |
tag | string | Filter by tag slug |
per_page | integer | Default: 25, max: 100 |
page | integer | Default: 1 |
{
"data": [
{
"id": 102,
"type": "company",
"name": "Acme Corp",
"email": "billing@acme.com",
"phone": "+1-555-0100",
"default_address": "123 Commerce St, New York, NY 10001",
"credit_limit": 5000.00,
"outstanding_balance": 1250.00,
"credit_status": "ok",
"payment_terms": "net_30",
"tags": ["enterprise", "e-commerce"],
"portal_access": true,
"created_at": "2025-01-15T00:00:00Z"
}
],
"meta": { "current_page": 1, "per_page": 25, "total": 284, "last_page": 12 }
}
/api/v1/clients
Create a client
Required ability: clients:write
{
"type": "company",
"name": "Acme Corp",
"email": "billing@acme.com",
"phone": "+1-555-0100",
"address": "123 Commerce St",
"city": "New York",
"state": "NY",
"postal_code": "10001",
"country": "US",
"company_name": "Acme Corporation LLC",
"vat_number": "US12-3456789",
"credit_limit": 5000.00,
"payment_terms": "net_30",
"tags": ["enterprise", "e-commerce"],
"notes": "Primary contact is the operations manager"
}
Response 201: Returns the created client object.
/api/v1/clients/{id}
Get a client
Required ability: clients:read
Returns the full client record. Optionally include related data with the include query parameter: ?include=shipments,invoices (adds paginated relationships to the response).
Response 200: Full client object with optional includes.
/api/v1/clients/{id}
Update a client
Required ability: clients:write
Accepts the same fields as the create endpoint. Only provided fields are updated (partial update accepted).
Response 200: Returns the updated client object.
Invoices
/api/v1/invoices
List invoices
Required ability: invoices:read
Query parameters:
| Parameter | Type | Description |
|---|---|---|
status | string | draft, sent, paid, overdue, cancelled |
client_id | integer | Filter by client |
date_from | date | Invoice date from |
date_to | date | Invoice date to |
overdue_only | boolean | Return only overdue invoices |
per_page | integer | Default: 25, max: 100 |
{
"data": [
{
"id": 881,
"invoice_number": "INV-2026-0881",
"client": { "id": 102, "name": "Acme Corp" },
"status": "sent",
"subtotal": 450.00,
"tax_amount": 40.50,
"total": 490.50,
"paid_amount": 0.00,
"balance_due": 490.50,
"currency": "USD",
"issue_date": "2026-04-01",
"due_date": "2026-05-01",
"payment_terms": "net_30",
"shipment_count": 12,
"created_at": "2026-04-01T10:00:00Z"
}
],
"meta": { "current_page": 1, "per_page": 25, "total": 142, "last_page": 6 }
}
/api/v1/invoices
Create an invoice
Required ability: invoices:write
{
"client_id": 102,
"due_date": "2026-05-01",
"payment_terms": "net_30",
"currency": "USD",
"shipment_ids": [4800, 4801, 4803, 4810, 4812, 4815, 4818, 4819, 4820, 4821, 4822, 4823],
"line_items": [
{
"description": "Standard Delivery — April 2026",
"quantity": 12,
"unit_price": 37.50,
"tax_rate": 9
}
],
"notes": "Payment due within 30 days of invoice date."
}
Either shipment_ids (auto-generates line items from shipments) or line_items (manual) must be provided, not both.
Response 201: Returns the created invoice object.
/api/v1/invoices/{id}
Get an invoice
Required ability: invoices:read
Returns the full invoice with line items and payment history.
Append ?format=pdf to receive the invoice as a PDF binary (Content-Type: application/pdf).
/api/v1/invoices/{id}/payments
Record a payment
Required ability: invoices:write
{
"amount": 490.50,
"payment_date": "2026-04-28",
"payment_method": "bank_transfer",
"reference": "WIRE-20260428-001",
"notes": "Full payment received"
}
Payment method values: cash, bank_transfer, cheque, card, mobile_payment, other
If amount equals the remaining balance, the invoice status is automatically set to paid. Partial payments are accepted and the invoice remains in sent status until fully paid.
Response 201: Returns the payment record and the updated invoice status.
Reports
Large exports are asynchronous
If the report dataset exceeds 5,000 rows, the API returns a 202 Accepted response with a job_id. Poll GET /api/v1/reports/exports/{job_id} to check the job status. When complete, the response includes a download_url valid for 24 hours.
/api/v1/reports/shipments
Shipments summary data
Required ability: reports:read
Query parameters:
| Parameter | Type | Description |
|---|---|---|
date_from | date | Required |
date_to | date | Required |
branch_id | integer | Optional |
service_type | string | Optional |
format | string | json (default), csv, xlsx, pdf |
{
"summary": {
"total": 1248,
"delivered": 1102,
"in_transit": 87,
"exception": 41,
"returned": 18,
"on_time_rate": 0.891,
"exception_rate": 0.033,
"return_rate": 0.014
},
"daily_trend": [
{ "date": "2026-04-01", "count": 48 },
{ "date": "2026-04-02", "count": 61 }
],
"by_status": [
{ "status": "delivered", "count": 1102, "percentage": 0.883 },
{ "status": "exception", "count": 41, "percentage": 0.033 }
]
}
/api/v1/reports/revenue
Revenue report data
Required ability: reports:read + Accountant or Admin role
Query parameters: Same as shipments report (date_from, date_to, branch_id, client_id, format).
{
"summary": {
"total_invoiced": 58420.00,
"total_collected": 51200.00,
"outstanding_ar": 7220.00,
"average_invoice_value": 410.00,
"average_dso_days": 28.4
},
"by_service_type": [
{ "service": "standard", "revenue": 42000.00, "percentage": 0.719 },
{ "service": "express", "revenue": 12600.00, "percentage": 0.216 },
{ "service": "same_day", "revenue": 3820.00, "percentage": 0.065 }
],
"top_clients": [
{ "client_id": 102, "name": "Acme Corp", "revenue": 8800.00 },
{ "client_id": 88, "name": "RetailXpress", "revenue": 6500.00 }
]
}
/api/v1/reports/exports/{job_id}
Check export job status
Required ability: reports:read
{
"job_id": "a3f8d21c-9b44-4f2e-bea1-0c3e81a52d70",
"status": "processing",
"progress": 42,
"created_at": "2026-04-04T09:00:00Z",
"download_url": null
}
{
"job_id": "a3f8d21c-9b44-4f2e-bea1-0c3e81a52d70",
"status": "complete",
"progress": 100,
"created_at": "2026-04-04T09:00:00Z",
"completed_at": "2026-04-04T09:00:48Z",
"download_url": "https://yourdomain.com/api/v1/reports/exports/a3f8d21c.../download",
"expires_at": "2026-04-05T09:00:00Z"
}
Error Codes
| HTTP Code | Error Code | Description |
|---|---|---|
400 | bad_request | Malformed request body or invalid JSON. |
401 | unauthenticated | Missing or invalid Bearer token. |
403 | forbidden | Valid token but insufficient abilities or role permissions for this action. |
404 | not_found | Resource does not exist or is not accessible to the authenticated user. |
409 | conflict | State conflict — e.g., attempting an invalid status transition. |
422 | validation_error | Request body failed validation. The errors object contains field-level details. |
429 | too_many_requests | Rate limit exceeded. See Retry-After header. |
500 | server_error | Internal error. Check Laravel logs. If persistent, contact support with the request_id. |
503 | maintenance_mode | The application is in maintenance mode. The API is unavailable until maintenance is complete. |
Error response format:
{
"error": {
"code": "validation_error",
"message": "The given data was invalid.",
"errors": {
"receiver.phone": ["The receiver phone field is required."],
"package.weight_kg": ["The weight must be greater than 0."]
},
"request_id": "req_b92f3e44a1d7"
}
}
Rate Limiting
API rate limits are applied per token using a sliding window algorithm backed by Redis.
| Endpoint group | Limit | Window |
|---|---|---|
Authentication (/api/v1/tokens) | 10 requests | Per minute |
| Read endpoints (GET) | 600 requests | Per minute |
| Write endpoints (POST/PUT/PATCH/DELETE) | 120 requests | Per minute |
| Report generation | 20 requests | Per minute |
Public tracking (/api/v1/track/*) | 60 requests per IP | Per minute |
Rate limit headers are included in every response:
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 586
X-RateLimit-Reset: 1743760860
Retry-After: 12
When the limit is exceeded, the response is 429 Too Many Requests. The Retry-After header indicates the number of seconds to wait before retrying.
Pagination
All list endpoints use cursor-based pagination compatible with the standard page / per_page pattern. Every paginated response includes a meta object and a links object:
{
"data": [ "...resources..." ],
"meta": {
"current_page": 3,
"per_page": 25,
"total": 3847,
"last_page": 154,
"from": 51,
"to": 75
},
"links": {
"first": "https://yourdomain.com/api/v1/shipments?page=1",
"last": "https://yourdomain.com/api/v1/shipments?page=154",
"prev": "https://yourdomain.com/api/v1/shipments?page=2",
"next": "https://yourdomain.com/api/v1/shipments?page=4"
}
}
To iterate through all pages:
import requests
token = "YOUR_API_TOKEN"
headers = {"Authorization": f"Bearer {token}", "Accept": "application/json"}
url = "https://yourdomain.com/api/v1/shipments"
all_shipments = []
while url:
response = requests.get(url, headers=headers, params={"per_page": 100})
data = response.json()
all_shipments.extend(data["data"])
url = data["links"].get("next") # None when on the last page
print(f"Fetched {len(all_shipments)} shipments")
Webhooks
Deprixa Plus can push real-time events to your systems via webhooks. Configure webhook endpoints from Settings > Integrations > Webhooks.
Available Events
| Event | Trigger |
|---|---|
shipment.created | A new shipment is created |
shipment.status_changed | Any shipment status change |
shipment.delivered | Shipment delivered successfully |
shipment.exception | Delivery exception recorded |
invoice.created | Invoice created (any state) |
invoice.sent | Invoice sent to client |
invoice.paid | Invoice fully paid |
invoice.overdue | Invoice passed due date unpaid |
Webhook Payload
{
"event": "shipment.status_changed",
"timestamp": "2026-04-04T07:45:00Z",
"data": {
"tracking_number": "TRK00004821",
"previous_status": "in_transit",
"new_status": "out_for_delivery",
"changed_by": "Michael Torres",
"note": null
},
"webhook_id": "wh_0a1b2c3d4e5f",
"delivery_attempt": 1
}
Webhook Security
Each webhook delivery includes an X-Deprixa-Signature header: an HMAC-SHA256 signature of the raw request body using your webhook secret. Verify this signature before processing the payload:
$secret = 'your_webhook_secret';
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_DEPRIXA_SIGNATURE'];
$expected = 'sha256=' . hash_hmac('sha256', $payload, $secret);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit('Invalid signature');
}
$event = json_decode($payload, true);
// Process $event...
Failed webhook deliveries are retried up to 5 times with exponential backoff (1 min, 5 min, 15 min, 30 min, 60 min). After 5 failures, the webhook is automatically disabled and an alert is sent to the admin email.