Age Verification

POST/v1/age/verifications

Confirm that a person has reached a minimum age, using the date of birth held by the RDCPASS national digital identity instead of a self-declared birthday or a checkbox. Send an RDCPASS ID or an identity document number and the age threshold you must enforce; RDCPASS answers with over_threshold and the person’s basic KYC. Every call requires the authentication stack described in Authentication.

At a glance

  • Endpoints: POST /v1/age/verifications (single) and POST /v1/age/verifications/batches (batch).
  • Input: an identifier — rdcpass_id or a document type (passport, ceni, driving_licence, national_id) — and a threshold in whole years (default 18).
  • Output: result (verified or not_found), over_threshold, age, account and kyc.basic.
  • Basic KYC only — additional KYC scopes never apply to this service.
  • Declared purpose is always age_gating.
  • Single, batch, synchronous and asynchronous modes; async results arrive on the age_verification.completed webhook.

This service returns basic KYC only

Age Verification never returns documents, addresses, phone numbers, biometrics or any other additional scope — even if your organization subscribes to them and your application has selected them. The request takes no scopes or document_delivery field, and scopes_applied is always ["kyc.basic"] on a verified result. If you need more than basic identity data, use KYC Validation instead. See KYC scopes & subscriptions for how basic KYC is defined.

Use cases

Age Verification is designed for any business that must keep minors out of a product or channel, and must be able to prove it did so. Set threshold to the minimum age that applies to your activity under Congolese law, your regulator’s rules or your own policy.

IndustryTypical thresholdWhere it fits
Gaming & betting18Account opening on sports-betting and online-casino platforms, before the first deposit, and again before a withdrawal to an unverified wallet.
Alcohol & tobacco e-commerce18 or 21At checkout, before an order containing restricted products is accepted — and at delivery, by checking the customer’s CENI card number against the order.
Social media & digital platforms13, 16 or 18Sign-up age assurance, access to age-restricted content or features, and eligibility for creator monetization and payouts.
TelecommunicationsMinimum age for SIM registrationSIM card registration at a shop or agent, and bulk re-checks of an existing subscriber base during a regulatory registration campaign.

How the check works

RDCPASS resolves the identifier to a certified RDCPASS account, computes the person’s age in whole years from their registered date of birth on the date of the request, and compares it with threshold. over_threshold is true when age is greater than or equal to threshold. A person who turns 18 today is over_threshold for a threshold of 18.

Only verified + over_threshold: true is a pass

Grant access only when result is verified and over_threshold is true. A not_found result carries over_threshold: null and must be treated as a failed check — ask the person to register with RDCPASS or to use another identifier.

Request body

Plaintext JSON shape, before encryption.

FieldTypeRequiredDescription
identifierobjectYesThe person to check, { "type", "value" }. type is rdcpass_id (e.g. COD-2103-0214-4937) or a document type: passport (e.g. OB1234567), ceni (voter card, e.g. 1234567890123), driving_licence or national_id.
thresholdintegerNoMinimum age in whole years, from 1 to 120. Default 18.
purposestring enumYesAlways age_gating, which must be among the purposes approved for your application.
referencestringIn batchesYour own identifier for this check, echoed back in the response and webhooks. Optional for single calls, required for every batch item.
Request body — by RDCPASS ID (plaintext, before encryption)
{
  "identifier": { "type": "rdcpass_id", "value": "COD-2103-0214-4937" },
  "threshold": 18,
  "purpose": "age_gating",
  "reference": "player-KIN-55012"
}
Request body — by CENI card number, threshold 21 (plaintext, before encryption)
{
  "identifier": { "type": "ceni", "value": "1234567890123" },
  "threshold": 21,
  "purpose": "age_gating",
  "reference": "order-LUB-2026-00931"
}

Response body

A successful call returns 200 OK. Both verified and not_found are successful calls, not errors.

