Back
API Reference
Full Docs

Overview

The DREWQ API is a REST service that reads data from ECOWAS biometric identity cards (DREWQ) via USB smart card readers. It exposes endpoints for card scanning, citizen record management, statistics, and scan audit logs.

The API communicates with reader hardware through a local desktop agent. All responses are application/json.

Base URL

http://localhost:8000

Auth

Bearer Token / API Key

Format

application/json

Authentication

All API requests require a Bearer token in the Authorization header. Tokens are scoped per organisation and can be rotated from the dashboard.

bash: request header
Authorization: Bearer drewq_xxxxxxxxxxxxxxxxxxxxxxxxxxxx

Keep your API key secure

Never expose API keys in client-side code or public repositories. Use environment variables or a secrets manager. Keys can be revoked instantly from the dashboard.

HeaderValueNotes
AuthorizationBearer <token>Required on all endpoints
Content-Typeapplication/jsonRequired on POST requests

Endpoints

POST/card/scan

Reads the card currently on the reader and creates or updates the citizen record in the database. Returns full citizen data and a scan log entry.

Request Body

FieldTypeRequiredDescription
doc_numberstringRequiredDocument number printed on the card (MRZ line 1, positions 1-9).
date_of_birthstring (YYYY-MM-DD)RequiredHolder's date of birth. Used for card authentication.
expiry_datestring (YYYY-MM-DD)RequiredCard expiry date. Used for card authentication.
station_idstringOptionalIdentifier of the scan station. Recorded in the audit log.
Response 200: success
{
  "success": true,
  "citizen": {
    "id": "uuid",
    "personal_id_number": "GHA-XXXXXXXXXX-X",
    "card_number": "string",
    "surname": "MENSAH",
    "given_names": "KOFI AGYEMAN",
    "nationality": "GHA",
    "sex": "M",
    "date_of_birth": "1990-03-15",
    "expiry_date": "2028-11-30",
    "scan_count": 4,
    "last_scanned_at": "2025-01-15T08: 30: 00Z",
    "created_at": "2024-06-01T12: 00: 00Z",
    "updated_at": "2025-01-15T08: 30: 00Z"
  },
  "scan_log_id": "uuid",
  "is_new": false
}
Response 200: card error
{
  "success": false,
  "error": "No card present on reader"
}
curl example
curl"color:#FCD116"> -X "color:#79c0ff;font-weight:bold">POST http://localhost:8000/card/scan \
 "color:#FCD116"> -H "Authorization: Bearer drewq_xxxx" \
 "color:#FCD116"> -H "Content-Type: application/json" \
 "color:#FCD116"> -d '{
    "station_id": "TERMINAL-01",
    "doc_number": "A12345678",
    "date_of_birth": "1990-03-15",
    "expiry_date": "2028-11-30"
  }'
GET/card/status

Returns the list of connected readers and whether a card is currently present on the active reader.

Response 200
{
  "readers": ["Identiv SCR3310 [CCID Interface] 00 00"],
  "card_present": true,
  "reader_name": "Identiv SCR3310 [CCID Interface] 00 00"
}
curl example
curl http://localhost:8000/card/status \
 "color:#FCD116"> -H "Authorization: Bearer drewq_xxxx"
GET/card/status/stream

Streaming endpoint that pushes real-time card presence updates to the browser. Authenticates via ?token= query parameter, cookie, or Authorization header.

Query Parameters

ParamTypeRequiredDescription
tokenstringOptionalAccess token. Required when connecting directly from the browser.
Event format
// Card presence update
data: {"card_present": true}

// Scan result (emitted after a successful card scan)
data: {"type": "scan_result", "citizen": { ...CitizenResponse }}
JavaScript example
const stream = new EventSource(
  "https://api.example.com/card/status/stream?token=your_token"
);
stream.onmessage = (e) => {
  const { card_present } = JSON.parse(e.data);
  console.log("Card present:", card_present);
};
GET/card/lookup

Finds citizens by the last 4 digits and check digit of their Ghana Personal ID number (the suffix after GHA-·····). Used on the verification page when a card is present but needs to be matched to a stored record.

Query Parameters

ParamTypeRequiredDescription
last4stringRequiredLast 4 digits of the personal ID number (e.g. '0470').
checkstringRequiredSingle check digit following the last 4 (e.g. '0').
Response 200
[
  {
    "id": "uuid",
    "personal_id_number": "GHA-000000470-0",
    "card_number": "AW0081448",
    "surname": "MENSAH",
    "given_names": "KOFI",
    "expiry_date": "2030-01-13",
    "scan_count": 24,
    "last_scanned_at": "2025-01-15T08: 30: 00Z"
  }
]
curl example
curl "http://localhost:8000/card/lookup?last4=0470&check=0" \
 "color:#FCD116"> -H "Authorization: Bearer drewq_xxxx"
POST/card/scan-by-citizen/:id

Scans the card on the reader using BAC credentials stored for a known citizen — no manual document number, date of birth, or expiry date required. The photo read (DG2) is skipped since it is already stored, making this faster than /card/scan. The result is broadcast to all open SSE connections and a scan log entry is recorded.

Parameters

ParamTypeRequiredDescription
idstring (UUID)RequiredThe citizen's UUID (path parameter).
Response 200: success
{
  "success": true,
  "citizen": { ...CitizenResponse },
  "scan_log_id": "uuid",
  "is_new": false
}
Response 200: card mismatch
{
  "success": false,
  "error": "Card read timed out. Make sure the card is placed correctly on the reader."
}
curl example
curl"color:#FCD116"> -X "color:#79c0ff;font-weight:bold">POST http://localhost:8000/card/scan-by-citizen/d4f3a1b2-... \
 "color:#FCD116"> -H "Authorization: Bearer drewq_xxxx"
