API Reference · Version 1.3.16

IMBox API

A data-acquisition agnostic API for the IMBox smart locker platform. Send identifiers, receive access codes — no personal data crosses the boundary in either direction.

Base URL https://ecommerce.api.imbox.solutions
API root https://ecommerce.api.imbox.solutions/api/v1/

1 Introduction

The IMBox API is a data-acquisition agnostic API for the IMBox smart locker platform. It lets your system create and track parcel orders, monitor locker stations, and receive events — without ever handling customer or courier personal data.

Two properties define it.

Data-acquisition agnostic. The API operates entirely on identifiers you choose. A customer is CUST-9917 because you say so. Requests carrying such fields are rejected outright, so the guarantee is enforced by the software rather than promised in a contract. The correspondence between an identifier and a real person stays inside your systems, where it already is.

Stable and extensible. The API presents a small contract that does not move. New station types, new cell sizes and new capabilities are introduced additively — existing calls keep working, fields are added and never repurposed, and the API is versioned under /api/v1/ so a breaking change would arrive as /api/v2/ alongside it.

1.1 What the API does for you

You send The API does
customer_id Finds or creates the matching record
courier_id Finds or creates the matching record
billing_id Finds or creates the pricing plan
notification_id Finds or creates the message template
station_id, cell_size Reserves a physical cell and returns its access codes

Every one of those lookups is cached after first use, is safe to repeat, and never requires you to know an internal primary key.

1.2 Live deployment

The API is deployed and serving.

Base URL https://ecommerce.api.imbox.solutions
API root https://ecommerce.api.imbox.solutions/api/v1/
Console https://ecommerce.api.imbox.solutions/console/
Health https://ecommerce.api.imbox.solutions/api/v1/health/

2 The console

The console is a browser interface for your developers. It is where API credentials are obtained and where the webhook endpoints are configured. It requires no separate account: you sign in with the same organisation credentials you already hold with IMBox.

2.1 Signing in

The console sign-in page
Field Description
Username Your IMBox organisation name, for example Vodafone.
Password The password for that organisation account.

The API holds no password of its own. Each sign-in is verified against your IMBox account and accepted only if it is valid and the account is an organisation. Two consequences follow: there is no second password for your team to manage, and revoking your IMBox account revokes console access at the same moment.

Sign-in attempts are rate limited to five per minute per source address.

2.2 Overview

The overview page

The landing page after sign-in contains information about the organisation’s activity and webhook configuration.

Element Description
Orders created Total orders this organisation has created through the API.
Currently open Orders not yet collected — status CREATED or DROPPED or etc.
Webhooks configured How many of the six events have a live endpoint.
Organisation id Your IMBox organisation identifier.
Webhook endpoints Each event, its destination URL, and whether it is active.
Recent deliveries The ten most recent callback attempts with their outcome.

2.3 Credentials

The credentials page

Where your developers collect the values their code needs. The page is divided into three independent credentials, each serving a different purpose.

2.3.1 OAuth2

Identifies your system when it calls the API.

Field Description
Token URL Where to exchange your credentials for an access token.
Grant type Always client_credentials. There is no user-facing login flow.
Client id Public identifier for your organisation. Always visible here.
Client secret Stored as a one-way hash. Rotate to reveal a new value.

2.3.2 Request signing (HMAC)

An optional second layer. OAuth2 alone is accepted by default; enabling signing additionally protects each request against tampering and replay.

Field Description
Key id Public identifier of the signing key, sent as X-IMBox-Key.
Secret Encrypted at rest. Rotate to reveal a new value.

2.3.3 Webhook signing secret

Used only in the outbound direction. It is never sent on an API request; it exists so that your endpoint can verify that a callback genuinely originated from the API.

Field Description
Value Encrypted at rest. Rotate to reveal a new value.
Used for Outbound webhooks only.
Algorithm HMAC-SHA256.

How the signature is constructed and verified is covered in Verifying a callback.

2.3.4 Obtaining a secret

Secrets are displayed exactly once, at the moment they are generated. Nothing in the system can display them again — they are stored hashed or encrypted.

