Create a verification session

POST /api/verification/init — the request body, the verification types, the age threshold, the supported countries, the session the call returns, and the ways it fails.

Last updated: 2026-10-29

Starts a verification session and returns the URL to send the shopper to. This is the only call that creates state, the only billable one, and one of the three routes that require an API key.

POST https://api.eidas-pro.com/api/verification/init

Requires the x-merchant-api-key header — see authentication. Answers 201 Created on success.

Request body

curl -X POST https://api.eidas-pro.com/api/verification/init \
  -H "x-merchant-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "verificationType": "age",
    "method": "idin",
    "redirectUrl": "https://shop.example.com/checkout/age-check",
    "orderRef": "1234"
  }'
FieldTypeRequiredNotes
verificationTypeage country bothyesThe only required field.
ageThresholdinteger, 12–25noThe age an age or both session checks. Omitted, it is 18. Not accepted with country. The method must be able to check it: see the age threshold.
methodeudi idin itsme avnoTarget a specific verification method instead of the deployment's default. Only request one that /api/config lists.
redirectUrlstringnoWhere the shopper's browser is sent after the identity step. Must be https on a public domain name, and its host must be in your allowed redirect hosts (dashboard → Policy) once you have listed any. The host is checked against the list again when the shopper comes back; while the list is empty, or once the host is removed, the shopper sees an eIDAS Pro "verification finished" page instead.
countryWhiteliststring arraynoISO 3166-1 alpha-2 codes. Part of the country-verification contract.
countryBlackliststring arraynoISO 3166-1 alpha-2 codes. Part of the country-verification contract.
orderRefstringnoYour own pseudonymous reference to the order or cart, at most 64 characters: letters, digits and . _ - : # / only. It is returned by /status and nowhere else — never in webhooks or audit records. A malformed value answers 400. Do not put a name, an email address or anything else that identifies the shopper in it.
metadataobjectnoDeprecated. Accepted so older integrations keep working, and then discarded without being stored. The one exception: a valid metadata.orderRef is used as orderRef when no top-level orderRef is sent (an invalid one is dropped, not refused). Send orderRef instead.
dcApiOriginstringnoFor the browser wallet (Digital Credentials API). The origin of the shop page that will offer it, exactly as the browser's location.origin gives it: https://shop.example.com, with no port, path or trailing slash. It is kept with the session only when its host is in your allowed redirect hosts. Any other value does not fail the request: the session starts as usual and the response reports dcApiOrigin: null.
captchaTokenstringnoRequired only when the deployment has a CAPTCHA secret configured. Maximum 2048 characters.
licenseKeystringnoAccepted for wire compatibility with older clients and ignored. Do not send it in new code.

Unknown properties are rejected, not ignored: a body carrying a field that is not in this table answers 400 with code VALIDATION_ERROR. (The keys inside the deprecated metadata object are the exception: they are discarded.) That is worth knowing when you are porting code from another provider — a field this API has never had (an age field named anything other than ageThreshold, for example) will fail the whole request rather than being quietly dropped.

The age threshold

ageThreshold sets the age an age or both session checks: a whole number from 12 to 25. Send 16 for a product sold from 16, 21 for one sold from 21; leave it out for 18, which is what every session checked before the field existed. The session keeps it for its whole life, and the init response, /status, /result and the webhooks all report it as ageThreshold. It is your setting, not personal data.

Not every method can check every age:

MethodAccepted thresholdsWhy
eudi12–25The age is computed from the birth date in the EUDI wallet's PID.
av12–25The EU Age Verification app is asked for that age (age_over_NN).
idin, itsme12–18The bank-identity broker answers one fixed question, "18 or over?". A pass proves any age up to 18.

Read the ranges for the methods your deployment actually serves from GET /api/config → ageThresholds (next to defaultAgeThreshold, which is 18). An init whose method cannot check the requested age answers 400 with code AGE_THRESHOLD_UNSUPPORTED, and no session is created; nothing is rounded to an age the method can check.

For idin and itsme below 18, a shopper who passes the 18+ check is verified. One who fails it is not reported as below your threshold — failing "18 or over?" says nothing about 16 or over — so the session ends rejected with rejectionReason: "age_indeterminate", and it is not billed. A verified session, and one where the method proved the shopper is below the threshold, are billed as before.

Verification types

