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:
"color:#8b949e"># macOS
brew install pcsc-lite
pcsctest
"color:#8b949e"># Ubuntu/Debian
sudo apt-get install pcscd pcsc-tools
pcsc_scan2. Your First Scan
With the API running, place a card on the reader and trigger a scan:
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"
}'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);"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.
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.
API Reference
/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"
}/card/statusReturns the list of connected readers and whether a card is currently present.
{
"readers": ["Identiv SCR3310 [CCID Interface] 00 00"],
"card_present": true,
"reader_name": "Identiv SCR3310 [CCID Interface] 00 00"
}/card/status/streamStreaming endpoint that pushes real-time card presence updates. The dashboard badge (“Card detected” / “No card”) is driven by this endpoint.
| Param | Type | Required | Description |
|---|---|---|---|
| token | string | Optional | Access token (query parameter). 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 }}/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.
| 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"
}
]/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.
| 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."
}/citizensReturns a paginated list of citizen records.
| Param | Type | Required | Description |
|---|---|---|---|
| page | integer | Optional | Page number (default: 1). |
| page_size | integer | Optional | Records per page (default: 20, max: 100). |
/citizens/searchFull-text search across surname, given names, personal ID number, and card number.
| Param | Type | Required | Description |
|---|---|---|---|
| q | string | Required | Search query. Case-insensitive partial match. |
curl "http://localhost:8000/citizens/search?q=mensah"/statsReturns aggregate statistics: total citizens, total scans, scans today, active readers.
{
"total_citizens": 18204,
"total_scans": 93410,
"scans_today": 147,
"active_readers": 2
}/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
}
]/citizens/:id/scansReturns the scan history for a specific citizen, ordered by most recent first.
| Param | Type | Required | Description |
|---|---|---|---|
| id | string (UUID) | Required | The citizen's UUID (path parameter). |
| limit | integer | Optional | Max entries to return (default: 50). |
/citizens/:idPermanently deletes a citizen record and all associated scan logs.
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.
| Reader | Manufacturer | Interface | Notes |
|---|---|---|---|
| SCR3310 | Identiv | USB (CCID) | Default recommended reader |
| SCR3310v2.0 | Identiv | USB (CCID) | Faster read speeds |
| ACR39U-N1 | ACS | USB (CCID) | Compact, widely deployed |
| ACR38U-R2 | ACS | USB (CCID) | Budget option |
| Omnikey 3121 | HID Global | USB (CCID) | FIPS 201 compliant |
| PC Twin Reader | Thales/Gemalto | USB (CCID) | Banking sector standard |
| ACR122U | ACS | USB / NFC | ISO 14443 contactless |
| ST-1144UB | Cherry | USB (CCID) | TAA compliant |
| uTrust 3700F | Identiv | USB / NFC | Dual-interface |
| miniLector | Bit4id | USB (CCID) | Ultra-compact field reader |
Errors & Status Codes
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 |
SDKs & Integrations
The DREWQ API is a standard REST service. Any HTTP client works. Below are recommended patterns for the most common environments.
// 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();
}"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.