Dawurobo Partner Platform

Dawurobo API error codes & how to fix them

Decode Dawurobo Partner Platform API error responses — 401, 402, 409, 429 and the signing, scope, nonce, and wallet failures behind them — with the exact fix for each.

Every error response has the shape:

{
  "status": "error",
  "code": "ERROR_CODE",
  "message": "Human-readable description"
}

Authentication errors

MISSING_API_KEY (401)

Cause: No X-API-Key header (and no Authorization: Bearer fallback).

Fix: Send X-API-Key: <your_api_key> on every request.

INVALID_API_KEY (401)

Cause: The key doesn't resolve to any partner app.

Fix: Confirm you're using the right key for the target environment (staging vs production).

INSUFFICIENT_SCOPE (403)

Cause: Your key doesn't carry the scope required for this operation (see the operation's x-required-scope in the API Reference). The most common case: calling a money-moving operation — wallet endpoints, wallet-funded orders.create, or Safe orders.create — with a key that only has broad scopes. Money scopes (delivery:wallet:topup, delivery:wallet:spend, safe:write) are exact-only: a delivery:* or * wildcard never grants them.

Fix: Request a key that carries the needed scope by name (for wallet-funded orders that's delivery:wallet:spend). A wildcard key is enough only for reads and standard writes.

PARTNER_INACTIVE (403)

Cause: The partner account is suspended or not yet active.

Fix: Contact Dawurobo support to resolve account status.

Signature and replay errors

MISSING_SIGNATURE_HEADERS (401)

Cause: Missing one or more of X-Signature, X-Timestamp, X-Nonce.

Fix: Include all three signed headers on every request.

STALE_REQUEST (401)

Cause: X-Timestamp is invalid, or more than 5 minutes away from server time.

Fix: Send Unix seconds and keep your server clock NTP-synced.

SIGNING_UNAVAILABLE (401)

Cause: The platform couldn't resolve a signing key for this API key (server-side configuration issue, not something you can fix client-side).

Fix: Contact Dawurobo support with the timestamp, endpoint, and key ID you used.

INVALID_SIGNATURE (401)

Cause: Canonical string mismatch — wrong signing secret, or the signed bytes don't match what was actually sent.

Fix:

  1. Rebuild the canonical string exactly as described in Authentication and signing.
  2. Sign with your key's signing secret, not the API key itself.
  3. Make sure the query string and body bytes you sign match what's actually sent on the wire.

REPLAY_DETECTED (409)

Cause: A nonce was reused.

Fix: Generate a fresh, unique nonce (e.g. a UUID) for every request.

Rate limiting

RATE_LIMITED (429)

Cause: Exceeded 120 req/min (reads) or 60 req/min (writes) for this key.

Fix: Back off and retry with jitter; consider caching read responses where possible.

Operation errors

UNKNOWN_OPERATION (404)

Cause: The service/operation combination doesn't exist — usually a typo'd path.

Fix: Double-check the path against the API Reference.

VALIDATION_ERROR (400)

Cause: The request body doesn't match the operation's published schema (a required field is missing, a field has the wrong type, or an enum value isn't allowed). The message names the exact field.

Fix: Check the operation's request schema in the API Reference and send exactly the documented shape.

ENVIRONMENT_NOT_ACTIVE (403)

Cause: The key's environment isn't active for its app — a production key whose production access hasn't been approved yet, or a staging key on a suspended app.

Fix: For production, complete the approval flow in Partner Hub; use a staging key while you finish testing.

Money and order errors

INSUFFICIENT_WALLET_BALANCE (402)

Cause: A wallet-funded delivery order (payment.payer: "partner") costs more than your wallet balance (and any credit headroom). The response includes balance_ghs and required_ghs.

Fix: Top up your wallet and retry, or create the order as recipient-pays.

COORDINATES_REQUIRED (400)

Cause: A wallet-funded order was created without valid pickup + delivery coordinates. The fee for wallet-funded orders is always priced server-side from distance — it's never taken from payment.amount.

Fix: Include pickup.coordinates and delivery.coordinates (lat/lng) on wallet-funded orders.

DUPLICATE_ORDER_REFERENCE (409)

Cause: An order with this order_reference already exists (or is being created right now) for your app. This is the idempotency guard doing its job — a retried create can't double-charge.

Fix: If you're retrying after a timeout, a 409 usually means the original create landed — confirm with orders.get. For a genuinely new order, use a fresh order_reference.

ORDER_NOT_FOUND (404)

Cause: The order_id doesn't exist or doesn't belong to your app — both cases return the same 404 by design.

Fix: Use the order_id returned by your own orders.create calls.

INVALID_ORDER_STATUS (400)

Cause: Cancelling an order that's already delivered or cancelled.

Fix: Check current status with orders.get before cancelling.

WALLET_PRODUCTION_ONLY / SYNDICATION_PRODUCTION_ONLY (403)

Cause: Wallet endpoints and Safe orders.create/orders.verify charge a real payment gateway, so they require a production key — staging keys are rejected.

Fix: Finish the production activation step (approval + KYC), then use your production key.

Safe catalog order errors

These come from safe-catalog/orders.create (and catalog.get / orders.get / orders.verify).

VENDOR_NOT_AVAILABLE (404)

Cause: The vendor_id isn't an opted-in, syndicatable vendor (not enrolled, paused, or removed).

Fix: Only order from vendors returned by catalog.list — they're the ones open to reselling.

PRODUCT_NOT_FOUND (404)

Cause: The product_id doesn't exist, is inactive, or its vendor isn't syndicatable — all return the same 404.

Fix: Use product_id/variant_id values from catalog.list / catalog.get.

SINGLE_VENDOR_ONLY (400)

Cause: The order mixes items from more than one vendor. Syndicated orders are single-vendor.

Fix: Split into one order per vendor.

ITEM_UNAVAILABLE (400) / OUT_OF_STOCK (409)

Cause: A variant is no longer available, or there isn't enough stock to fill the requested quantity.

Fix: Re-check availability with catalog.get and adjust the quantity, or try again later.

INVALID_MARKUP (400) / MARKUP_NOT_SUPPORTED_FOR_COD (400)

Cause: reseller_markup_ghs is out of range (must be 0 to the subtotal), or is greater than 0 on a cash-on-delivery order — COD collects only subtotal + delivery fee, so you collect your markup from your buyer yourself.

Fix: Send a markup between 0 and the subtotal for prepaid orders; omit it (or send 0) for COD.

MIXED_CURRENCY (400)

Cause: The order's items aren't all in the same currency.

Fix: Order items priced in a single currency.

DUPLICATE_REFERENCE (409)

Cause: An order with this external_reference already exists for your app (the idempotency guard).

Fix: A 409 on retry means the first order landed — call orders.get. For a new order, use a fresh external_reference.

SALE_NOT_FOUND (404)

Cause: The sale_id (on orders.get / orders.verify) doesn't exist or doesn't belong to your app.

Fix: Use the sale_id returned by your own orders.create.

Wallet errors

BELOW_MINIMUM (400)

Cause: A wallet top-up is below the minimum (GHS 30).

Fix: Top up at least GHS 30.

TOPUP_NOT_FOUND (404)

Cause: The client_reference passed to wallet.topup.verify doesn't match a top-up you started (or isn't yours).

Fix: Use the client_reference returned by wallet.topup.initiate.

Webhook issues

Webhooks not received

  1. Confirm the webhook URL is configured for your app in Partner Hub.
  2. Confirm your endpoint returns 2xx quickly (slow receivers can time out).
  3. Check webhook job status (pending / retrying / dead) in Partner Hub.
  4. Make sure your endpoint is publicly reachable (not behind auth or a firewall that blocks Dawurobo's egress IPs).

Signature verification fails

Cause: Verifying the parsed/re-serialized JSON instead of the raw body, or using the wrong webhook secret.

Fix: Verify HMAC over the raw request body string, using the current secret from Partner Hub. See Webhooks.

What to share with support

When contacting Dawurobo support about an integration issue, include:

  • Request method and path
  • Timestamp and nonce used
  • Response code and code from the error body
  • App ID and environment (staging vs production)

See Get help for how to reach us and what to expect.