BMI KYC API
The BMI KYC API lets you verify user identities in real time using AI-powered face matching, document OCR, and liveness detection. Integrate with a single REST API call.
https://kyc.bm-e-industrie.com/api/v1 — All requests must use HTTPS.
Get running in 5 minutes
Follow these steps to submit your first KYC verification.
Get your credentials
Contact BMI to receive your email and password. You'll use these to get a JWT token for API calls.
Login to get a token
curl -X POST https://kyc.bm-e-industrie.com/api/v1/auth/login \ -d "username=you@company.com&password=yourpassword" # Response: { "access_token": "eyJhbGci...", "token_type": "bearer" }
Submit your first verification
curl -X POST https://kyc.bm-e-industrie.com/api/v1/verify/upload \ -H "Authorization: Bearer YOUR_TOKEN" \ -F "id_document=@id_card.jpg" \ -F "selfie_video=@selfie.jpg"
Read the decision
decision is approved — activate the user. No human review needed.
Authentication
All API requests require a JWT Bearer token obtained by logging in.
Login endpoint
| Field | Value |
|---|---|
| Method | POST |
| URL | /auth/login |
| Content-Type | application/x-www-form-urlencoded |
| Body | username=you@email.com&password=yourpassword |
/auth/login again.
Using the token
# Add this header to every request
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Submit a Verification
| Method | URL | Auth |
|---|---|---|
| POST | /verify/upload | Required |
Required Fields
| Field | Type | Description |
|---|---|---|
| id_document | image file | Photo of ID card, passport, or any government ID |
| selfie_video | image/video | Selfie photo or short video (3–5 sec) |
Optional Fields
| Field | Type | Description |
|---|---|---|
| vehicle_document | image file | Vehicle registration card / Carte grise |
| insurance_document | image file | Insurance certificate / Assurance |
| driver_license | image file | Driver's license / Permis de conduire |
Response Fields
| Field | Type | Description |
|---|---|---|
| session_id | string | Unique ID for this verification. Save in your DB. |
| status | string | completed, failed, processing |
| decision | string | approved, rejected, manual_review |
| decision_reason | string | Human-readable explanation of the decision |
| face_match_score | float 0–1 | Similarity between selfie and ID photo. ≥0.75 = good match |
| document_valid | boolean | Whether the document was successfully read and validated |
| document_confidence | float 0–1 | Confidence in document OCR extraction |
| document_type | string | id_card, passport, driver_license, etc. |
| extracted_data | object | Name, date of birth, document number, expiry date extracted by OCR |
| liveness_score | float 0–1 | Anti-spoofing score. ≥0.7 = real person |
| processing_time | float | Time in seconds to process the verification |
Understanding Decisions
| Decision | Meaning | What to do |
|---|---|---|
| approved | All checks passed | ✅ Activate user / unlock feature |
| rejected | Verification failed | ❌ Ask user to retry with better photos |
| manual_review | Borderline case | ⏳ Show "under review" — admin will decide |
Decision Reasons & User Messages
| Reason | Message to show user |
|---|---|
| All checks passed | ✅ Your identity has been verified |
| Face match score too low | Your selfie doesn't match your ID. Retake in good lighting looking straight at camera. |
| No face detected in ID document | We couldn't read your ID photo. Place it flat on a dark surface with no glare. |
| No face detected in selfie | Your face wasn't detected. Ensure your face is fully visible and well-lit. |
| Document could not be validated | Your document is unreadable. Retake with better lighting and no blur. |
| Partial match - manual review recommended | ⏳ Your verification is under review. You'll be notified shortly. |
Score Interpretation
≥ 0.85 — Excellent
0.75–0.84 — Good
0.60–0.74 — Review
< 0.60 — Reject
≥ 0.70 — Clearly readable
0.30–0.69 — Partially readable
< 0.30 — Unreadable
≥ 0.70 — Real person
0.50–0.69 — Uncertain
< 0.50 — Possible fake
Check Verification Status
Use the session_id returned from the upload endpoint to check the status of a verification later.
curl https://kyc.bm-e-industrie.com/api/v1/verify/status/{session_id} \ -H "Authorization: Bearer YOUR_TOKEN"
Implementing Retry Logic
When a verification is rejected, your app should determine if the user can retry or if it's a hard rejection.
function handleKycResult($result, $attempt = 1): array { $decision = $result['decision'] ?? 'error'; $reason = $result['decision_reason'] ?? ''; if ($decision === 'approved') { return ['action' => 'activate', 'message' => '✅ Verified!']; } // Retryable: image quality issues $retryable = ['face', 'document', 'detected', 'unreadable']; $canRetry = $attempt < 3 && array_filter( $retryable, fn($w) => stripos($reason, $w) !== false ); if ($canRetry) { return ['action' => 'retry', 'message' => getUserMessage($reason)]; } if ($decision === 'manual_review' || $attempt >= 3) { return ['action' => 'review', 'message' => '⏳ Under review']; } return ['action' => 'reject', 'message' => '❌ Verification failed']; }
Image Requirements
• Place flat on dark surface
• Good lighting, no flash glare
• All 4 corners visible
• Min 800×600 pixels
• JPG, PNG, HEIC accepted
• Face fully visible, looking straight
• Well-lit, no backlighting
• No sunglasses or mask
• Min 400×400 pixels
• JPG, PNG, MP4 accepted
HTTP Error Codes
| Code | Meaning | Action |
|---|---|---|
| 200 | Success | Read the decision field |
| 401 | Unauthorized | Token expired or invalid — re-login |
| 422 | Validation Error | Missing required fields (id_document or selfie_video) |
| 500 | Server Error | Retry after 30 seconds |
Complete Integration Examples
// KYC Client Class class BMIKycClient { private string $base = 'https://kyc.bm-e-industrie.com/api/v1'; private string $token; public function __construct(string $email, string $password) { $ch = curl_init($this->base . '/auth/login'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => "username=$email&password=$password", CURLOPT_RETURNTRANSFER => true, ]); $this->token = json_decode(curl_exec($ch), true)['access_token']; } public function verify(string $idPath, string $selfiePath, array $extra = []): array { $fields = [ 'id_document' => new CURLFile($idPath), 'selfie_video' => new CURLFile($selfiePath), ]; foreach (['vehicle_document','insurance_document','driver_license'] as $k) { if (!empty($extra[$k])) $fields[$k] = new CURLFile($extra[$k]); } $ch = curl_init($this->base . '/verify/upload'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => $fields, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 60, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$this->token}"], ]); return json_decode(curl_exec($ch), true); } } // Usage $kyc = new BMIKycClient('you@company.com', 'yourpassword'); $result = $kyc->verify('id.jpg', 'selfie.jpg'); match($result['decision']) { 'approved' => activateUser(), 'manual_review' => showPending(), default => showError($result['decision_reason']), };
import requests class BMIKycClient: BASE = 'https://kyc.bm-e-industrie.com/api/v1' def __init__(self, email, password): res = requests.post( f'{self.BASE}/auth/login', data={'username': email, 'password': password} ) self.token = res.json()['access_token'] def verify(self, id_path, selfie_path, **extra): files = { 'id_document': open(id_path, 'rb'), 'selfie_video': open(selfie_path, 'rb'), } for key, path in extra.items(): if path: files[key] = open(path, 'rb') return requests.post( f'{self.BASE}/verify/upload', headers={'Authorization': f'Bearer {self.token}'}, files=files, timeout=60 ).json() # Usage kyc = BMIKycClient('you@company.com', 'yourpassword') result = kyc.verify('id.jpg', 'selfie.jpg', vehicle_document='car.jpg', insurance_document='insurance.jpg' ) if result['decision'] == 'approved': activate_user() elif result['decision'] == 'manual_review': show_pending() else: show_error(result['decision_reason'])
const FormData = require('form-data'); const fs = require('fs'); const axios = require('axios'); const BASE = 'https://kyc.bm-e-industrie.com/api/v1'; async function getToken(email, password) { const { data } = await axios.post(`${BASE}/auth/login`, `username=${email}&password=${password}`, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } } ); return data.access_token; } async function verify(token, idPath, selfiePath) { const form = new FormData(); form.append('id_document', fs.createReadStream(idPath)); form.append('selfie_video', fs.createReadStream(selfiePath)); const { data } = await axios.post(`${BASE}/verify/upload`, form, { headers: { 'Authorization': `Bearer ${token}`, ...form.getHeaders() }, timeout: 60000 }); return data; } // Usage const token = await getToken('you@company.com', 'yourpassword'); const result = await verify(token, 'id.jpg', 'selfie.jpg'); console.log(result.decision); // 'approved' | 'rejected' | 'manual_review'
#!/bin/bash # Step 1: Get token TOKEN=$(curl -s -X POST \ https://kyc.bm-e-industrie.com/api/v1/auth/login \ -d "username=you@company.com&password=yourpassword" \ | python3 -c "import sys,json;print(json.load(sys.stdin)['access_token'])") # Step 2: Submit verification curl -s -X POST \ https://kyc.bm-e-industrie.com/api/v1/verify/upload \ -H "Authorization: Bearer $TOKEN" \ -F "id_document=@id_card.jpg" \ -F "selfie_video=@selfie.jpg" \ | python3 -m json.tool