Get API key
Getting started · 4 min read

Quickstart

Sandbox key to live mode in four steps. Every step shows curl, Node, Python and PHP — copy the one that matches your stack.

1

Get your API key

Mint your own key from the dashboard — no admin email required. Sign in → open API & Webhooks → pick the environment (sandbox / live) → click New key.

  1. Sign in to the dashboard. Open API keys
  2. Open API & Webhooks → New key.
  3. Pick the environment + label. The plaintext is shown EXACTLY ONCE — copy it into env / vault before closing the modal.

Key format: mk_test_* for sandbox · mk_live_* for live. Prefixes never cross. Max 5 active keys per account.

Base URL — https://merdiexpress.com/api/v1/ (path-based, no subdomain). e.g. GET /api/v1/shipments
Always pass keys via environment variables — export MERDI_KEY=mk_test_…
2

Create your first shipment

POST to /v1/shipments. Include an Idempotency-Key header — retries within 24h return the original response without creating a duplicate shipment.

service_tier_id takes a Service Code (e.g. HKASTDGGSTCM). How codes are built and which you can use → Service Codes guide.
curl https://merdiexpress.com/api/v1/shipments \
  -H "Authorization: Bearer mk_test_rh9ABekJfP31cN7sQ8jKvLwY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "service_tier_id": "HKASTDGGSTCM",
    "recipient": {
      "contact_name": "John Doe", "phone": "+14155551234",
      "country_code": "US", "city": "San Francisco",
      "district": "CA", "address": "1 Hacker Way",
      "postal_code": "94025"
    },
    "parcels": [{ "weight_kg": 0.5 }],
    "items": [{ "description": "Sample item", "quantity": 1, "unit_price_hkd": 100 }],
    "goods_category": "general", "payment_type": "prepaid"
  }'

Expected response:

201 Created
{
  "status": 201,
  "message": "Created",
  "data": {
    "id": "IM-2026-0528-9ZNCP3-1",
    "tracking_number": "IM-2026-0528-9ZNCP3-1",
    "status": "label_created",
    "service_tier_id": "HKASTDGGSTCM",
    "livemode": false,
    "created_at": "2026-05-28T07:44:23.908Z"
  }
}
3

Verify a webhook

When you register a webhook you receive a one-time signing_secret. Every event we POST carries an X-Merdi-Signature header in the form t=<unix>,v1=<hex>.

import crypto from 'node:crypto';

export default function handler(req, res) {
  const raw = req.rawBody;                                    // unparsed body
  const sig = req.headers['x-merdi-signature'];               // "t=...,v1=..."
  const [tPart, v1Part] = sig.split(',');
  const ts  = tPart.split('=')[1];
  const sent = v1Part.split('=')[1];

  const expected = crypto
    .createHmac('sha256', process.env.MERDI_WEBHOOK_SECRET)   // whsec_...
    .update(`${ts}.${raw}`)
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(sent), Buffer.from(expected))) {
    return res.status(400).send('invalid signature');
  }
  res.status(200).send('ok');
}

See Webhooks guide for retry policy and event catalog.

4

Switch to live mode

Swap mk_test_ for mk_live_ — the URL doesn't change. Before flipping, run the dashboard's "last-call lint" to confirm every POST in the last 24h carried an Idempotency-Key.

diff
- export MERDI_KEY="mk_test_rh9ABekJfP31cN7sQ8jKvLwY"
+ export MERDI_KEY="mk_live_8ZqAvR2nLkPyWxV4DfHcM6tB"

# URL stays the same:
  https://merdiexpress.com/api/v1/shipments