FieldTypeDescription
idstringUnique identifier of the check, prefixed age_.
objectstringAlways age_verification.
livemodebooleantrue in production, false in the sandbox.
referencestring | nullThe reference you sent, or null.
statusstring enumAlways completed for synchronous calls. Async and batch items can be failed.
resultstring enumverified or not_found. See the table below.
over_thresholdboolean | nulltrue if age ≥ threshold, false otherwise. null when result is not_found.
thresholdintegerThe threshold applied to this check.
ageinteger | nullAge in whole years on the date of the request. null when result is not_found.
identifierobjectThe identifier you sent.
accountobjectVerified only. RDCPASS account: rdcpass_id, status, certified, created_at, level_of_assurance.
kyc.basicobjectVerified only. Basic KYC: full_name, first_name, last_name, date_of_birth, age, gender, nationality. No other kyc block is ever present.
scopes_appliedstring[]["kyc.basic"] when verified, empty when not_found.
scopes_withheldstring[]Always empty for this service.
purposestringThe purpose you declared (age_gating).
created_atstringWhen the check was performed (ISO 8601, UTC).
resultMeaningData returned
verifiedA certified RDCPASS account exists for the identifier; its date of birth was used to compute age.over_threshold, age, account, kyc.basic.
not_foundNo certified RDCPASS account matches the identifier.None — over_threshold and age are null.

Keep only what you need

Basic KYC is returned so you can link the check to the right customer record. If your obligation is only to prove an age check, store the check id, result, over_threshold and threshold, and discard the rest.
200 OK — verified, over threshold (after decryption)
{
  "id": "age_5f1b8c3d27",
  "object": "age_verification",
  "livemode": true,
  "reference": "player-KIN-55012",
  "status": "completed",
  "result": "verified",
  "over_threshold": true,
  "threshold": 18,
  "age": 38,
  "identifier": { "type": "rdcpass_id", "value": "COD-2103-0214-4937" },
  "account": {
    "rdcpass_id": "COD-2103-0214-4937",
    "status": "active",
    "certified": true,
    "created_at": "2025-03-14T09:22:41Z",
    "level_of_assurance": "LOA3"
  },
  "kyc": {
    "basic": {
      "full_name": "Kabeya Mwamba Tshisekedi",
      "first_name": "Kabeya",
      "last_name": "Tshisekedi",
      "date_of_birth": "1988-04-12",
      "age": 38,
      "gender": "male",
      "nationality": "COD"
    }
  },
  "scopes_applied": ["kyc.basic"],
  "scopes_withheld": [],
  "purpose": "age_gating",
  "created_at": "2026-09-26T10:15:02Z"
}
200 OK — verified, under threshold (after decryption)
{
  "id": "age_2e9d4a7c80",
  "object": "age_verification",
  "livemode": true,
  "reference": "player-GOM-10877",
  "status": "completed",
  "result": "verified",
  "over_threshold": false,
  "threshold": 18,
  "age": 16,
  "identifier": { "type": "rdcpass_id", "value": "COD-0910-7734-2051" },
  "account": {
    "rdcpass_id": "COD-0910-7734-2051",
    "status": "active",
    "certified": true,
    "created_at": "2026-01-08T14:03:19Z",
    "level_of_assurance": "LOA3"
  },
  "kyc": {
    "basic": {
      "full_name": "Mbuyi Ilunga Nsimba",
      "first_name": "Mbuyi",
      "last_name": "Nsimba",
      "date_of_birth": "2010-02-19",
      "age": 16,
      "gender": "female",
      "nationality": "COD"
    }
  },
  "scopes_applied": ["kyc.basic"],
  "scopes_withheld": [],
  "purpose": "age_gating",
  "created_at": "2026-09-26T10:17:45Z"
}
200 OK — not_found (after decryption)
{
  "id": "age_9c4e1f6b32",
  "object": "age_verification",
  "livemode": true,
  "reference": "player-KIN-55013",
  "status": "completed",
  "result": "not_found",
  "over_threshold": null,
  "threshold": 18,
  "age": null,
  "identifier": { "type": "rdcpass_id", "value": "COD-4417-0092-6618" },
  "scopes_applied": [],
  "scopes_withheld": [],
  "purpose": "age_gating",
  "created_at": "2026-09-26T10:18:10Z"
}

Example request

The same check in eight languages. Header values are illustrative — see Authentication to compute a real signature, encrypt the body and set X-RDCPASS-IV.

verify-age.sh
# Plaintext body shown for readability - encrypt and sign it as described in /docs/authentication before sending.
curl https://api.rdcpass.cd/v1/age/verifications \
  -X POST \
  -H "X-RDCPASS-Key-Id: key_live_8f2a1c0e9b" \
  -H "X-RDCPASS-Timestamp: 1735689600" \
  -H "X-RDCPASS-Nonce: 7a3c9e1b-5d2f-4b86-a0e4-1c8f6d2b9a73" \
  -H "X-RDCPASS-IV: Zt4Kp1Qw8Rm3Xv6N" \
  -H "X-RDCPASS-Signature: 1d6f2a...b48e" \
  -H "Content-Type: application/json" \
  -d '{
    "identifier": { "type": "rdcpass_id", "value": "COD-2103-0214-4937" },
    "threshold": 18,
    "purpose": "age_gating",
    "reference": "player-KIN-55012"
  }'