GET/citizens

Returns a paginated list of all citizen records stored in the database.

Query Parameters

ParamTypeRequiredDescription
pageintegerOptionalPage number (default: 1).
page_sizeintegerOptionalRecords per page (default: 20, max: 100).
curl example
curl "http://localhost:8000/citizens?page=1&page_size=20" \
 "color:#FCD116"> -H "Authorization: Bearer drewq_xxxx"
GET/citizens/:id

Retrieves a single citizen record by UUID. Also supports full-text search via /citizens/search?q= across surname, given names, personal ID, and card number.

ParamTypeRequiredDescription
idstring (UUID)RequiredThe citizen's UUID (path parameter).
qstringOptionalSearch query for /citizens/search. Case-insensitive partial match.
curl example
"color:#8b949e"># By UUID
curl http://localhost:8000/citizens/d4f3a1b2-... \
 "color:#FCD116"> -H "Authorization: Bearer drewq_xxxx"

"color:#8b949e"># Full-text search
curl "http://localhost:8000/citizens/search?q=mensah" \
 "color:#FCD116"> -H "Authorization: Bearer drewq_xxxx"
GET/stats

Returns aggregate statistics: total citizens, total scans, scans today, and active readers.

Response 200
{
  "total_citizens": 18204,
  "total_scans": 93410,
  "scans_today": 147,
  "active_readers": 2
}
curl example
curl http://localhost:8000/stats \
 "color:#FCD116"> -H "Authorization: Bearer drewq_xxxx"
GET/scans

Returns the most recent scan log entries with enriched citizen fields.

ParamTypeRequiredDescription
limitintegerOptionalMax entries to return (default: 20, max: 200).
Response 200
[
  {
    "id": "uuid",
    "citizen_id": "uuid",
    "scanned_at": "2025-01-15T08: 30: 00Z",
    "station_id": "TERMINAL-01",
    "status": "success",
    "citizen_given_names": "KOFI",
    "citizen_surname": "MENSAH",
    "citizen_personal_id": "GHA-000000470-0",
    "citizen_expiry_date": "2030-01-13",
    "citizen_scan_count": 24
  }
]
curl example
curl "http://localhost:8000/scans?limit=50" \
 "color:#FCD116"> -H "Authorization: Bearer drewq_xxxx"
GET/citizens/:id/scans

Returns the scan history for a specific citizen, ordered by most recent first.

Parameters

ParamTypeRequiredDescription
idstring (UUID)RequiredThe citizen's UUID (path parameter).
limitintegerOptionalMax entries to return (default: 50).
curl example
curl "http://localhost:8000/citizens/d4f3a1b2-.../scans?limit=20" \
 "color:#FCD116"> -H "Authorization: Bearer drewq_xxxx"
DELETE/citizens/:id

Permanently deletes a citizen record and all associated scan logs from the database.

ParamTypeRequiredDescription
idstring (UUID)RequiredThe citizen's UUID (path parameter).
Response 204: no content
// Empty body — HTTP 204 No Content
curl example
curl"color:#FCD116"> -X "color:#79c0ff;font-weight:bold">DELETE http://localhost:8000/citizens/d4f3a1b2-... \
 "color:#FCD116"> -H "Authorization: Bearer drewq_xxxx"

Errors

All errors return a JSON body with success: false and an error string describing the problem.

StatusMeaningCommon Cause
200OKRequest succeeded
204No ContentDeletion successful
400Bad RequestMissing or invalid parameters
401UnauthorizedInvalid or missing API key
404Not FoundCitizen record not found
422Unprocessable EntityValidation error on request body
500Internal Server ErrorReader error, DB connection failure
Error response format
{
  "success": false,
  "error": "No card present on reader",
  "detail": "Reader service unavailable"
}

SDKs

The DREWQ API is a standard REST service. Any HTTP client works. Below are recommended patterns for the most common environments.

JavaScript / TypeScript

typescript: minimal client
const BASE = process.env.DREWQ_API_URL ?? 'http://localhost:8000';

async function scanCard(params: {
  doc_number: string;
  date_of_birth: string;
  expiry_date: string;
  station_id?: string;
}) {
  const res = await fetch(`${BASE}/card/scan`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${process.env.DREWQ_API_KEY}`,
    },
    body: JSON.stringify(params),
  });
  if (!res.ok) throw new Error(`API error ${res.status}`);
  return res.json();
}

Python

python: minimal client
"color:#ff7b72">import os, requests

"color:#ff7b72">class DREWQClient:
    "color:#ff7b72">def __init__("color:#ff7b72">self):
        "color:#ff7b72">self.base = os.environ["DREWQ_API_URL"]
        "color:#ff7b72">self.headers = {
            "Authorization": f"Bearer {os.environ['DREWQ_API_KEY']}"
        }

    "color:#ff7b72">def scan("color:#ff7b72">self, doc_number, dob, expiry, station_id="color:#ff7b72">None):
        "color:#ff7b72">return requests.post(
            f"{">self.base}/card/scan",
            headers="color:#ff7b72">self.headers,
            json={
                "doc_number": doc_number,
                "date_of_birth": dob,
                "expiry_date": expiry,
                "station_id": station_id,
            },
        ).json()

client = DREWQClient()
result = client.scan("A12345678", "1990-03-15", "2028-11-30")

Webhook support

Configure a webhook URL in your dashboard to receive real-time POST notifications for every card scan event. Payload includes the full scan log entry and citizen summary. Useful for triggering downstream workflows without polling /scans.

Supported readers

The API works with all 10 supported USB smart card readers. Browse setup guides →