To obtain one:

  1. Open Credentials.
  2. Press Rotate on the credential you need.
  3. Enter your organisation password when prompted, then confirm.
  4. Copy the revealed value immediately and store it in your secret manager.

Rotation is deliberately gated behind the password, not merely the session, because it invalidates a credential that may be carrying live traffic. An unattended browser is not sufficient authority to break a running integration.

The previous value stops working the instant a rotation completes. Deploy the new secret at the same time.

2.4 Webhooks

The webhooks page

One destination URL per event. The API posts to these as orders progress.

Field Description
Event name The identifier sent in the X-IMBox-Event header.
URL Your HTTPS endpoint.
Enabled Uncheck to suspend delivery without discarding the URL.
Last delivery Time and status of the most recent attempt, plus any run of consecutive failures.
Send test Posts a representative payload immediately and reports the status code, latency and response body.

Send test is the fastest route to a working integration: it exercises your endpoint, your signature verification and your parser before a real parcel is involved.

Press Save all endpoints to persist changes.

2.5 Deliveries

The deliveries page

An audit trail of the last fifty callback attempts.

Column Description
When Time of the attempt.
Event Which event was being delivered.
Order The order the event concerns.
Status delivered, pending (awaiting retry), failed (retries exhausted), or no endpoint.
Code status code returned by your endpoint.
Tries Attempts made so far.
Error Transport error or response body from the failure.

A status of no endpoint means the event occurred and was recorded but no URL was configured for it. The event is still readable through the activity log API, so nothing is lost.

3 Getting started

This walkthrough takes you from an empty console to a live order with pickup codes and a working webhook, using nothing but curl. Each step builds on the last; run them in order and you will have exercised every part of the integration in a few minutes.

3.1 Collect your credentials

Sign in to the console and open Credentials (see The console section). You need two values:

  • Client id — always shown on the page.
  • Client secret — press Rotate to reveal it once, then copy it straight into your secret manager.

Export them so the commands below can reuse them:

export BASE="https://ecommerce.api.imbox.solutions"
export CLIENT_ID="<your client id>"
export CLIENT_SECRET="<your client secret>"

3.2 Get an access token

export TOKEN=$(curl -s -X POST "$BASE/oauth2/token/" \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d grant_type=client_credentials \
  | python -c "import sys, json; print(json.load(sys.stdin)['access_token'])")
echo "$TOKEN"

The token lasts an hour — cache it and reuse it on every call below.

3.3 Create a test order

Send identifiers only. The API resolves the customer and courier, reserves a cell of the requested size, and returns the two access codes.

Note — station_id must be a locker station IMBox has assigned to your organisation. If you do not have one yet, please contact the IMBox team to have a station provisioned and to receive its identifier. The example below uses MALL-MASR-001 for illustration.

curl -s -X POST "$BASE/api/v1/orders/" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "station_id": "MALL-MASR-001",
        "customer_id": "CUST-1001",
        "courier_id":  "CR-7",
        "cell_size":   "medium",
        "order_id":    "TEST-0001",
        "idempotency_key": "TEST-0001"
      }'

Response — 201 Created

{
  "order_id": "TEST-0001",
  "status": "CREATED",
  "station_id": "MALL-MASR-001",
  "cell_id": "3",
  "cell_size": "medium",
  "customer_id": "CUST-1001",
  "courier_id": "CR-7",
  "customer_code": "8251372",
  "courier_code": "2875912",
  "codes_available": true
}

The two codes are the heart of the flow: courier_code opens the cell for the courier to deposit the parcel, and customer_code opens it for the recipient to collect it. Distribute them through your own channels.

idempotency_key makes the call safe to retry — replaying the same key returns the original order instead of creating a second one.

3.4 List your orders and their status

curl -s "$BASE/api/v1/orders/?customer_id=CUST-1001" \
  -H "Authorization: Bearer $TOKEN"

Response — 200 OK

{
  "count": 1,
  "pages": 1,
  "results": [
    { "order_id": "TEST-0001", "status": "CREATED", "cell_id": "3", "…": "…" }
  ]
}

status moves through CREATED → DROPPED → PICKED (or EXPIRED / FAILED) as the courier and recipient act at the locker keypad. Combine status=, date=, station_id= and the other filters — see List orders.

