Skip to main content

Capture links

A buyer outside DawaTrack records receiving a consignment, with the one-time token the pharmacy sent it.

Ask for a capture code

POST https://api.dawatrack.com/partner-api/v1/capture/code/

Email the 6-digit code a receiver enters to confirm a consignment. No account: the token is the credential.

No credentials needed

The code goes to the receiver's email on file with its sender (where the link was emailed). Only when there is none, send email: the code goes there, and it becomes the receiver's contact.

A code works once and for 10 minutes; 5 wrong tries lock it; a new code voids the one before; at most 5 codes a link per hour (too_many_codes, 429). DawaTrack stores only a keyed hash of it.

Returns 202 with sent_to, the address masked. 404 for an unknown token, 410 when it was used, withdrawn or expired, 400 email_required when there is no address to send to.

Request body

JSON. Send Content-Type: application/json.

  • token string Required

    The secret in the link the sender sent you.

  • email string Optional

    Where to send the code, only when the sender has no email on file for you.

Example request

curl -X POST https://api.dawatrack.com/partner-api/v1/capture/code/ \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "token": "q3V8m1x0K9cJr2Zt7bWfY4nH6sL5aP0dE8gU1iO3kTw"
  }'
import uuid
import requests

response = requests.post(
    "https://api.dawatrack.com/partner-api/v1/capture/code/",
    headers={"Idempotency-Key": str(uuid.uuid4())},
    json={
      "token": "q3V8m1x0K9cJr2Zt7bWfY4nH6sL5aP0dE8gU1iO3kTw"
    },
)
response.raise_for_status()
print(response.json())
const response = await fetch("https://api.dawatrack.com/partner-api/v1/capture/code/", {
  method: "POST",
  headers: {
    "Idempotency-Key": crypto.randomUUID(),
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "token": "q3V8m1x0K9cJr2Zt7bWfY4nH6sL5aP0dE8gU1iO3kTw"
  }),
});
console.log(await response.json());

Response fields

  • sent_to string

Example response

JSON
{
  "sent_to": "s•••@upcountry.example"
}

Status codes

202Accepted: queued for processing
400The request is invalid, for example a malformed GTIN or a missing field
409Conflict: a request with this Idempotency-Key is in progress, or the record already exists
429Rate limit exceeded

Record a captured receipt

POST https://api.dawatrack.com/partner-api/v1/capture/receive/

A buyer outside DawaTrack records receiving one consignment, line by line. No account: the token is the credential.

No credentials needed

The token comes in the link DawaTrack emails a receiver outside DawaTrack the moment a consignment to it is recorded, by a pharmacy, a supplier on the portal or a partner here. The same capture can be made on the page at /capture/{token}/. The Shipments and confirmation guide shows the whole flow.

A token is a random secret, shown once to the pharmacy and stored by DawaTrack only as its SHA-256. It works once and lasts 30 days; the pharmacy can withdraw it before use, and making a new one withdraws the open one.

Send lines: each line's batch_number (and gtin, if given), the quantity received of it (damaged units included; 0 for a line that didn't arrive), and optionally damaged (of those received, the units that arrived damaged) and refused (units sent back with the delivery). Every line you send is answered, as in a GS1 RECADV (Receiving Advice): units neither received nor refused are recorded short. Received plus refused is at most what was sent (refused_out_of_range), damaged at most what was received (damaged_out_of_range). A line you leave out stays open. For a one-line consignment, batch_number and quantity (with damaged and refused) alone also work.

scans (optional): the text of what you scanned, a pack's GS1 DataMatrix or Digital Link, or a carton's SSCC. DawaTrack reads each against the lines: a pack matches the line with its GTIN and batch (and its expiry, when the pack carries one), a carton the line shipped under its SSCC. A plain barcode has no batch and matches nothing. Each receipt then records batch_scanned_and_matched, true when a scan matched it, returned on each RECEIVE in events.

delivery_note (optional, multipart): a photo (JPEG, PNG, WebP) or PDF of the delivery note you signed, up to 10 MB, read by its first bytes (delivery_note_unreadable, delivery_note_too_large). It is kept off the ledger, privately, and only the sender can open it.

