Base URL: https://api.sodo-dd.vn · All endpoints accept and return JSON unless stated.
Two auth flows:
- API keys (B2B integrations) —
Authorization: Bearer sk_live_<random> - JWT (web portal) —
Authorization: Bearer <jwt>issued byPOST /auth/login
API keys are rate-limited via a token bucket: 60 rpm sustained, burst 20, configurable per
key. Repeated 429s should be retried with exponential backoff respecting Retry-After.
All errors:
{ "error": { "code": "rate_limited", "message": "API key rate limit exceeded" } }| code | http | meaning |
|---|---|---|
unauthorized |
401 | missing / invalid token |
forbidden |
403 | scope or role insufficient |
not_found |
404 | resource missing or not owned by org |
bad_request |
400 | validation failed |
rate_limited |
429 | back off |
upstream_unavailable |
502 | external partner system error |
Body: { "email": "...", "password": "..." }
Returns { "access_token": "...", "expires_in": 1800 }.
Owner / admin only. Body: { "name": "production-ingest", "scopes": ["due_diligence:write"] }
Returns the raw key once — never retrievable again.
multipart/form-data, field file=<image|pdf>. Returns the created Certificate. Mime
must be one of image/jpeg | image/png | image/tiff | application/pdf. Max 25 MB.
multipart/form-data; uploads + triggers in one shot.
| field | type | notes |
|---|---|---|
certificate |
file | required |
options |
string | optional JSON-encoded DueDiligenceOptions |
callback_url |
string | optional, webhook will also fire if registered |
Returns the Job with status queued.
DueDiligenceOptions:
{
"include_ocr": true,
"include_portal_verify": true,
"include_zoning": true,
"include_history": true,
"province_hint": "Hà Nội",
"parcel_hint": "142",
"sheet_hint": "27"
}JSON body. Use when the certificate has already been uploaded.
{ "certificate_id": "cert_01HX...", "options": { "include_zoning": false } }{
"id": "ddj_01HX...",
"status": "completed",
"progress_pct": 100,
"certificate_id": "cert_01HX...",
"created_at": "2026-05-14T03:11:00Z",
"completed_at": "2026-05-14T03:11:42Z",
"report": {
"id": "ddr_01HX...",
"risk_score": 18,
"risk_level": "low",
"red_flags": [],
"sources": [
{ "name": "land_portal", "status": "ok", "queried_at": "...",
"response_hash": "8f3..." }
],
"pdf_url": "https://...presigned..."
}
}POST /api/v1/webhooks registers an endpoint. The platform delivers events with:
Content-Type: application/jsonX-Sodo-Event: dd.completed | dd.failedX-Sodo-Signature: t=<unix>,v1=<hex_hmac_sha256(secret, "<t>." + body)>
Verify with the helper in app/core/security.py::verify_webhook or your own
constant-time HMAC. Allow a clock skew of ±5 minutes.
Delivery is retried with exponential backoff up to 4 attempts; permanently failed deliveries are surfaced in the dashboard.