← Bantaba DevelopersGet API keys

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. 1. Create an account — register (email + password or Google) and verify your email.
  2. 2. Generate an API key — from the API Keys page. Start with a pk_test_ sandbox key.
  3. 3. Make your first call — create a sandbox parcel below.
bash
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.

EnvironmentKey prefixPathBehaviour
Sandboxpk_test_/v1/sandbox/…Simulated parcels, no real dispatch
Productionpk_live_/v1/…Real dispatch, billed against your plan
A key only works on its own environment path. Using a pk_test_ key against /v1/parcels (production) returns 403 forbidden.

Base URLs:

text
Development:  http://localhost:5013
Production:   https://developer.kafayaa.com

Authentication

There are two distinct credentials. Do not confuse them:

  • API keys authenticate server-to-server calls to the Parcel API via the x-api-key header. 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).
bash
# 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"
Portal accounts support email/password and Google sign-in. Email verification is required before production access when enabled by your organization.

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.

POST/api-keys
bash
curl -X POST http://localhost:5011/api-keys \
  -H "Authorization: Bearer YOUR_JWT" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Backend server", "environment": "SANDBOX" }'
json
{
  "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

bash
npm install @tripgee/parcel-sdk
typescript
import { 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

bash
pip install tripgee-parcel  # or: pip install -e packages/parcel-sdk-python
python
from 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": … }.

POST/v1/parcels

Create a parcel delivery. Body fields:

FieldTypeRequired
pickup.latitude / longitudenumberyes
pickup.addressstringno
dropoff.latitude / longitudenumberyes
vehicleTypeIdstringno
paymentMethodstringno
notesstringno
metadataobjectno
json
{
  "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"
  }
}
GET/v1/parcels/:id

Retrieve a parcel by id.

GET/v1/parcels/:id/track

Return the live status and driver location for a parcel.

POST/v1/parcels/:id/cancel

Cancel a parcel that has not yet been delivered.

POST/v1/sandbox/parcels/:id/simulate

Sandbox only. Advance a test parcel through its lifecycle (e.g. accepted → picked up → delivered) so you can exercise your integration without a real driver.

GET/v1/sandbox/errors/:code

Sandbox 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.

POST/webhooks
bash
curl -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.

Verify authenticity by checking the signature header and confirming the event against 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.

StatuserrorMeaning
400validation_errorA request field is missing or malformed
401invalid_api_keyMissing, revoked, or unknown API key
403forbiddenKey lacks the required scope or wrong environment
403plan_restrictionPlan does not allow production access
404not_foundParcel (or resource) does not exist
429rate_limitedToo many requests this minute
429quota_exceededMonthly 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.

Back off on 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.

bash
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/sandbox to /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.