3.5 Read one order’s codes and history

curl -s "$BASE/api/v1/orders/TEST-0001/" \
  -H "Authorization: Bearer $TOKEN"

This returns the full order — including customer_code and courier_code — with its event history (create, reserve, sync). If the codes could not be read at creation time (codes_available: false), retrieving the order fetches them again.

3.6 Register a webhook and test it

Rather than polling, let the API call you as the order progresses.

  1. In the console, open Webhooks (see The console).
  2. Enter your HTTPS endpoint for the events you care about — order.created, order.dropped, customer.notified, order.picked, order.expired and order.failed.
  3. Press Send test. The API immediately POSTs a representative payload and reports the status code, latency and response body — so you validate your endpoint, your signature check and your parser before a real parcel is involved.

Every callback is signed. Verify it against the raw body, before any JSON parsing:

import hmac, hashlib, time

def verify(secret, headers, raw_body):
    timestamp = headers.get("X-IMBox-Timestamp", "")
    signature = headers.get("X-IMBox-Signature", "")
    if not timestamp or not signature:
        return False
    if abs(time.time() - int(timestamp)) > 300:      # reject stale replays
        return False
    expected = hmac.new(
        secret.encode(),
        f"{timestamp}.".encode() + raw_body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", signature)

The webhook signing secret is on the Credentials page; the full payload and header reference is in Webhooks.

3.7 Force a status refresh (optional)

The API keeps statuses current on its own. To pull the latest immediately — handy while testing — call:

curl -s -X POST "$BASE/api/v1/orders/sync/" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" -d '{}'

Any status change it discovers dispatches the matching webhook. You have now created an order, read its codes, listed it, and wired up callbacks — the whole integration in miniature.

4 Authentication

Every API call carries an OAuth2 bearer token. Obtaining one is a single request.

4.1 Requesting a token

POST /oauth2/token/

Send your client id and secret using Basic authentication.

curl -X POST https://ecommerce.api.imbox.solutions/oauth2/token/ \
  -u "<client_id>:<client_secret>" \
  -d grant_type=client_credentials

Response — 200 OK

{
  "access_token": "Q5nHSrdYTtIc9m2K0pLxWvB3aRt7Ye",
  "expires_in": 3600,
  "token_type": "Bearer",
  "scope": "orders:read orders:write stations:read logs:read"
}
Field Description
access_token Send as Authorization: Bearer <token> on every API call.
expires_in Lifetime in seconds. Cache the token and reuse it.
scope Permissions granted to this token.

Response — 401 Unauthorized — the client id or secret is wrong.

4.2 Scopes

Scope Grants
orders:read Reading orders, customers and couriers
orders:write Creating orders and forcing a status refresh
stations:read Reading stations and cells
logs:read Reading the activity log

4.3 Optional request signing

Where payload integrity and replay protection are required, the API also accepts IMBOX-HMAC-SHA256 request signatures alongside the bearer token. Four additional headers are sent: X-IMBox-Key, X-IMBox-Timestamp, X-IMBox-Nonce and X-IMBox-Signature. The signature covers the method, path, canonical query string, body digest, timestamp, nonce and OAuth2 client id, so a captured signature cannot be replayed, redirected to another endpoint, or used by another organisation. Contact the IMBox team to enable enforcement for your organisation.

5 API reference

Base URL: https://ecommerce.api.imbox.solutions/api/v1/

All requests require Authorization: Bearer <token>. All request bodies are JSON with Content-Type: application/json.

5.1 Create an order

POST /api/v1/orders/ — scope orders:write

Creates a parcel order from identifiers alone. The API resolves the customer, courier, billing plan and notification template — creating any that do not yet exist — reserves a cell of the requested size at the station, and returns the access codes for the courier and the customer.

5.1.1 Request parameters

Required

Parameter Type Description
station_id string Locker station identifier, e.g. MALL-MASR-001.
customer_id string Your reference for the recipient. Created on first use.
courier_id string Your reference for the delivering courier. Created on first use.

Note — station_id must identify a locker station IMBox has assigned to your organisation. Please contact the IMBox team to buy an assigned station and to obtain its identifier before creating orders.

Optional

Parameter Type Default Description
cell_size string medium One of small, medium, large, xlarge.
billing_id string STANDARD Pricing plan reference. Created on first use.
notification_id string DEFAULT Message template reference. Created on first use.
order_id string generated Your order reference. Must be unique for your organisation.
dropped_date date today YYYY-MM-DD. When the courier will deposit the parcel.
picked_date date +3 days YYYY-MM-DD. End of the collection window.
idempotency_key string — Replaying a key returns the original order instead of creating a second.

Identifier fields accept up to 128 characters from A–Z a–z 0–9 . _ : @ -.

5.1.2 Example

curl -X POST https://ecommerce.api.imbox.solutions/api/v1/orders/ \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "station_id":      "MALL-MASR-001",
        "customer_id":     "CUST-9917",
        "courier_id":      "CR-42",
        "cell_size":       "small",
        "order_id":        "VF-2026-0001",
        "idempotency_key": "VF-2026-0001"
      }'

