From 216296304f6832a265ada5da848e84baab3c7781 Mon Sep 17 00:00:00 2001 From: Sergei Tsoganov Date: Wed, 10 Jun 2026 11:33:32 +0300 Subject: [PATCH 1/5] Updated identification info --- .../components/schemas/ClaimsComparison.yaml | 41 ++++ .../schemas/IdentificationRequest.yaml | 192 +++++++++--------- .../schemas/PendingIdentificationRequest.yaml | 24 ++- openapi/openapi.yaml | 10 + .../paths/show_identification_request.yaml | 6 +- .../identification_request_completed.yaml | 6 +- 6 files changed, 182 insertions(+), 97 deletions(-) create mode 100644 openapi/components/schemas/ClaimsComparison.yaml diff --git a/openapi/components/schemas/ClaimsComparison.yaml b/openapi/components/schemas/ClaimsComparison.yaml new file mode 100644 index 00000000..7f47ebb5 --- /dev/null +++ b/openapi/components/schemas/ClaimsComparison.yaml @@ -0,0 +1,41 @@ +type: object +properties: + claims_matched: + type: + - boolean + - 'null' + description: | + Whether all non-blank required claims matched the authenticated user's data. + + - `true` — all required claims matched (or no claims were provided for comparison). + - `false` — at least one required claim mismatched. + - `null` — the request is not yet completed; comparison has not run. + example: true + mismatched_claims: + type: object + description: | + Details of claims that did not match, keyed by claim type (for example `sub`, `name`, `birthdate`, `country`). + An empty object when all claims matched or the request is not yet completed. + additionalProperties: + type: object + properties: + expected: + type: string + description: The value provided when creating the identification request. + actual: + type: string + description: The value from the authenticated user's identity. + required: + - expected + - actual + example: + sub: + expected: "EE12345678901" + actual: "EE30303039914" + claims_comparison_errors: + type: array + description: Human-readable comparison error messages. An empty array when all claims matched or the request is not yet completed. + items: + type: string + example: + - "Value mismatch for claim 'sub', expected 'EE12345678901', got 'EE30303039914'" diff --git a/openapi/components/schemas/IdentificationRequest.yaml b/openapi/components/schemas/IdentificationRequest.yaml index 95bb765f..0e5e997a 100644 --- a/openapi/components/schemas/IdentificationRequest.yaml +++ b/openapi/components/schemas/IdentificationRequest.yaml @@ -1,103 +1,109 @@ -type: object -properties: - id: - type: string - format: uuid - description: The unique identifier for the identification request. - link: - type: string - format: uri - description: The link associated with the identification request. - link_expires_at: - type: string - format: date-time - description: The expiration date and time of the link. - claims_required: - type: array - description: A list of claims required for the identification request. - items: - type: object - properties: - type: - type: string - description: The type of claim. - example: "sub" - value: - type: string - description: The value of the claim. - example: "EE30303039914" - completed_at: - type: string - format: date-time - description: The date and time when the request was completed. - status: - type: string - description: The current status of the identification request. - example: completed - enum: - - pending - - completed - - expired - created_at: - type: string - format: date-time - description: The date and time when the request was created. - result: - type: object - description: The result of the identification request. +allOf: + - type: object properties: - acr: + id: type: string - example: "high" - description: The level of authentication based on the eIDAS LoA (level of assurance). - sub: + format: uuid + description: The unique identifier for the identification request. + link: type: string - example: "EE30303039914" - description: The national id or document number of the authenticated user. - auth_time: - type: integer - format: unix-timestamp - example: 1734432000 - given_name: + format: uri + description: The link associated with the identification request. + link_expires_at: type: string - example: "OK" - family_name: + format: date-time + description: The expiration date and time of the link. + claims_required: + type: array + description: A list of claims required for the identification request. + items: + type: object + properties: + type: + type: string + description: The type of claim. + example: "sub" + value: + type: string + description: The value of the claim. + example: "EE30303039914" + completed_at: type: string - example: "TESTNUMBER" - date_of_birth: + format: date-time + description: The date and time when the request was completed. + status: type: string - format: date - example: "2000-01-01" - expiration_time: - type: integer - format: unix-timestamp - example: 1734432000 - description: The expiration time of the identification result as a Unix timestamp (based on the document or certificate validity period). - authentication_type: + description: | + The current status of the identification request. + + `completed` means the end-user successfully authenticated — not necessarily that all required claims matched. + Use `claims_matched` to determine whether the authenticated data matched your expectations. + example: completed + enum: + - pending + - completed + - expired + created_at: type: string - example: "ID_CARD" - description: The type of authentication used for the identification process. - client_id: - type: string - description: The client ID associated with the request. - example: "oidc-b8b8b8b8-c88f-1111-b11d-ecf190aa4982-72" - reference: - type: string - description: A reference string for the identification request. - reason: - type: string - description: The reason for the identification request. - example: "Test Reason" - reason_translations: - type: object - description: The translations of the reason for the identification request. - properties: - en: + format: date-time + description: The date and time when the request was created. + result: + type: object + description: The authenticated user's identity data. Present when the request is completed. + properties: + acr: + type: string + example: "high" + description: The level of authentication based on the eIDAS LoA (level of assurance). + sub: + type: string + example: "EE30303039914" + description: The national id or document number of the authenticated user. + auth_time: + type: integer + format: unix-timestamp + example: 1734432000 + given_name: + type: string + example: "OK" + family_name: + type: string + example: "TESTNUMBER" + date_of_birth: + type: string + format: date + example: "2000-01-01" + expiration_time: + type: integer + format: unix-timestamp + example: 1734432000 + description: The expiration time of the identification result as a Unix timestamp (based on the document or certificate validity period). + authentication_type: + type: string + example: "ID_CARD" + description: The type of authentication used for the identification process. + client_id: type: string - description: The translation of the reason for the identification request in English. - et: + description: The client ID associated with the request. + example: "oidc-b8b8b8b8-c88f-1111-b11d-ecf190aa4982-72" + reference: type: string - description: The translation of the reason for the identification request in Estonian. - ru: + description: A reference string for the identification request. + reason: type: string - description: The translation of the reason for the identification request in Russian. \ No newline at end of file + description: The reason for the identification request. + example: "Test Reason" + reason_translations: + type: object + description: The translations of the reason for the identification request. + properties: + en: + type: string + description: The translation of the reason for the identification request in English. + et: + type: string + description: The translation of the reason for the identification request in Estonian. + ru: + type: string + description: The translation of the reason for the identification request in Russian. + - $ref: ./ClaimsComparison.yaml diff --git a/openapi/components/schemas/PendingIdentificationRequest.yaml b/openapi/components/schemas/PendingIdentificationRequest.yaml index 84b1b14f..011301dc 100644 --- a/openapi/components/schemas/PendingIdentificationRequest.yaml +++ b/openapi/components/schemas/PendingIdentificationRequest.yaml @@ -33,7 +33,11 @@ properties: example: null status: type: string - description: The current status of the identification request. + description: | + The current status of the identification request. + + `completed` means the end-user successfully authenticated — not necessarily that all required claims matched. + Use `claims_matched` to determine whether the authenticated data matched your expectations. example: pending created_at: type: string @@ -66,4 +70,20 @@ properties: description: The translation of the reason for the identification request in Estonian. ru: type: string - description: The translation of the reason for the identification request in Russian. \ No newline at end of file + description: The translation of the reason for the identification request in Russian. + claims_matched: + type: + - boolean + - 'null' + description: Always `null` while the request is pending. + example: null + mismatched_claims: + type: object + description: Always an empty object while the request is pending. + example: {} + claims_comparison_errors: + type: array + description: Always an empty array while the request is pending. + items: + type: string + example: [] \ No newline at end of file diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 9944c3d3..a6d656de 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -20,6 +20,16 @@ info: The token has a limited lifespan and will expire after 120 seconds. Clients should handle token expiration and generate a new one as necessary. + + ## Claims comparison + + When an identification request is created with `claims_required`, eeID compares the authenticated user's identity against those claims. Authentication success is separate from claims match: + + - A request becomes `completed` when the end-user successfully authenticates, even if some claims do not match. + - Check `claims_matched`, `mismatched_claims`, and `claims_comparison_errors` on completed requests to see the comparison outcome. + - Your service is responsible for deciding how to handle mismatches (for example manual review, approval, or rejection). + - Proof of identity (POI) is generated and the completion webhook is sent for all successfully authenticated requests, regardless of claim match. + - After the first successful authentication, the identification link expires. The end-user cannot use the same link to retry; create a new request if another identification is needed. tags: - name: Access Token description: "Endpoint for obtaining authentication token." diff --git a/openapi/paths/show_identification_request.yaml b/openapi/paths/show_identification_request.yaml index 89b971ed..c8824f0e 100644 --- a/openapi/paths/show_identification_request.yaml +++ b/openapi/paths/show_identification_request.yaml @@ -1,6 +1,10 @@ get: summary: Retrieve an identification request - description: Fetches the details of a specific identification request by its UUID. + description: | + Fetches the details of a specific identification request by its UUID. + + For completed requests, check `claims_matched` to see whether the authenticated user's data matched the + `claims_required` provided at creation time. Use `mismatched_claims` and `claims_comparison_errors` for details. operationId: getIdentificationRequest security: - BearerAuth: [] diff --git a/openapi/webhooks/identification_request_completed.yaml b/openapi/webhooks/identification_request_completed.yaml index bcdfa6e8..1631666d 100644 --- a/openapi/webhooks/identification_request_completed.yaml +++ b/openapi/webhooks/identification_request_completed.yaml @@ -1,6 +1,10 @@ post: summary: Identification request completed webhook - description: "Webhook triggered when an identification request is completed." + description: | + Webhook triggered when an identification request is completed after the end-user successfully authenticates. + + This fires for both matched and mismatched claims. Retrieve the identification request to read `claims_matched`, + `mismatched_claims`, and `claims_comparison_errors`, then compare `result` against your own records as needed. operationId: identificationRequestCompleted security: - HMAC: [] From 9c9b9f1571d818f27cff3673b3e1e16af3251af2 Mon Sep 17 00:00:00 2001 From: Sergei Tsoganov Date: Fri, 12 Jun 2026 15:00:39 +0300 Subject: [PATCH 2/5] Updated result structure --- .../schemas/IdentificationRequest.yaml | 34 +--------- .../schemas/IdentificationResult.yaml | 64 +++++++++++++++++++ openapi/openapi.yaml | 6 ++ 3 files changed, 71 insertions(+), 33 deletions(-) create mode 100644 openapi/components/schemas/IdentificationResult.yaml diff --git a/openapi/components/schemas/IdentificationRequest.yaml b/openapi/components/schemas/IdentificationRequest.yaml index 0e5e997a..a32cb2e4 100644 --- a/openapi/components/schemas/IdentificationRequest.yaml +++ b/openapi/components/schemas/IdentificationRequest.yaml @@ -48,40 +48,8 @@ allOf: format: date-time description: The date and time when the request was created. result: - type: object description: The authenticated user's identity data. Present when the request is completed. - properties: - acr: - type: string - example: "high" - description: The level of authentication based on the eIDAS LoA (level of assurance). - sub: - type: string - example: "EE30303039914" - description: The national id or document number of the authenticated user. - auth_time: - type: integer - format: unix-timestamp - example: 1734432000 - given_name: - type: string - example: "OK" - family_name: - type: string - example: "TESTNUMBER" - date_of_birth: - type: string - format: date - example: "2000-01-01" - expiration_time: - type: integer - format: unix-timestamp - example: 1734432000 - description: The expiration time of the identification result as a Unix timestamp (based on the document or certificate validity period). - authentication_type: - type: string - example: "ID_CARD" - description: The type of authentication used for the identification process. + $ref: ./IdentificationResult.yaml client_id: type: string description: The client ID associated with the request. diff --git a/openapi/components/schemas/IdentificationResult.yaml b/openapi/components/schemas/IdentificationResult.yaml new file mode 100644 index 00000000..a6892cdb --- /dev/null +++ b/openapi/components/schemas/IdentificationResult.yaml @@ -0,0 +1,64 @@ +type: object +description: | + The authenticated user's identity data from OIDC userinfo. + + For document-based identification, `country`, `id_number`, and `document_number` may also be included + when available. + + `sub` is always taken from OIDC userinfo and is not backfilled from document-based identification data. + For document-based identification, `sub` is typically the document country code followed by `id_number` + or `document_number`, depending on which value was used when the subject was constructed during authentication. +properties: + acr: + type: string + example: "high" + description: The level of authentication based on the eIDAS LoA (level of assurance). + sub: + type: string + example: "EE30303039914" + description: | + Subject identifier for the authenticated user — typically a country-prefixed + document or national ID number. + auth_time: + type: integer + format: unix-timestamp + example: 1734432000 + given_name: + type: string + example: "OK" + family_name: + type: string + example: "TESTNUMBER" + date_of_birth: + type: string + format: date + example: "2000-01-01" + expiration_time: + type: integer + format: unix-timestamp + example: 1734432000 + description: | + The expiration time of the identification result as a Unix timestamp + (based on the document or certificate validity period). + authentication_type: + type: string + example: "ID_CARD" + description: | + The type of authentication used for the identification process + (for example `ID_CARD`, `SMART_ID`, `MOBILE_ID`). + country: + type: string + example: "GB" + description: | + Document country in ISO 3166-1 Alpha-2 format. + Present for document-based identification when available. + id_number: + type: string + example: "12345678901" + description: | + National identification number from document-based identification, when available. + document_number: + type: string + example: "AB123456" + description: | + Identity document number from document-based identification, when available. diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index a6d656de..a8f5eaec 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -30,6 +30,12 @@ info: - Your service is responsible for deciding how to handle mismatches (for example manual review, approval, or rejection). - Proof of identity (POI) is generated and the completion webhook is sent for all successfully authenticated requests, regardless of claim match. - After the first successful authentication, the identification link expires. The end-user cannot use the same link to retry; create a new request if another identification is needed. + + ## Document-based identification result fields + + For document-based identification, the `result` object may also include `country`, `id_number`, and + `document_number`. These fields are not present for other authentication methods. The `sub` claim + always comes from OIDC userinfo. tags: - name: Access Token description: "Endpoint for obtaining authentication token." From 6ded2b688823337bf4c94ee6152764473d279d5f Mon Sep 17 00:00:00 2001 From: Sergei Tsoganov Date: Thu, 2 Jul 2026 11:26:58 +0300 Subject: [PATCH 3/5] Updated specification --- openapi/openapi.yaml | 2 +- openapi/paths/create_identification_request.yaml | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index a8f5eaec..11d077fe 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -1,6 +1,6 @@ openapi: 3.1.0 info: - version: 1.0.0 + version: 1.1.0 title: eeID Identification Service API termsOfService: https://meedia.internet.ee/files/Terms_of_use_eeID.pdf contact: diff --git a/openapi/paths/create_identification_request.yaml b/openapi/paths/create_identification_request.yaml index ba266dc3..5da2d2ac 100644 --- a/openapi/paths/create_identification_request.yaml +++ b/openapi/paths/create_identification_request.yaml @@ -1,5 +1,10 @@ summary: Create an identification request -description: This endpoint allows a service to create a new identification request. The request must include claims required for the identification process. +description: | + This endpoint creates a new identification request. + + `claims_required` must always be present. In normal matching mode, provide expected + claim values to compare against the authenticated user's data. In discovery mode, + you may provide supported claim types with blank or omitted values. operationId: createIdentificationRequest security: - BearerAuth: [] @@ -24,7 +29,12 @@ requestBody: description: A unique reference for the identification request. claims_required: type: array - description: An array of claims required for the identification process. + description: | + A required array of supported claims for the identification request. + + The array itself must be present and contain at least one supported claim object. + In discovery mode, claim `value` may be blank or omitted. Blank-valued claims are + treated as discovery hints and are not compared for mismatch. items: type: object properties: From bc7ff9f8d23d996c869e05354a6b3df2c2dbe333 Mon Sep 17 00:00:00 2001 From: Sergei Tsoganov Date: Thu, 2 Jul 2026 12:34:30 +0300 Subject: [PATCH 4/5] Added pr preview workflow --- .github/workflows/deploy-preview.yml | 55 ++++++++++++++++++++++++++++ .github/workflows/deploy.yml | 2 +- .github/workflows/remove-preview.yml | 40 ++++++++++++++++++++ 3 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/deploy-preview.yml create mode 100644 .github/workflows/remove-preview.yml diff --git a/.github/workflows/deploy-preview.yml b/.github/workflows/deploy-preview.yml new file mode 100644 index 00000000..2f55758f --- /dev/null +++ b/.github/workflows/deploy-preview.yml @@ -0,0 +1,55 @@ +name: Deploy API Documentation Preview + +on: + pull_request: + types: + - opened + - synchronize + - reopened + +permissions: + contents: write + pull-requests: write + +concurrency: + group: docs-preview-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + deploy-preview: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Build docs + run: ./make.sh + + - name: Stage preview files + run: | + mkdir -p preview/docs + cp index.html preview/index.html + cp -R docs/. preview/docs/ + + - name: Deploy preview to GitHub Pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./preview + destination_dir: previews/pr-${{ github.event.pull_request.number }} + keep_files: true + exclude_assets: '.github,openapi,redocly.yaml,LICENSE,README.md,make.sh' + + - name: Comment preview URL + uses: actions/github-script@v7 + with: + script: | + const previewUrl = `https://${context.repo.owner}.github.io/${context.repo.repo}/previews/pr-${context.issue.number}/`; + const body = `## Docs Preview\n\nPreview URL: ${previewUrl}`; + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body + }); diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 65715ed6..56fce0ba 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -20,5 +20,5 @@ jobs: with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: . - force_orphan: true + keep_files: true exclude_assets: '.github,openapi,redocly.yaml,LICENSE,README.md,make.sh' diff --git a/.github/workflows/remove-preview.yml b/.github/workflows/remove-preview.yml new file mode 100644 index 00000000..6679f21f --- /dev/null +++ b/.github/workflows/remove-preview.yml @@ -0,0 +1,40 @@ +name: Remove API Documentation Preview + +on: + pull_request: + types: + - closed + +permissions: + contents: write + +concurrency: + group: docs-preview-cleanup-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + remove-preview: + runs-on: ubuntu-latest + + steps: + - name: Checkout GitHub Pages branch + uses: actions/checkout@v4 + with: + ref: gh-pages + fetch-depth: 1 + + - name: Remove preview directory + run: | + rm -rf "previews/pr-${{ github.event.pull_request.number }}" + + - name: Commit preview removal + run: | + if git diff --quiet; then + echo "No preview directory to remove." + exit 0 + fi + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add -A + git commit -m "Remove docs preview for PR #${{ github.event.pull_request.number }}" + git push From 24ac72a84e9eb60f7fc60b692c98a296534f7bb6 Mon Sep 17 00:00:00 2001 From: Sergei Tsoganov Date: Thu, 2 Jul 2026 12:37:33 +0300 Subject: [PATCH 5/5] Added preview note --- .github/workflows/deploy-preview.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-preview.yml b/.github/workflows/deploy-preview.yml index 2f55758f..05c2cebe 100644 --- a/.github/workflows/deploy-preview.yml +++ b/.github/workflows/deploy-preview.yml @@ -41,12 +41,23 @@ jobs: keep_files: true exclude_assets: '.github,openapi,redocly.yaml,LICENSE,README.md,make.sh' + - name: Add preview access note + run: | + preview_url="https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/previews/pr-${{ github.event.pull_request.number }}/" + { + echo "## Docs Preview" + echo + echo "Preview deployed successfully." + echo + echo "Access it here: ${preview_url}" + } >> "$GITHUB_STEP_SUMMARY" + - name: Comment preview URL uses: actions/github-script@v7 with: script: | const previewUrl = `https://${context.repo.owner}.github.io/${context.repo.repo}/previews/pr-${context.issue.number}/`; - const body = `## Docs Preview\n\nPreview URL: ${previewUrl}`; + const body = `## Docs Preview\n\nPreview deployed successfully.\n\nAccess it here: ${previewUrl}`; await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo,