Parcel API Documentation
Everything you need to integrate Bantaba logistics — create parcels, track deliveries, and receive webhooks from sandbox to production.
Getting Started
Integrating with the Parcel API takes three steps:
- 1. Create an account — register (email + password or Google) and verify your email.
- 2. Generate an API key — from the API Keys page. Start with a
pk_test_sandbox key. - 3. Make your first call — create a sandbox parcel below.
curl -X POST http://localhost:5013/v1/sandbox/parcels \
-H "x-api-key: pk_test_your_key" \
-H "Content-Type: application/json" \
-d '{
"pickup": { "latitude": 13.4549, "longitude": -16.5790, "address": "Senegambia" },
"dropoff": { "latitude": 13.4432, "longitude": -16.7117, "address": "Banjul" }
}'Environments
Every account has two isolated environments. Sandbox never dispatches real drivers and is free to call while you build; production requires an active paid plan.
| Environment | Key prefix | Path | Behaviour |
|---|---|---|---|
| Sandbox | pk_test_ | /v1/sandbox/… | Simulated parcels, no real dispatch |
| Production | pk_live_ | /v1/… | Real dispatch, billed against your plan |
pk_test_ key against /v1/parcels (production) returns 403 forbidden.Base URLs:
Development: http://localhost:5013
Production: https://developer.kafayaa.comAuthentication
There are two distinct credentials. Do not confuse them:
- API keys authenticate server-to-server calls to the Parcel API via the
x-api-keyheader. Never expose them in a browser or mobile app. - Portal JWT (a bearer token from
/auth/login) authenticates you to the dashboard/management API (creating keys, webhooks, billing).
# Parcel API call — API key
curl http://localhost:5013/v1/sandbox/parcels/PARCEL_ID \
-H "x-api-key: pk_test_your_key"
# Management API call — portal JWT
curl http://localhost:5011/auth/me \
-H "Authorization: Bearer YOUR_JWT"API Keys
Keys are shown in full once at creation — only a hash and the prefix are stored. Store the secret in your server-side secret manager immediately. Each key carries scopes: parcel:read and/or parcel:write.
/api-keyscurl -X POST http://localhost:5011/api-keys \
-H "Authorization: Bearer YOUR_JWT" \
-H "Content-Type: application/json" \
-d '{ "name": "Backend server", "environment": "SANDBOX" }'{
"result": "success",
"data": {
"id": "clx…",
"name": "Backend server",
"prefix": "pk_test_1a2b3c4d",
"environment": "SANDBOX",
"scopes": ["parcel:read", "parcel:write"],
"secret": "pk_test_1a2b3c4d…shown-once",
"createdAt": "2026-07-18T10:00:00.000Z"
}
}Rotate a compromised key with POST /api-keys/:id/rotate (revokes the old, returns a new secret) and revoke with DELETE /api-keys/:id.
SDKs
Official SDKs wrap authentication, retries, and typing for you.
Node.js / TypeScript
npm install @tripgee/parcel-sdkimport { TripGeeParcelClient } from "@tripgee/parcel-sdk";
const client = new TripGeeParcelClient({
apiKey: process.env.TRIPGEE_API_KEY!, // pk_test_… or pk_live_…
baseUrl: "http://localhost:5013",
});
const parcel = await client.createParcel({
pickup: { latitude: 13.4549, longitude: -16.579, address: "Senegambia" },
dropoff: { latitude: 13.4432, longitude: -16.7117, address: "Banjul" },
});
console.log(parcel.id);Python
pip install tripgee-parcel # or: pip install -e packages/parcel-sdk-pythonfrom tripgee_parcel import TripGeeParcelClient
client = TripGeeParcelClient("pk_test_…", base_url="http://localhost:5013")
parcel = client.create_parcel(
pickup={"latitude": 13.4549, "longitude": -16.579, "address": "Senegambia"},
dropoff={"latitude": 13.4432, "longitude": -16.7117, "address": "Banjul"},
)
print(parcel)REST API — Parcels
All request/response bodies are JSON. Sandbox paths are prefixed with /v1/sandbox; production paths use /v1. Success responses use { "result": "success", "data": … }.
/v1/parcelsCreate a parcel delivery. Body fields:
| Field | Type | Required |
|---|---|---|
pickup.latitude / longitude | number | yes |
pickup.address | string | no |
dropoff.latitude / longitude | number | yes |
vehicleTypeId | string | no |
paymentMethod | string | no |
notes | string | no |
metadata | object | no |
{
"result": "success",
"data": {
"id": "prc_1a2b3c",
"status": "PENDING",
"environment": "SANDBOX",
"pickup": { "latitude": 13.4549, "longitude": -16.579 },
"dropoff": { "latitude": 13.4432, "longitude": -16.7117 },
"createdAt": "2026-07-18T10:00:00.000Z"
}
}/v1/parcels/:idRetrieve a parcel by id.
/v1/parcels/:id/trackReturn the live status and driver location for a parcel.
/v1/parcels/:id/cancelCancel a parcel that has not yet been delivered.
/v1/sandbox/parcels/:id/simulateSandbox only. Advance a test parcel through its lifecycle (e.g. accepted → picked up → delivered) so you can exercise your integration without a real driver.
/v1/sandbox/errors/:codeSandbox only. Force any documented error response (see Error Handling) to test your error paths.
Webhooks
Register an HTTPS endpoint to receive parcel lifecycle events instead of polling. Configure webhooks per environment from the dashboard or the management API.
/webhookscurl -X POST http://localhost:5013/webhooks \
-H "Authorization: Bearer YOUR_JWT" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/hooks/bantaba",
"environment": "SANDBOX",
"events": ["parcel.created", "parcel.picked_up", "parcel.delivered", "parcel.cancelled"]
}'Each delivery is a POST with a JSON event body. Inspect recent attempts (status, response code, retries) via GET /webhooks/:id/deliveries. Always respond 2xx quickly and process asynchronously.
GET /v1/parcels/:id before acting on it.Error Handling
Errors return the matching HTTP status and a body of the shape { "result": "fail", "error": "code", "message": "…" }. Branch on the stable error code, not the human message.
| Status | error | Meaning |
|---|---|---|
400 | validation_error | A request field is missing or malformed |
401 | invalid_api_key | Missing, revoked, or unknown API key |
403 | forbidden | Key lacks the required scope or wrong environment |
403 | plan_restriction | Plan does not allow production access |
404 | not_found | Parcel (or resource) does not exist |
429 | rate_limited | Too many requests this minute |
429 | quota_exceeded | Monthly request quota exhausted |
Rate Limits
Requests are limited per minute by your plan (default 60 requests/minute) and by a monthly quota. When you exceed the per-minute limit you receive 429 rate_limited; when the monthly quota is exhausted you receive 429 quota_exceeded.
429 using exponential retry with jitter, and cache reads where possible. Upgrade your plan in the portal to raise both limits.Pagination
List endpoints accept limit (default 20, max 100) and either a cursor or page query parameter, and return the items newest first alongside a nextCursor when more results exist.
curl "http://localhost:5013/v1/parcels?limit=50&cursor=prc_last_seen" \
-H "x-api-key: pk_live_your_key"Production Deployment
Before switching from sandbox to production:
- Verify your email and confirm your billing plan allows production access.
- Create a separate
pk_live_key; never reuse sandbox keys. - Store secrets in your platform’s secret manager (env vars / vault) — never in source control or client bundles.
- Point calls at the production base URL and switch paths from
/v1/sandboxto/v1. - Register production webhooks over HTTPS and handle retries idempotently.
Best Practices
- Call the API only from your backend; keep keys off the client.
- Use the SDKs — they handle auth headers, retries, and typing.
- Attach your own
metadata(e.g. order id) to correlate parcels. - Prefer webhooks over polling for status updates.
- Make webhook handlers idempotent — the same event may arrive more than once.
- Test every error path in sandbox with
/v1/sandbox/errors/:code. - Rotate keys periodically and immediately on suspected exposure.
Troubleshooting
401 invalid_api_key
Confirm the x-api-key header is present, the key is active (not revoked), and you copied the full secret shown at creation.
403 forbidden
The key is being used against the wrong environment path, or lacks the required scope (parcel:write to create).
403 plan_restriction
Your plan does not include production. Use sandbox or upgrade in Billing.
429 rate_limited / quota_exceeded
You hit the per-minute or monthly limit. Back off and retry, or upgrade your plan.
Webhook not received
Ensure the URL is public HTTPS and returns 2xx quickly. Check GET /webhooks/:id/deliveries for the response code and retry history.
FAQs
+ - Is sandbox free?
Yes. Sandbox calls never dispatch a real driver and do not count against a paid plan, so you can build and test freely.
+ - How many API keys can I create?
As many as you need — one per service or environment is a good pattern. Revoke unused keys.
+ - What happens if I lose a key secret?
Secrets are shown once and stored only as a hash. Rotate the key to get a new secret; the old one stops working.
+ - Can I use both password and Google sign-in?
Yes. Signing in with Google on an existing email links the two; you can continue using either.
+ - How do I move from test to live?
See Production Deployment: create a pk_live_ key, ensure your plan allows production, and switch the path from /v1/sandbox to /v1.