5.1.3 Response — 201 Created

{
  "order_id": "VF-2026-0001",
  "status": "CREATED",
  "station_id": "MALL-MASR-001",
  "cell_id": "2",
  "cell_size": "small",
  "customer_id": "CUST-9917",
  "courier_id": "CR-42",
  "billing_id": "STANDARD",
  "notification_id": "DEFAULT",
  "customer_code": "8251372",
  "courier_code": "2875912",
  "codes_available": true,
  "dropped_date": "2026-08-26",
  "picked_date": "2026-08-29",
  "upstream_order_id": "28",
  "failure_reason": "",
  "created_at": "2026-08-26T09:14:02.113Z",
  "dropped_at": null,
  "picked_at": null
}
Field Description
order_id Your reference, or one generated for you.
status CREATED, DROPPED, PICKED, EXPIRED, CANCELLED or FAILED.
cell_id The physical cell reserved at the station.
customer_code Keypad code for the recipient to collect the parcel.
courier_code Keypad code for the courier to deposit it.
codes_available false indicates the codes could not be read, not that none exist.
upstream_order_id The order’s internal reference, for support.
failure_reason Populated only when status is FAILED.

Distribute the two codes through your own channels. The API has no address to send them to, which is precisely the point.

5.1.4 Other responses

Status error Meaning
400 validation_error A field is missing, malformed, or not part of the contract. See fields.
401 — Token missing, malformed or expired.
403 insufficient_scope The token lacks orders:write.
409 conflict An order with that order_id already exists.
409 no_capacity No free cell of the requested size at that station.
422 provisioning_failed A referenced record could not be created.
429 — Rate limit exceeded. Back off and retry.
502 / 503 upstream_unavailable The service is temporarily unavailable.

A rejected personal-data field looks like this:

{
  "error": "validation_error",
  "detail": "One or more fields are invalid.",
  "request_id": "c014d4147a2c47fb88e3228724a99027",
  "fields": {
    "customer_name":  ["Unknown field. Send identifiers only - no personal data."],
    "customer_phone": ["Unknown field. Send identifiers only - no personal data."]
  }
}

5.2 List orders

GET /api/v1/orders/ — scope orders:read

Returns your organisation’s orders, newest first. Every parameter is optional and they combine.

Parameter Type Description
order_id string Exact match on your order reference.
customer_id string All orders for one recipient.
courier_id string All orders for one courier.
station_id string All orders at one locker station.
cell_id string All orders that used one cell.
organization_id string Your organisation. Other values return an empty set.
status string CREATED, DROPPED, PICKED, EXPIRED, CANCELLED, FAILED.
date string today, yesterday, last_week, last_month, last_year.
page integer Page number, from 1.
page_size integer Results per page. Default 50, maximum 200.
curl "https://ecommerce.api.imbox.solutions/api/v1/orders/?customer_id=CUST-9917&status=CREATED" \
  -H "Authorization: Bearer $TOKEN"

5.2.1 Response — 200 OK