verificationType accepts three values at the request-validation level:

  • age — is the shopper at least the threshold age?
  • country — is the shopper's verified country one you allow? Only the yes/no answer is kept and returned (countryVerified), never the country itself.
  • both — age and country in one session.

GET /api/config's supportedVerificationTypes tells you which of these a given deployment can actually deliver — read it rather than assuming all three work. The currently implemented path — bank-identity methods delivered through a broker — serves age only, so supportedVerificationTypes lists age once a broker method is configured there, and is empty when none is. Sending country or both to that path answers 400 rather than silently downgrading them to an age check that can never satisfy what you asked for. countryWhitelist and countryBlacklist are accepted by the request body for the same reason and are not evaluated by that path.

Supported countries

GET /api/config reports 32 under supportedCountries — the 27 EU member states plus Iceland, Liechtenstein, Norway, Switzerland, and the United Kingdom:

AT BE BG HR CY CZ DK EE FI FR DE GR HU IE IT LV LT LU MT NL
PL PT RO SK SI ES SE IS LI NO CH GB

Read the live endpoint rather than hard-coding this list, and read supportedMethods from the same response to learn which verification methods that particular deployment can actually serve. An empty supportedMethods array means none are configured there yet.

Response

{
    "sessionId": "0f3c1c2b-9a4d-4d1f-8c1a-2b6d5e4f7a90",
    "ageThreshold": 18,
    "expiresAt": "2026-09-04T12:15:00.000Z",
    "authorizationUrl": "https://broker.example/authorize?...",
    "dcApiOrigin": null,
    "dcApiAvailable": false,
    "dcApiProtocols": []
}
FieldNotes
sessionIdUUID. Every other call in the flow takes it, so store it against your order.
ageThresholdThe age the session checks: your ageThreshold, or 18 when you sent none. Absent for a country session.
expiresAtISO 8601 timestamp. Sessions on the broker path live 15 minutes from creation.
authorizationUrlWhere to send the shopper's browser. Present on the redirect-based path.
dcApiOriginThe dcApiOrigin kept with the session, or null when none was sent or it was not accepted. The reason is not reported. On its own it does not mean the browser wallet is available: dcApiAvailable says that.
dcApiAvailabletrue only when dcApiOrigin was kept and this deployment offers the browser wallet for the session's method. Show a browser-wallet option only when it is true; the wallet link and QR code work either way. It is false on the production API today, which does not offer the browser wallet yet.
dcApiProtocolsThe browser-wallet protocols this deployment serves for the session, for example openid4vp-v1-signed; empty when dcApiAvailable is false. Offer the browser wallet only when the shopper's browser allows at least one of them (DigitalCredential.userAgentAllowsProtocol()): Safari allows only org-iso-mdoc, so it would otherwise get an option that cannot work.
qrCodeUrlDeclared by the response contract for paths that present a QR code. Absent on the redirect-based path — treat it as optional and never assume it is there.

Send the shopper to authorizationUrl, then wait for the verdict: watch the event stream or poll the status from the browser, and read the authoritative answer from the result endpoint on your server.

Failures

StatuscodeCause
400VALIDATION_ERRORMissing or invalid verificationType, an ageThreshold that is not a whole number from 12 to 25 or is sent with country, an unknown property, a method or verification type the active path cannot serve, or a failed CAPTCHA check.
400AGE_THRESHOLD_UNSUPPORTEDThe requested method cannot check ageThreshold, for example idin above 18. See the age threshold. No session was created.
400REDIRECT_HOST_NOT_ALLOWEDredirectUrl is not https on a public domain name, or its host is not in your allowed redirect hosts. Rejected here, at session creation, rather than after a real shopper has finished a bank flow.
401INVALID_MERCHANT_KEYKey missing, unknown, revoked, or expired — identical response for all of them.
402NO_SUBSCRIPTIONValid key, no active subscription.
403LAYER_NOT_ENTITLEDActive subscription that does not include the electronic-identity layer.
429RATE_LIMIT_EXCEEDEDPer-minute ceiling reached. Wait the seconds in the Retry-After header (also repeated as retryAfter in the body).
503METHOD_UNAVAILABLEThe deployment has no credentials configured for the requested method, so it fails closed.
503BROKER_UNAVAILABLEThe identity provider could not be reached right now. Retryable; no session was created.

Branch on code, not on the human-readable message — see errors and status codes.