phone is required: the receiver's business phone, with its country code (+254…; a Kenyan 07… or 01… number works too), else 400 phone_required. It is kept with the receiver's record in E.164, with the verified email, off the ledger: how a recall reaches it.

consent must be true: the record is written to the ledger, can't be changed afterwards, and is visible to the sender, the supplier and regulators tracing or recalling the batch.

code is required: the 6-digit code from POST /capture/code/, which proves the receiver reads the email on file. A wrong, used or expired code is 400 (code_invalid, no_code, code_expired), 429 once locked. The address the code went to is kept with the receiver's record, off the ledger, for recall notices; email is ignored.

Writes a RECEIVE per line received, whose parent is the line's dispatch, with link_confidence INFERRED, and a RECEIPT_ANSWER per line short, damaged or refused: quantity 0 (it moves no stock), parent the dispatch, with expected, received, short, damaged and refused. The sender sees it; refused units stay the sender's to record as a return. Traces don't follow a RECEIPT_ANSWER and EPCIS leaves it out. events lists them line by line, each with its event_type. 404 for an unknown token, 410 when it was used, withdrawn or expired, 409 when the consignment is already recorded, 400 for a line it doesn't have, 429 after 10 attempts from one IP in an hour.

Request body

JSON. Send Content-Type: application/json.

  • token string Required

    The secret in the link the sender sent you.

  • code string Required

    The 6-digit code emailed to you (POST /capture/code/), valid 10 minutes, once.

  • company_name string Required
  • ppb_licence string Optional
  • attester_name string Required
  • email string Optional

    Ignored: the email the code was sent to is kept, off the ledger, for recall notices.

  • phone string Required

    Your business phone, with its country code (+254...; a Kenyan 07... or 01... number works too). Kept with your record in E.164, off the ledger, for recall notices.

  • gtin string Optional

    The product's GS1 GTIN, as 14 digits.

  • batch_number string Optional

    A one-line consignment: the line's batch. Or send lines.

  • quantity integer Optional

    A one-line consignment: units received, damaged ones included.

  • damaged integer Optional

    A one-line consignment: units received damaged.

    Default: 0

  • refused integer Optional

    A one-line consignment: units refused.

    Default: 0

  • lines array of objects Optional

    Each line of the consignment (GTIN, batch): the units received, damaged and refused. Every line given is answered; units neither received nor refused are recorded short.

    • gtin string Optional

      The product's GS1 GTIN, as 14 digits.

    • batch_number string Required

      The batch or lot number.

    • quantity integer Required

      Units received of this line, damaged ones included; 0 for a line that didn't arrive.

    • damaged integer Optional

      Of the units received, those that arrived damaged.

      Default: 0

    • refused integer Optional

      Units refused and sent back with the delivery.

      Default: 0

  • captured_at datetime Required

    When the stock was received.

  • consent boolean Required

    True: the submitter has read and accepts how the record is kept.

  • scans array of strings Optional

    What you scanned: packs' GS1 DataMatrix or Digital Link text, or cartons' SSCC. Each line's receipt records whether a scan matched its GTIN and batch (batch_scanned_and_matched).

  • delivery_note URL or null Optional

    A photo (JPEG, PNG, WebP) or PDF of the signed delivery note, up to 10 MB; multipart only. Kept off the ledger, shown only to the sender.

Example request

curl -X POST https://api.dawatrack.com/partner-api/v1/capture/receive/ \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "token": "q3V8m1x0K9cJr2Zt7bWfY4nH6sL5aP0dE8gU1iO3kTw",
    "code": "482915",
    "company_name": "Upcountry Chemist",
    "ppb_licence": "PPB/R/7781",
    "attester_name": "Wanjiru Kamau",
    "phone": "+254712345678",
    "lines": [
      {
        "gtin": "06164004321562",
        "batch_number": "BTH-2026-041",
        "quantity": 18
      },
      {
        "gtin": "06164004321562",
        "batch_number": "BTH-2026-052",
        "quantity": 10,
        "damaged": 2,
        "refused": 4
      }
    ],
    "captured_at": "2026-10-22T10:30:00+03:00",
    "consent": true,
    "scans": [
      "]d201061640043215621727113010BTH-2026-041\u001d21SN0007731"
    ]
  }'
