Back
Documentation
Dashboard

Overview

The DREWQ API is a REST service that reads data from ECOWAS biometric identity cards (DREWQ) via USB smart card readers and provides endpoints for scanning, querying, and auditing.

The platform communicates with the reader hardware through a local desktop agent. All citizen data is encrypted at rest and accessible through a modern REST API and web dashboard.

Base URL

http://localhost:8000

Auth

Bearer Token / API Key

Format

application/json

Quick Start

Get up and running in under 5 minutes. You'll need a supported USB smart card reader and a ECOWAS identity card.

1. Connect a Reader

Plug in your USB smart card reader. The platform auto-detects connected readers. Check the reader is visible:

bash: check reader detection
"color:#8b949e"># macOS
brew install pcsc-lite
pcsctest

"color:#8b949e"># Ubuntu/Debian
sudo apt-get install pcscd pcsc-tools
pcsc_scan

2. Your First Scan

With the API running, place a card on the reader and trigger a scan:

bash: scan a card
curl"color:#FCD116"> -X "color:#79c0ff;font-weight:bold">POST http://localhost:8000/card/scan \
 "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"
  }'
javascript: scan with fetch
const response = await fetch('http://localhost:8000/card/scan', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer drewq_xxxx',
  },
  body: JSON.stringify({
    station_id: 'TERMINAL-01',
    doc_number: 'A12345678',
    date_of_birth: '1990-03-15',
    expiry_date: '2028-11-30',
  }),
});

const data = await response.json();
console.log(data.citizen);
python: scan with requests
"color:#ff7b72">import requests

resp = requests.post(
    "http://localhost:8000/card/scan",
    headers={"Authorization": "Bearer drewq_xxxx"},
    json={
        "station_id": "TERMINAL-01",
        "doc_number": "A12345678",
        "date_of_birth": "1990-03-15",
        "expiry_date": "2028-11-30",
    }
)
citizen = resp.json()["citizen"]
print(citizen["surname"], citizen["given_names"])

Authentication

All API requests require authentication via 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.

API Reference

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"
}
GET/card/status

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

Response 200
{
  "readers": ["Identiv SCR3310 [CCID Interface] 00 00"],
  "card_present": true,
  "reader_name": "Identiv SCR3310 [CCID Interface] 00 00"
}
GET/card/status/stream

Streaming endpoint that pushes real-time card presence updates. The dashboard badge (“Card detected” / “No card”) is driven by this endpoint.

ParamTypeRequiredDescription
tokenstringOptionalAccess token (query parameter). 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 }}
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.

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"
  }
]
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.

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."
}
GET/citizens

Returns a paginated list of citizen records.

ParamTypeRequiredDescription
pageintegerOptionalPage number (default: 1).
page_sizeintegerOptionalRecords per page (default: 20, max: 100).
GET/citizens/search

Full-text search across surname, given names, personal ID number, and card number.

ParamTypeRequiredDescription
qstringRequiredSearch query. Case-insensitive partial match.
bash
curl "http://localhost:8000/citizens/search?q=mensah"
GET/stats

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

Response 200
{
  "total_citizens": 18204,
  "total_scans": 93410,
  "scans_today": 147,
  "active_readers": 2
}
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
  }
]
GET/citizens/:id/scans

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

ParamTypeRequiredDescription
idstring (UUID)RequiredThe citizen's UUID (path parameter).
limitintegerOptionalMax entries to return (default: 50).
DELETE/citizens/:id

Permanently deletes a citizen record and all associated scan logs.

curl example
curl"color:#FCD116"> -X "color:#79c0ff;font-weight:bold">DELETE http://localhost:8000/citizens/d4f3a1b2-... \
 "color:#FCD116"> -H "Authorization: Bearer drewq_xxxx"

Supported Readers

All readers communicate via the CCID protocol and are detected automatically by the system. No custom drivers are required.

ReaderManufacturerInterfaceNotes
SCR3310IdentivUSB (CCID)Default recommended reader
SCR3310v2.0IdentivUSB (CCID)Faster read speeds
ACR39U-N1ACSUSB (CCID)Compact, widely deployed
ACR38U-R2ACSUSB (CCID)Budget option
Omnikey 3121HID GlobalUSB (CCID)FIPS 201 compliant
PC Twin ReaderThales/GemaltoUSB (CCID)Banking sector standard
ACR122UACSUSB / NFCISO 14443 contactless
ST-1144UBCherryUSB (CCID)TAA compliant
uTrust 3700FIdentivUSB / NFCDual-interface
miniLectorBit4idUSB (CCID)Ultra-compact field reader

Errors & Status Codes

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

SDKs & Integrations

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

javascript / typescript
// Minimal TypeScript 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
"color:#ff7b72">import os
"color:#ff7b72">import 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.