{
  "count": 12,
  "pages": 1,
  "page": 1,
  "page_size": 50,
  "next": null,
  "previous": null,
  "results": [ { "order_id": "VF-2026-0001", "status": "CREATED", "…": "…" } ]
}
Field Description
count Total matching orders across all pages.
pages Number of pages available.
next / previous Absolute URLs, or null at the ends.
results Array of order objects, as returned by create.

Other responses: 401, 403 insufficient_scope.

5.3 Retrieve one order

GET /api/v1/orders/{order_id}/ — scope orders:read

Returns a single order together with its full history.

curl https://ecommerce.api.imbox.solutions/api/v1/orders/VF-2026-0001/ \
  -H "Authorization: Bearer $TOKEN"

5.3.1 Response — 200 OK

The order object, plus:

{
  "order_id": "VF-2026-0001",
  "status": "DROPPED",
  "events": [
    { "action": "create",  "to_status": "CREATED", "note": "Order accepted",
      "occurred_at": "2026-08-26T09:14:02Z" },
    { "action": "reserve", "to_status": "CREATED", "note": "Cell 2 reserved",
      "occurred_at": "2026-08-26T09:14:07Z" },
    { "action": "sync",    "from_status": "CREATED", "to_status": "DROPPED",
      "note": "Status changed at the locker",
      "occurred_at": "2026-08-26T11:20:14Z" }
  ]
}
Field Description
events[].action create, reserve, sync or fail.
events[].from_status / to_status The transition recorded.
events[].occurred_at When it happened, UTC.

Other responses: 404 not_found when no such order exists for your organisation.

5.4 Refresh order statuses

POST /api/v1/orders/sync/ — scope orders:write

The courier and the recipient act at the locker keypad, not through this API, so the API discovers those transitions on its own. This runs automatically every two minutes. Call this endpoint to force an immediate refresh.

curl -X POST https://ecommerce.api.imbox.solutions/api/v1/orders/sync/ \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{}'

5.4.1 Response — 200 OK

{ "synced": 3 }

synced is the number of orders whose status changed. Any resulting webhooks are dispatched as part of the call.

5.5 Activity log

GET /api/v1/logs/ — scope logs:read

An append-only record of everything that has happened to your orders and stations, filterable on every identifier you already hold.

Parameter Type Description
order_id string Everything concerning one order.
customer_id string Everything concerning one recipient.
courier_id string Everything concerning one courier.
station_id string Everything at one locker station.
cell_id string Everything involving one cell.
organization_id string Your organisation.
event_type string See the table below. Repeatable.
date string today, yesterday, last_week, last_month, last_year.
since / until ISO 8601 Explicit time bounds.
page / page_size integer Pagination.
event_type Recorded when
ORDER_CREATED An order was accepted and a cell reserved.
ORDER_DROPPED The courier deposited the parcel.
ORDER_PICKED The recipient collected the parcel.
ORDER_EXPIRED The collection window elapsed.
ORDER_FAILED The order could not be completed.
CUSTOMER_NOTIFIED The recipient’s collection code was issued.
RESOURCE_PROVISIONED An identifier was registered.
WEBHOOK_SENT A callback was delivered successfully.
WEBHOOK_FAILED A callback attempt failed.
UPSTREAM_ERROR The service returned an error.
curl "https://ecommerce.api.imbox.solutions/api/v1/logs/?station_id=MALL-MASR-001&date=last_week" \
  -H "Authorization: Bearer $TOKEN"

5.5.1 Response — 200 OK

{
  "count": 3,
  "results": [
    {
      "occurred_at": "2026-08-26T09:14:07Z",
      "event_type": "ORDER_CREATED",
      "station_id": "MALL-MASR-001",
      "cell_id": "2",
      "order_id": "VF-2026-0001",
      "customer_id": "CUST-9917",
      "courier_id": "CR-42",
      "organization_id": "12",
      "message": "Order VF-2026-0001 is CREATED",
      "request_id": "c014d4147a2c47fb88e3228724a99027"
    }
  ]
}

request_id correlates the entry with the API’s own logs; quote it in any support request.

5.6 List locker stations

GET /api/v1/stations/ — scope stations:read

curl https://ecommerce.api.imbox.solutions/api/v1/stations/ \
  -H "Authorization: Bearer $TOKEN"