200 OK
{
  "id": "age_5f1b8c3d27",
  "object": "age_verification",
  "livemode": true,
  "reference": "player-KIN-55012",
  "status": "completed",
  "result": "verified",
  "over_threshold": true,
  "threshold": 18,
  "age": 38,
  "identifier": { "type": "rdcpass_id", "value": "COD-2103-0214-4937" },
  "account": {
    "rdcpass_id": "COD-2103-0214-4937",
    "status": "active",
    "certified": true,
    "created_at": "2025-03-14T09:22:41Z",
    "level_of_assurance": "LOA3"
  },
  "kyc": {
    "basic": {
      "full_name": "Kabeya Mwamba Tshisekedi",
      "first_name": "Kabeya",
      "last_name": "Tshisekedi",
      "date_of_birth": "1988-04-12",
      "age": 38,
      "gender": "male",
      "nationality": "COD"
    }
  },
  "scopes_applied": ["kyc.basic"],
  "scopes_withheld": [],
  "purpose": "age_gating",
  "created_at": "2026-09-26T10:15:02Z"
}

Request modes

Age Verification supports all four request modes. Add Prefer: respond-async to run asynchronously and Idempotency-Key to retry safely. Details in Single, batch & async.

Synchronous (default)Asynchronous (Prefer: respond-async)
Single — POST /v1/age/verifications200 with the result.202 with a job; result via the age_verification.completed webhook or GET /v1/jobs/{job_id}.
Batch — POST /v1/age/verifications/batches200 with all results, up to 50 items.202 with a batch, up to 10,000 items; results via webhook or GET /v1/batches/{batch_id}/results.

Batch example

Typical for re-checking an existing player or subscriber base. Batch-level purpose and threshold apply to every item unless an item overrides them. Each item returns its own result; an invalid item fails on its own without failing the batch.

Synchronous batch (up to 50 items)
curl https://api.rdcpass.cd/v1/age/verifications/batches \
  -X POST \
  -H "X-RDCPASS-Key-Id: key_live_8f2a1c0e9b" \
  -H "X-RDCPASS-Timestamp: 1735689600" \
  -H "X-RDCPASS-Nonce: 7a3c9e1b-5d2f-4b86-a0e4-1c8f6d2b9a73" \
  -H "X-RDCPASS-IV: Zt4Kp1Qw8Rm3Xv6N" \
  -H "X-RDCPASS-Signature: 1d6f2a...b48e" \
  -H "Idempotency-Key: 3f8a1d6c-92e4-4b07-8c5a-e1d9b2f7a604" \
  -H "Content-Type: application/json" \
  --data-binary @batch.json   # Plaintext body shown for readability - encrypt and sign it as described in /docs/authentication before sending.
batch.json (plaintext, before encryption)
{
  "purpose": "age_gating",
  "threshold": 18,
  "items": [
    { "reference": "player-KIN-55012", "identifier": { "type": "rdcpass_id", "value": "COD-2103-0214-4937" } },
    { "reference": "player-GOM-10877", "identifier": { "type": "rdcpass_id", "value": "COD-0910-7734-2051" } },
    { "reference": "player-LUB-20411", "identifier": { "type": "passport", "value": "OB1234567" } },
    { "reference": "player-KIN-55014", "identifier": { "type": "rdcpass_id", "value": "COD-21O3-0214" } }
  ]
}
200 OK (after decryption, abridged)
{
  "object": "list",
  "data": [
    {
      "reference": "player-KIN-55012",
      "status": "succeeded",
      "result": {
        "id": "age_5f1b8c3d27",
        "object": "age_verification",
        "result": "verified",
        "over_threshold": true,
        "threshold": 18,
        "age": 38,
        "account": { "rdcpass_id": "COD-2103-0214-4937", "status": "active", "certified": true, "...": "..." },
        "kyc": { "basic": { "full_name": "Kabeya Mwamba Tshisekedi", "...": "..." } },
        "scopes_applied": ["kyc.basic"],
        "scopes_withheld": []
      }
    },
    {
      "reference": "player-GOM-10877",
      "status": "succeeded",
      "result": {
        "id": "age_2e9d4a7c80",
        "object": "age_verification",
        "result": "verified",
        "over_threshold": false,
        "threshold": 18,
        "age": 16,
        "account": { "rdcpass_id": "COD-0910-7734-2051", "...": "..." },
        "kyc": { "basic": { "full_name": "Mbuyi Ilunga Nsimba", "...": "..." } },
        "scopes_applied": ["kyc.basic"],
        "scopes_withheld": []
      }
    },
    {
      "reference": "player-LUB-20411",
      "status": "succeeded",
      "result": {
        "id": "age_7b3f0e2a91",
        "object": "age_verification",
        "result": "not_found",
        "over_threshold": null,
        "threshold": 18,
        "age": null,
        "scopes_applied": [],
        "scopes_withheld": []
      }
    },
    {
      "reference": "player-KIN-55014",
      "status": "failed",
      "error": {
        "code": "invalid_identifier",
        "message": "identifier.value is not a valid rdcpass_id (expected COD-XXXX-XXXX-XXXX)."
      }
    }
  ],
  "next_cursor": null
}

