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.
Authorization: Bearer drewq_xxxxxxxxxxxxxxxxxxxxxxxxxxxxKeep 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.
| Header | Value | Notes |
|---|---|---|
| Authorization | Bearer <token> | Required on all endpoints |
| Content-Type | application/json | Required on POST requests |
Endpoints
/card/scanReads 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
| Field | Type | Required | Description |
|---|---|---|---|
| doc_number | string | Required | Document number printed on the card (MRZ line 1, positions 1-9). |
| date_of_birth | string (YYYY-MM-DD) | Required | Holder's date of birth. Used for card authentication. |
| expiry_date | string (YYYY-MM-DD) | Required | Card expiry date. Used for card authentication. |
| station_id | string | Optional | Identifier of the scan station. Recorded in the audit log. |
{
"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
}{
"success": false,
"error": "No card present on reader"
}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"
}'/card/statusReturns the list of connected readers and whether a card is currently present on the active reader.
{
"readers": ["Identiv SCR3310 [CCID Interface] 00 00"],
"card_present": true,
"reader_name": "Identiv SCR3310 [CCID Interface] 00 00"
}curl http://localhost:8000/card/status \
"color:#FCD116"> -H "Authorization: Bearer drewq_xxxx"/card/status/streamStreaming endpoint that pushes real-time card presence updates to the browser. Authenticates via ?token= query parameter, cookie, or Authorization header.
Query Parameters
| Param | Type | Required | Description |
|---|---|---|---|
| token | string | Optional | Access token. Required when connecting directly from the browser. |
// Card presence update
data: {"card_present": true}
// Scan result (emitted after a successful card scan)
data: {"type": "scan_result", "citizen": { ...CitizenResponse }}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);
};/card/lookupFinds 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
| Param | Type | Required | Description |
|---|---|---|---|
| last4 | string | Required | Last 4 digits of the personal ID number (e.g. '0470'). |
| check | string | Required | Single check digit following the last 4 (e.g. '0'). |
[
{
"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 "http://localhost:8000/card/lookup?last4=0470&check=0" \
"color:#FCD116"> -H "Authorization: Bearer drewq_xxxx"/card/scan-by-citizen/:idScans 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
| Param | Type | Required | Description |
|---|---|---|---|
| id | string (UUID) | Required | The citizen's UUID (path parameter). |
{
"success": true,
"citizen": { ...CitizenResponse },
"scan_log_id": "uuid",
"is_new": false
}{
"success": false,
"error": "Card read timed out. Make sure the card is placed correctly on the reader."
}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"/citizensReturns a paginated list of all citizen records stored in the database.
Query Parameters
| Param | Type | Required | Description |
|---|---|---|---|
| page | integer | Optional | Page number (default: 1). |
| page_size | integer | Optional | Records per page (default: 20, max: 100). |
curl "http://localhost:8000/citizens?page=1&page_size=20" \
"color:#FCD116"> -H "Authorization: Bearer drewq_xxxx"/citizens/:idRetrieves a single citizen record by UUID. Also supports full-text search via /citizens/search?q= across surname, given names, personal ID, and card number.
| Param | Type | Required | Description |
|---|---|---|---|
| id | string (UUID) | Required | The citizen's UUID (path parameter). |
| q | string | Optional | Search query for /citizens/search. Case-insensitive partial match. |
"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"/statsReturns aggregate statistics: total citizens, total scans, scans today, and active readers.
{
"total_citizens": 18204,
"total_scans": 93410,
"scans_today": 147,
"active_readers": 2
}curl http://localhost:8000/stats \
"color:#FCD116"> -H "Authorization: Bearer drewq_xxxx"/scansReturns the most recent scan log entries with enriched citizen fields.
| Param | Type | Required | Description |
|---|---|---|---|
| limit | integer | Optional | Max entries to return (default: 20, max: 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 "http://localhost:8000/scans?limit=50" \
"color:#FCD116"> -H "Authorization: Bearer drewq_xxxx"/citizens/:id/scansReturns the scan history for a specific citizen, ordered by most recent first.
Parameters
| Param | Type | Required | Description |
|---|---|---|---|
| id | string (UUID) | Required | The citizen's UUID (path parameter). |
| limit | integer | Optional | Max entries to return (default: 50). |
curl "http://localhost:8000/citizens/d4f3a1b2-.../scans?limit=20" \
"color:#FCD116"> -H "Authorization: Bearer drewq_xxxx"/citizens/:idPermanently deletes a citizen record and all associated scan logs from the database.
| Param | Type | Required | Description |
|---|---|---|---|
| id | string (UUID) | Required | The citizen's UUID (path parameter). |
// Empty body — HTTP 204 No Contentcurl"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.
| Status | Meaning | Common Cause |
|---|---|---|
| 200 | OK | Request succeeded |
| 204 | No Content | Deletion successful |
| 400 | Bad Request | Missing or invalid parameters |
| 401 | Unauthorized | Invalid or missing API key |
| 404 | Not Found | Citizen record not found |
| 422 | Unprocessable Entity | Validation error on request body |
| 500 | Internal Server Error | Reader error, DB connection failure |
{
"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
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, 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 →