import uuid
import requests

response = requests.post(
    "https://api.dawatrack.com/partner-api/v1/capture/receive/",
    headers={"Idempotency-Key": str(uuid.uuid4())},
    json={
      "token": "q3V8m1x0K9cJr2Zt7bWfY4nH6sL5aP0dE8gU1iO3kTw",
      "code": "482915",
      "company_name": "Upcountry Chemist",
      "ppb_licence": "PPB/R/7781",
      "attester_name": "Wanjiru Kamau",
      "phone": "+254712345678",
      "lines": [
        {
          "gtin": "06164004321562",
          "batch_number": "BTH-2026-041",
          "quantity": 18
        },
        {
          "gtin": "06164004321562",
          "batch_number": "BTH-2026-052",
          "quantity": 10,
          "damaged": 2,
          "refused": 4
        }
      ],
      "captured_at": "2026-10-22T10:30:00+03:00",
      "consent": True,
      "scans": [
        "]d201061640043215621727113010BTH-2026-041\u001d21SN0007731"
      ]
    },
)
response.raise_for_status()
print(response.json())
const response = await fetch("https://api.dawatrack.com/partner-api/v1/capture/receive/", {
  method: "POST",
  headers: {
    "Idempotency-Key": crypto.randomUUID(),
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "token": "q3V8m1x0K9cJr2Zt7bWfY4nH6sL5aP0dE8gU1iO3kTw",
    "code": "482915",
    "company_name": "Upcountry Chemist",
    "ppb_licence": "PPB/R/7781",
    "attester_name": "Wanjiru Kamau",
    "phone": "+254712345678",
    "lines": [
      {
        "gtin": "06164004321562",
        "batch_number": "BTH-2026-041",
        "quantity": 18
      },
      {
        "gtin": "06164004321562",
        "batch_number": "BTH-2026-052",
        "quantity": 10,
        "damaged": 2,
        "refused": 4
      }
    ],
    "captured_at": "2026-10-22T10:30:00+03:00",
    "consent": true,
    "scans": [
      "]d201061640043215621727113010BTH-2026-041\u001d21SN0007731"
    ]
  }),
});
console.log(await response.json());

Response fields

  • event_id integer
  • parent_event_id integer
  • link_confidence string
  • batch_number string

    The batch or lot number.

  • quantity integer

    The number of units.

  • event_type string

    The kind of event.

  • events array of objects

Example response

JSON
{
  "event_id": 4512,
  "parent_event_id": 4488,
  "link_confidence": "INFERRED",
  "batch_number": "BTH-2026-041",
  "quantity": 18,
  "event_type": "RECEIVE",
  "events": [
    {
      "event_id": 4512,
      "parent_event_id": 4488,
      "link_confidence": "INFERRED",
      "batch_number": "BTH-2026-041",
      "quantity": 18,
      "event_type": "RECEIVE",
      "batch_scanned_and_matched": true
    },
    {
      "event_id": 4513,
      "parent_event_id": 4489,
      "link_confidence": "INFERRED",
      "batch_number": "BTH-2026-052",
      "quantity": 10,
      "event_type": "RECEIVE",
      "batch_scanned_and_matched": false
    },
    {
      "event_id": 4514,
      "parent_event_id": 4489,
      "link_confidence": "INFERRED",
      "batch_number": "BTH-2026-052",
      "quantity": 0,
      "event_type": "RECEIPT_ANSWER",
      "expected": 16,
      "received": 10,
      "short": 2,
      "damaged": 2,
      "refused": 4
    }
  ]
}

Status codes

200OK
400The request is invalid, for example a malformed GTIN or a missing field
409Conflict: a request with this Idempotency-Key is in progress, or the record already exists
429Rate limit exceeded

Partner API v1. The API reference is generated from the API's own code.