Developers

Blink Business API

Quote, book and track deliveries from your store or system. Sandbox, scoped keys, idempotency and signed webhooks.

Download openapi.yamlGet an API key

Quickstart

JSON over HTTPS. Money is integer sen (RM1 = 100 sen). Build against the sandbox with a test key, then swap in a live key.

  1. Create a test key. In the business portal → Developer → API keys → New key → mode Test. You get blink_test_… once; store it as BLINK_KEY. Or create one on the Try it page.
  2. Get a quote.
    cURL
    curl -X POST https://api-kirim.159-223-134-64.sslip.io/v1/quote \
      -H "Authorization: Bearer $BLINK_KEY" \
      -H "Content-Type: application/json" \
      -d '{"vehicle":"motorcycle","stops":[
            {"address":"Bandar Sunway","lat":3.0733,"lng":101.6078},
            {"address":"SS2, Petaling Jaya","lat":3.118,"lng":101.623}]}'
  3. Create the order. Send an Idempotency-Key so a retried request never books twice, and your own order number as externalRef.
    cURL
    curl -X POST https://api-kirim.159-223-134-64.sslip.io/v1/orders \
      -H "Authorization: Bearer $BLINK_KEY" \
      -H "Idempotency-Key: WC-10421-create" \
      -H "Content-Type: application/json" \
      -d '{"vehicle":"motorcycle","paymentMethod":"credit","externalRef":"WC-10421",
           "stops":[{"address":"Bandar Sunway","lat":3.0733,"lng":101.6078},
                    {"address":"SS2, Petaling Jaya","lat":3.118,"lng":101.623,
                     "contactName":"Aisyah","contactPhone":"0172223344"}]}'
  4. Watch it move. Sandbox orders are driven by a simulator (assigned → at pickup → picked up → in transit → delivered, a few seconds per step) and fire real webhooks. Skip ahead with the test helper:
    bash
    # Sandbox only (blink_test_ key): jump a test order to any status; every webhook in between fires.
    curl -X POST https://api-kirim.159-223-134-64.sslip.io/v1/sandbox/orders/BL3F9A21C0DE/status \
      -H "Authorization: Bearer $BLINK_TEST_KEY" \
      -H "Content-Type: application/json" \
      -d '{"status":"delivered"}'
  5. Receive webhooks. Add an HTTPS endpoint in the portal, pick events, and verify every request (below).

Authentication & scopes

Send a key as a bearer token: Authorization: Bearer blink_live_…. Keys belong to one business and can be named, limited to scopes, given an expiry date and an IP allowlist (IPv4 CIDR or exact addresses). Last-used time and IP are shown in the portal. Keys cannot manage other keys.

PrefixKindUse
blink_live_Secret, liveServer-side. Real orders, real drivers, billed.
blink_test_Secret, sandboxServer-side. Sandbox orders only: simulated, never dispatched or billed.
pk_live_ / pk_test_PublishableSafe in web pages. Quotes and widgets only, from the key's allowed domains.
ScopeAllows
orders:readGET /v1/orders, /v1/orders/{code}, recipients, sandbox reads
orders:writeCreate / cancel orders, stops, bulk, plan-day, recurring, sandbox helpers
quotesPOST /v1/quote, /v1/route/optimize (the only scope publishable keys get)
webhooksManage webhook endpoints, delivery log, replay and test events
reportsAnalytics, stop reports, invoices, calendar

A key without the needed scope gets 403 API key is missing the … scope; expired keys get 401, calls from outside the IP allowlist 403.

Sandbox

Every business has a sandbox on the production API. Orders created with a blink_test_ key are priced like real ones and flagged sandbox: true, but they are never offered to drivers, never posted to your ledger or invoices, and never counted in analytics or ops boards. A fake rider moves them through every status and attaches a proof of delivery; webhooks fire to endpoints whose mode is test or all, with data.sandbox: true and header X-Blink-Mode: test.

POST /v1/sandbox/orders/{code}/status with {"status":"delivered"|"failed"|"cancelled"|…} forces a status (and pauses the simulator for that order). Listing with a test key returns sandbox orders only. externalRef is unique per mode, so tests can reuse real order numbers.

Idempotency

Every POST and PATCH under /v1 accepts an Idempotency-Key header (1–255 printable characters). The first response is stored for 24 hours, per key holder:

Repeat with…Result
Same key, same bodyThe stored status and body, plus Idempotent-Replayed: true.
Same key, different body422 — use a new key for a new request.
Same key while the first is still running409 — retry shortly.
After a 5xxNot stored: retry with the same key.

externalRef on orders remains a second, permanent guard: a repeated ref returns the existing order with duplicate: true.

Webhooks

Add up to 20 endpoints, each with its own secret, event filter (order.*, stop.completed, …) and mode. Failed deliveries retry with exponential backoff (30 s, 1 min, 2 min … up to 1 day) and are dead-lettered after the endpoint's max attempts (default 8); replay any delivery from the log. See the event catalogue.

Node
import { createHmac, timingSafeEqual } from "node:crypto";

// v2: X-Blink-Signature-V2: t=<unix seconds>,v1=<hex HMAC-SHA256("<t>.<raw body>", secret)>
export function verifyBlink(rawBody: string, header: string, secret: string, toleranceS = 300) {
  const m = /t=(\d+),v1=([0-9a-f]+)/.exec(header ?? "");
  if (!m || Math.abs(Date.now() / 1000 - Number(m[1])) > toleranceS) return false;
  const expected = createHmac("sha256", secret).update(`${m[1]}.${rawBody}`).digest("hex");
  return expected.length === m[2].length && timingSafeEqual(Buffer.from(expected), Buffer.from(m[2]));
}

// express: app.post("/blink/webhook", express.raw({ type: "application/json" }), (req, res) => {
//   if (!verifyBlink(req.body.toString("utf8"), req.header("x-blink-signature-v2"), process.env.BLINK_WEBHOOK_SECRET)) return res.sendStatus(401);
//   const { event, data } = JSON.parse(req.body); res.sendStatus(200);
// });

SDKs & plugins

Zero-dependency SDKs live in sdks/: TypeScript (fetch), PHP (curl) and Python (stdlib). Each sets idempotency keys and verifies webhook signatures. The WooCommerce plugin shows live rates at checkout and books on processing. Embeddable fee and booking widgets use publishable keys.

Errors

StatusMeaning
400{"error":"validation","issues":[…]} — fix the request.
401Missing, invalid, revoked or expired key.
402Credit limit exceeded.
403Missing scope, IP not allowed, or domain not allowed (publishable keys).
404Not found or not yours.
409Not allowed in the current state, or the same Idempotency-Key is in flight.
422Business rule failed, or Idempotency-Key reused with a different body.
429Rate limited — back off and retry.

Machine-readable contract: openapi.yaml (OpenAPI 3.1), browsable in the reference.