5.6.1 Response — 200 OK

[
  {
    "station_id": "MALL-MASR-001",
    "name": "Mall Masr",
    "location": "6th of October",
    "status": "ACTIVE",
    "cells_total": 6,
    "cells_free": 2,
    "cells_occupied": 1,
    "cells_reserved": 3
  }
]
Field Description
station_id Use this as station_id when creating an order.
status ACTIVE or INACTIVE.
cells_total Cells installed at the station.
cells_free Cells available for a new order right now.
cells_occupied Cells holding a parcel awaiting collection.
cells_reserved Cells allocated to an order not yet deposited.

5.7 Retrieve one station

GET /api/v1/stations/{station_id}/ — scope stations:read

Returns the single station object shown above.

Other responses: 404 not_found.

5.8 List cells at a station

GET /api/v1/stations/{station_id}/cells/ — scope stations:read

Parameter Type Description
status string free, occupied or reserved.
size string small, medium, large or xlarge.
curl "https://ecommerce.api.imbox.solutions/api/v1/stations/MALL-MASR-001/cells/?status=free" \
  -H "Authorization: Bearer $TOKEN"

5.8.1 Response — 200 OK

[
  {
    "cell_id": "2",
    "station_id": "MALL-MASR-001",
    "station_name": "Mall Masr",
    "size": "small",
    "status": "free",
    "state": "active"
  }
]
Field Description
cell_id Identifier of the physical compartment.
size Cell size class.
status Current occupancy.
state active, or not_active when withdrawn from service.

Checking capacity before creating an order is optional — the API selects a cell for you and returns 409 no_capacity if none is available.

5.9 Customers and couriers

GET /api/v1/customers/, GET /api/v1/couriers/ — scope orders:read

The identifiers your organisation has used, with per-party counters. No personal attribute is returned, because none is held.

Parameter Type Description
id string Substring match on the identifier.
page / page_size integer Pagination.

GET /api/v1/customers/{customer_id}/, GET /api/v1/couriers/{courier_id}/ return one record.

5.9.1 Response — 200 OK

{
  "id": "CUST-9917",
  "kind": "customer",
  "provisioned": true,
  "first_seen": "2026-08-01T09:14:22Z",
  "last_seen": "2026-08-26T09:14:02Z",
  "orders_total": 14,
  "orders_open": 1,
  "orders_picked": 12
}
Field Description
id The identifier you supplied.
provisioned Whether a corresponding record exists.
first_seen / last_seen When the API first and last saw this identifier.
orders_total Orders ever created for it.
orders_open Orders currently CREATED or DROPPED.
orders_picked Orders collected.

Other responses: 404 not_found for an identifier never used.

5.10 Health

GET /api/v1/health/ — no authentication required

curl https://ecommerce.api.imbox.solutions/api/v1/health/

5.10.1 Response — 200 OK

{
  "status": "ok",
  "version": "1.3.16",
  "database": "ok",
  "upstream": {
    "reachable": true,
    "authenticated": true,
    "configured": true
  },
  "codes_readable": true,
  "hmac_required": false
}
Field Description
status ok, or degraded when the API cannot serve normally.
upstream.reachable Whether the locker service is responding.
upstream.authenticated Whether the API can authenticate to it.
upstream.configured Whether service credentials are present.
hmac_required Whether request signing is enforced for this deployment.

A 503 indicates the API itself is degraded. Suitable for a load balancer or an uptime monitor.

6 Webhooks

Rather than polling, register an endpoint per event and let the API call you.

6.1 Events

Event Fires when
order.created The order was accepted, a cell reserved and the courier code issued.
order.dropped The courier opened the cell and deposited the parcel.
customer.notified The recipient’s collection code was issued.
order.picked The recipient opened the cell and collected the parcel.
order.expired The collection window elapsed without collection.
order.failed The order could not be completed.

6.2 Payload

Every event uses the same envelope.