Asynchronous example

Send the same body with Prefer: respond-async. RDCPASS answers immediately with 202 Accepted and a job; when the check completes, the age_verification.completed event is delivered to your application’s webhook URL with the result embedded. You can also poll GET /v1/jobs/{job_id}. Asynchronous batches of up to 10,000 items return a batch object and emit batch.completed or batch.completed_with_errors. See Webhooks for signature verification.

202 Accepted
HTTP/1.1 202 Accepted
Content-Type: application/json

{
  "id": "job_6a2d9f4e1c",
  "object": "job",
  "service": "age_verification",
  "status": "pending",
  "created_at": "2026-09-26T10:15:00Z",
  "result_url": "/v1/jobs/job_6a2d9f4e1c"
}
Webhook event age_verification.completed (abridged)
{
  "id": "evt_1c7e4a9d3b2f8065",
  "object": "event",
  "type": "age_verification.completed",
  "livemode": true,
  "created_at": "2026-09-26T10:15:01Z",
  "data": {
    "object": {
      "id": "job_6a2d9f4e1c",
      "object": "job",
      "service": "age_verification",
      "status": "completed",
      "created_at": "2026-09-26T10:15:00Z",
      "result_url": "/v1/jobs/job_6a2d9f4e1c",
      "result": {
        "id": "age_5f1b8c3d27",
        "object": "age_verification",
        "reference": "player-KIN-55012",
        "status": "completed",
        "result": "verified",
        "over_threshold": true,
        "threshold": 18,
        "age": 38,
        "account": { "rdcpass_id": "COD-2103-0214-4937", "...": "..." },
        "kyc": { "basic": { "...": "..." } },
        "scopes_applied": ["kyc.basic"],
        "scopes_withheld": []
      }
    }
  }
}

Billing

Each check is billed at the Age Verification base price — there is never a scope surcharge, because no additional scope applies. not_found results are billed at the base rate; requests rejected with an error are not billed. Batch items are billed individually. See Billing.

Errors

Errors use the standard format described in Errors. A threshold outside 1–120 or a missing identifier is rejected with 400 Bad Request.

CodeHTTPCauseWhat to do
invalid_identifier400The identifier value is malformed for its type.Check the format (e.g. COD-2103-0214-4937 for an RDCPASS ID).
unsupported_identifier_type400The identifier type is not accepted.Use rdcpass_id, passport, ceni, driving_licence or national_id.
purpose_not_approved403age_gating is not an approved purpose for your application.Add it to the application’s declared purposes in the console.
service_not_enabled403Age Verification is not enabled on this application.Enable the service on the application in the console.
production_access_required403Production keys used before the application was approved for production.Complete the process described in Going to production.
batch_too_large413A synchronous batch contains more than 50 items.Send it with Prefer: respond-async (up to 10,000 items).
insufficient_balance402Prepaid wallet is empty.Top up the wallet.
credit_limit_reached402Postpaid usage is over the credit limit.Contact your financial officer to settle or raise the limit.
quota_exceeded429The plan’s quota is used up.Wait for the reset or upgrade the plan.
rate_limited429Burst rate limit exceeded.Back off per retry_after and retry with the same Idempotency-Key.

Next steps

Questions about your integration? Contact developer support