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

Base URL
https://yourdomain.com/api/v1

All requests must include the following headers:

HeaderValueRequired
AuthorizationBearer {token}Yes (all endpoints except token creation)
Acceptapplication/jsonYes
Content-Typeapplication/jsonYes (POST/PUT/PATCH requests)
X-Branch-IDInteger branch IDOptional — 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.

POST /api/v1/tokens Create an API token

Returns a plaintext token. Store it immediately — it is only shown once.

Request body:

JSON
{
  "email": "api-user@yourcompany.com",
  "password": "secret",
  "token_name": "e-commerce-integration",
  "abilities": ["shipments:read", "shipments:write", "clients:read"]
}

Available abilities:

AbilityDescription
*Full access (all abilities) — requires Admin+ role
shipments:readList and retrieve shipments
shipments:writeCreate and update shipments
clients:readList and retrieve clients
clients:writeCreate and update clients
invoices:readList and retrieve invoices
invoices:writeCreate and update invoices
tracking:readPublic tracking access (no auth required on the public endpoint)
reports:readAccess report data endpoints

Response 200:

JSON
{
  "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
}
DELETE /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

GET /api/v1/tokens List all tokens for the authenticated user

Returns all active tokens. The plaintext token value is never returned — only metadata.

JSON
{
  "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

GET /api/v1/shipments List shipments

Required ability: shipments:read

Query parameters:

ParameterTypeDescription
statusstringFilter by status code: created, picked_up, in_transit, out_for_delivery, delivered, exception, on_hold, returned
courier_idintegerFilter by assigned courier
client_idintegerFilter by sender client
branch_idintegerFilter by branch
date_fromdate (Y-m-d)Created at or after this date
date_todate (Y-m-d)Created at or before this date
searchstringSearch by tracking number, sender name, or receiver name
per_pageintegerResults per page (default: 25, max: 100)
pageintegerPage number (default: 1)
sortstringSort field: created_at, updated_at, status (prefix with - for descending, e.g. -created_at)

Response 200:

JSON
{
  "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"
  }
}
POST /api/v1/shipments Create a shipment

Required ability: shipments:write

Request body
{
  "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.

GET /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.

Response 200
{
  "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"
  }
}
PATCH /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.

Request body
{
  "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.

PATCH /api/v1/shipments/{tracking_number}/exception Record a delivery exception

Required ability: shipments:write

Request body
{
  "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.

GET /api/v1/shipments/{tracking_number}/label Download a shipment label as PDF

Required ability: shipments:read

Query parameters:

ParameterValueDefault
formata4_full, a4_half, thermal_4x6, thermal_4x4a4_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.

DELETE /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.

GET /api/v1/track/{tracking_number} Public shipment tracking — no authentication required
Response 200
{
  "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

GET /api/v1/clients List clients

Required ability: clients:read

Query parameters:

ParameterTypeDescription
searchstringSearch by name, email, or phone
typestringindividual or company
tagstringFilter by tag slug
per_pageintegerDefault: 25, max: 100
pageintegerDefault: 1
Response 200
{
  "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 }
}
POST /api/v1/clients Create a client

Required ability: clients:write

Request body
{
  "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.

GET /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.

PUT /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

GET /api/v1/invoices List invoices

Required ability: invoices:read

Query parameters:

ParameterTypeDescription
statusstringdraft, sent, paid, overdue, cancelled
client_idintegerFilter by client
date_fromdateInvoice date from
date_todateInvoice date to
overdue_onlybooleanReturn only overdue invoices
per_pageintegerDefault: 25, max: 100
Response 200
{
  "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 }
}
POST /api/v1/invoices Create an invoice

Required ability: invoices:write

Request body
{
  "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.

GET /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).

POST /api/v1/invoices/{id}/payments Record a payment

Required ability: invoices:write

Request body
{
  "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.

GET /api/v1/reports/shipments Shipments summary data

Required ability: reports:read

Query parameters:

ParameterTypeDescription
date_fromdateRequired
date_todateRequired
branch_idintegerOptional
service_typestringOptional
formatstringjson (default), csv, xlsx, pdf
Response 200 (format=json)
{
  "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 }
  ]
}
GET /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).

Response 200 (format=json)
{
  "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 }
  ]
}
GET /api/v1/reports/exports/{job_id} Check export job status

Required ability: reports:read

Response 200 (pending)
{
  "job_id": "a3f8d21c-9b44-4f2e-bea1-0c3e81a52d70",
  "status": "processing",
  "progress": 42,
  "created_at": "2026-04-04T09:00:00Z",
  "download_url": null
}
Response 200 (complete)
{
  "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 CodeError CodeDescription
400bad_requestMalformed request body or invalid JSON.
401unauthenticatedMissing or invalid Bearer token.
403forbiddenValid token but insufficient abilities or role permissions for this action.
404not_foundResource does not exist or is not accessible to the authenticated user.
409conflictState conflict — e.g., attempting an invalid status transition.
422validation_errorRequest body failed validation. The errors object contains field-level details.
429too_many_requestsRate limit exceeded. See Retry-After header.
500server_errorInternal error. Check Laravel logs. If persistent, contact support with the request_id.
503maintenance_modeThe application is in maintenance mode. The API is unavailable until maintenance is complete.

Error response format:

JSON
{
  "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 groupLimitWindow
Authentication (/api/v1/tokens)10 requestsPer minute
Read endpoints (GET)600 requestsPer minute
Write endpoints (POST/PUT/PATCH/DELETE)120 requestsPer minute
Report generation20 requestsPer minute
Public tracking (/api/v1/track/*)60 requests per IPPer minute

Rate limit headers are included in every response:

Response headers
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:

Pagination envelope
{
  "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:

Python example
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

EventTrigger
shipment.createdA new shipment is created
shipment.status_changedAny shipment status change
shipment.deliveredShipment delivered successfully
shipment.exceptionDelivery exception recorded
invoice.createdInvoice created (any state)
invoice.sentInvoice sent to client
invoice.paidInvoice fully paid
invoice.overdueInvoice passed due date unpaid

Webhook Payload

POST to your endpoint
{
  "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:

PHP verification
$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.