{
  "event": "order.dropped",
  "event_id": "evt_9c1e77a30b5d4f62",
  "occurred_at": "2026-08-26T11:20:14Z",
  "data": {
    "order_id": "VF-2026-0001",
    "status": "DROPPED",
    "station_id": "MALL-MASR-001",
    "cell_id": "2",
    "cell_size": "small",
    "customer_id": "CUST-9917",
    "courier_id": "CR-42",
    "billing_id": "STANDARD",
    "notification_id": "DEFAULT",
    "customer_code": "8251372",
    "courier_code": "2875912"
  }
}

Identifiers and codes only — the same rule applies outbound as inbound.

6.3 Headers

Header Description
X-IMBox-Event The event name.
X-IMBox-Event-Id Unique delivery identifier, useful for deduplication.
X-IMBox-Timestamp Unix epoch seconds, included in the signature.
X-IMBox-Signature sha256=<hex digest>.

6.4 Verifying a callback

import hmac, hashlib, time

def verify(secret, headers, raw_body):
    timestamp = headers.get("X-IMBox-Timestamp", "")
    signature = headers.get("X-IMBox-Signature", "")
    if not timestamp or not signature:
        return False
    if abs(time.time() - int(timestamp)) > 300:      # reject stale replays
        return False
    expected = hmac.new(
        secret.encode(),
        f"{timestamp}.".encode() + raw_body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", signature)

Verify against the raw body, before any JSON parsing — re-serialising changes the bytes and the signature will not match.

6.5 Delivery behaviour

Property Value
Method POST, Content-Type: application/json
Timeout 10 seconds
Success Any 2xx
Retries Up to 5 attempts, exponential backoff (2s, 4s, 8s, 16s, 32s)
Ordering Not guaranteed — use occurred_at
Duplicates Possible — deduplicate on event_id

Delivery never blocks the API call that triggered it. Every attempt is visible on the console’s Deliveries page. If no endpoint is configured, the event is still recorded and remains readable through the activity log.

7 Errors

Every failure uses one envelope.

{
  "error": "no_capacity",
  "detail": "No free cell of the requested size at that station.",
  "request_id": "8f2c4b1e9a7d43c6b0e5f1a29d3c7b84"
}
Field Description
error Stable machine-readable code. Branch on this.
detail Human-readable explanation. Do not parse.
request_id Correlation identifier. Quote it in support requests.
fields Present on validation errors: field name to messages.

request_id is also returned on success, in the X-Request-Id response header.

7.1 Status codes

Status Meaning Action
200 / 201 Success —
400 Validation failure Correct the request. Inspect fields.
401 Missing or expired token Obtain a new token.
403 Insufficient scope Request the required scope.
404 Not found for your organisation Check the identifier.
409 Conflicting state or no capacity Retry with different parameters.
422 Provisioning failed Transient service issue; retry.
429 Rate limited Back off and retry.
502 / 503 Service unavailable Retry with backoff.

7.2 Error codes

error Meaning
validation_error A field is missing, malformed or not part of the contract.
conflict An order with that reference already exists.
no_capacity No free cell of the requested size.
provisioning_failed A referenced record could not be created.
not_found No such resource for your organisation.
insufficient_scope The token lacks the required scope.
upstream_unavailable The service is temporarily unavailable.
upstream_rejected The service declined the operation.

8 The data-acquisition model

This section documents how the API keeps your data yours, because the mechanism matters more than the assurance.

8.1 What is refused

The order endpoint accepts a closed set of fields. Anything outside it is rejected with 400 and the offending field names returned. customer_name, customer_phone, address, email are not “ignored” — they cause the request to fail. Your integration cannot accidentally leak personal data into the API, because there is no code path that accepts it.

8.2 What is stored

The API’s schema contains no column for a name, telephone number, email address or postal address. The complete record of a party is:

Column Content
external_id The identifier you chose, e.g. CUST-9917
internal_id The API’s own record number
pseudonym A keyed hash of your identifier
provisioned_at When the record was created

There is nowhere to put personal data even if someone tried.

8.3 How a party is represented

A party record requires a name, an email address and a telephone number, unique across the system. The API satisfies those constraints without involving a real person, by deriving every value from a pseudonym:

pseudonym = HMAC-SHA256(server_key, "customer:CUST-9917")[:12]
          = "b039cbaa1024"
Field Value Property
username vf_cus_b039cbaa1024 Unique, stable, opaque
name Vodafone Customer B039CB Meaningless to a reader
email cus-b039cbaa1024@vodafone.invalid .invalid can never receive mail (RFC 2606)
mobile +201073806589 Derived from the hash; satisfies uniqueness
employee_id b039cbaa1024 The pseudonym, used to find the record again

The mapping is deterministic, so the same identifier always resolves to the same record and no duplicate is ever created. It is keyed, so your identifiers cannot be recovered from anything the API holds. And the addresses are undeliverable by construction, so no message can escape to a real recipient.

8.4 Consequences

  • The link between an identifier and a person exists only in your systems.
  • The API cannot re-identify anyone.
  • A database disclosure exposes identifiers, not people.
  • Your obligations under data-protection regimes are materially narrowed, because the personal data never leaves your control in the first place.

9 Extensibility

The API is designed to absorb change rather than transmit it.

9.1 A contract that does not move

Cell sizes, station types, billing models and status vocabularies evolve over time. The API keeps a stable contract over all of it: statuses are mapped to a fixed set, size names are canonicalised, and internal identifiers are never exposed. Such changes never reach your integration.

9.2 Adding capability

New capabilities reach you by extension, not migration:

  • A new station type appears in GET /api/v1/stations/ with no client change at all.
  • A new cell size becomes an additional accepted value for cell_size.
  • A new identifier kind — a locker bank, a contract, a cost centre — follows the same resolution pattern already used for customers, couriers, billing plans and templates: send an identifier, get a resolved record.
  • A new event is added to the webhook set; endpoints you have not configured are simply not called.

Additive changes do not break existing clients. Fields are added, never repurposed, and the API is versioned under /api/v1/ so a breaking change would arrive as /api/v2/ alongside it.

9.3 Multiple organisations

Each organisation is issued its own credentials, scopes, webhook endpoints and activity log. Data is partitioned per organisation at the query layer: one organisation cannot read another’s orders, and identifier collisions between organisations are impossible because mappings are namespaced. Onboarding a new partner requires no deployment.

9.4 Availability

The API keeps its own record of every order it creates. Order history, identifier mappings and the activity log stay queryable, and read endpoints serve stored data — so your reporting does not depend on any single component being reachable.

10 Appendix A — enumerations

Order status

Value Meaning
CREATED Accepted, cell reserved, awaiting deposit
DROPPED Parcel deposited, awaiting collection
PICKED Parcel collected
EXPIRED Collection window elapsed
CANCELLED Withdrawn before collection
FAILED Could not be completed

Cell size — small, medium, large, xlarge

Cell status — free, occupied, reserved

Cell state — active, not_active

Station status — ACTIVE, INACTIVE

Date shortcuts — today, yesterday, last_week, last_month, last_year

11 Appendix B — quick reference

BASE=https://ecommerce.api.imbox.solutions

# 1. Token
TOKEN=$(curl -s -X POST $BASE/oauth2/token/ \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d grant_type=client_credentials | python -c "import sys,json;print(json.load(sys.stdin)['access_token'])")

# 2. Capacity
curl -s "$BASE/api/v1/stations/" -H "Authorization: Bearer $TOKEN"

# 3. Create an order
curl -s -X POST "$BASE/api/v1/orders/" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"station_id":"MALL-MASR-001","customer_id":"CUST-1",
       "courier_id":"CR-1","cell_size":"small","idempotency_key":"job-1"}'

# 4. Track
curl -s "$BASE/api/v1/orders/?customer_id=CUST-1" -H "Authorization: Bearer $TOKEN"

# 5. Audit
curl -s "$BASE/api/v1/logs/?order_id=VF-2026-0001" -H "Authorization: Bearer $TOKEN"

Add -k while evaluating against the private certificate authority, or supply --cacert ca.crt to verify properly.

12 Appendix C — support

Quote the request_id from the error response, or the X-Request-Id response header, in any support request. It correlates your call with the API’s internal logs.

Resource Location
Console https://ecommerce.api.imbox.solutions/console/
Health https://ecommerce.api.imbox.solutions/api/v1/health/