> ## Documentation Index
> Fetch the complete documentation index at: https://docs.singapay.id/llms.txt
> Use this file to discover all available pages before exploring further.

# Verify a name against an e-wallet account

> Looks up the registered holder name on the e-wallet for the given
phone number and compares it with the `name` you send.

A completed check is billed (`pricing=PAID`), including when the
account is not found. Validation errors and service failures are
not billed (`pricing=FREE`).

### Outcomes

| Result | Response |
|---|---|
| Account found with a registered name | `code=SUCCESS`, `data.status="found with kyc"`, `pricing=PAID` |
| Account found, holder has not completed KYC | `code=SUCCESS`, `data.status="found without kyc"`, `similarity=0`, `suggestion=review`, `pricing=PAID` |
| No account for this phone | `code=SUCCESS`, `data.status="not found"`, `suggestion=reject`, `pricing=PAID` |
| Temporary service issue | `code=SERVER_ERROR`, `pricing=FREE` |
| Invalid request (phone, name, wallet, or `request_id`) | `code=CLIENT_ERROR`, `pricing=FREE` |
| Same `request_id` with a different body | `code=DUPLICATE_REFERENCE`, `pricing=FREE` |
| Missing token, or IP not allowlisted | `code=UNAUTHORIZED`, `pricing=FREE` |
| Prepaid balance too low | `code=INSUFFICIENT_BALANCE`, `pricing=FREE` |

### Phone numbers

Send Indonesian numbers as `08…` or `62…` (9–13 digits). Do not
include spaces or a `+` prefix.

### Idempotency

Use a unique `request_id` for each new check (UUID v4 is
recommended). Repeating the same `request_id` with the same body
returns the original result and is billed once. A different body
with the same `request_id` returns `DUPLICATE_REFERENCE`.




## OpenAPI

````yaml https://core.singapay.id/identity-verification/docs/swagger.json post /api/v1/kyc/ewallet/verify
openapi: 3.0.3
info:
  title: Singapay Identity Verification API
  description: |
    Verify e-wallet accounts and bank accounts with one API.

    ## Authentication

    1. Call `POST /api/v1/kyc/auth/get-auth-token` with your `client_id`,
       a UTC timestamp, and an HMAC signature. You receive a Bearer
       token that is valid for one hour.
    2. Send that token on every other request:

       `Authorization: Bearer <access_token>`

    `client_id` and `client_secret` are issued from the KYC dashboard.
    The secret is shown only once — if you lose it, regenerate it from
    the dashboard.

    ### How to sign

    ```
    timestamp      = current UTC time, RFC 3339, second precision
    string_to_sign = "{client_id}:{timestamp}"
    signature      = hex(HMAC-SHA256(client_secret, string_to_sign))
    ```

    The timestamp must be within 5 minutes of our server clock.

    ## Idempotency

    Send a unique `request_id` on every new check. If you retry with the
    same `request_id` and the same body, you get the same result and are
    charged once.

    Reusing a `request_id` with a **different** body returns
    `DUPLICATE_REFERENCE` and is not charged.

    If a previous attempt did not complete (for example a temporary
    service error), retrying the same `request_id` runs the check again.

    ## Rate limits

    The standard limit is 60 requests per second. Above that, the API
    returns `429 Too Many Requests` with `Retry-After` and
    `X-RateLimit-*` headers. Contact support if you need a higher limit.

    ## IP allowlisting

    You can restrict a credential to specific IP addresses. Requests
    from other addresses are rejected. If you do not set an allowlist,
    any IP may use the credential.
  version: 1.0.0
  contact:
    name: Singapay Integration Support
    email: integration@singapay.id
  license:
    name: Proprietary
servers:
  - url: https://sandbox-apigw.singapay.id
    description: Sandbox — for integration testing.
  - url: https://api.singapay.id
    description: Production.
security:
  - bearerAuth: []
tags:
  - name: Authentication
    description: |
      Get an access token. These endpoints do not require a Bearer token.
  - name: E-Wallet
    description: |
      Check whether a name matches the registered holder of an e-wallet
      account. Requires a Bearer token.
  - name: Bank
    description: |
      Check whether a name matches the registered holder of a bank
      account. Requires a Bearer token. This feature must be enabled on
      your account.
paths:
  /api/v1/kyc/ewallet/verify:
    post:
      tags:
        - E-Wallet
      summary: Verify a name against an e-wallet account
      description: >
        Looks up the registered holder name on the e-wallet for the given

        phone number and compares it with the `name` you send.


        A completed check is billed (`pricing=PAID`), including when the

        account is not found. Validation errors and service failures are

        not billed (`pricing=FREE`).


        ### Outcomes


        | Result | Response |

        |---|---|

        | Account found with a registered name | `code=SUCCESS`,
        `data.status="found with kyc"`, `pricing=PAID` |

        | Account found, holder has not completed KYC | `code=SUCCESS`,
        `data.status="found without kyc"`, `similarity=0`, `suggestion=review`,
        `pricing=PAID` |

        | No account for this phone | `code=SUCCESS`, `data.status="not found"`,
        `suggestion=reject`, `pricing=PAID` |

        | Temporary service issue | `code=SERVER_ERROR`, `pricing=FREE` |

        | Invalid request (phone, name, wallet, or `request_id`) |
        `code=CLIENT_ERROR`, `pricing=FREE` |

        | Same `request_id` with a different body | `code=DUPLICATE_REFERENCE`,
        `pricing=FREE` |

        | Missing token, or IP not allowlisted | `code=UNAUTHORIZED`,
        `pricing=FREE` |

        | Prepaid balance too low | `code=INSUFFICIENT_BALANCE`, `pricing=FREE`
        |


        ### Phone numbers


        Send Indonesian numbers as `08…` or `62…` (9–13 digits). Do not

        include spaces or a `+` prefix.


        ### Idempotency


        Use a unique `request_id` for each new check (UUID v4 is

        recommended). Repeating the same `request_id` with the same body

        returns the original result and is billed once. A different body

        with the same `request_id` returns `DUPLICATE_REFERENCE`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VerifyRequest'
            examples:
              dana_match:
                summary: DANA lookup
                value:
                  request_id: 8b1d6f3e-9a02-4c5d-9f7a-2c8e1b3d4f5a
                  phone_number: '081234567890'
                  name: Budi Santoso
                  ewallet_code: DANA
              ovo_partial:
                summary: OVO lookup with a partial name
                value:
                  request_id: f1c2a3b4-5d6e-7f80-9012-345678901234
                  phone_number: '6281122334455'
                  name: Siti R.
                  ewallet_code: OVO
      responses:
        '200':
          description: |
            Check complete (whether the account exists or not). `code` is
            `SUCCESS`. Read `data.status` and `data.suggestion` for the
            outcome. `pricing` is `PAID`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VerifyResponse'
              examples:
                found_with_kyc:
                  summary: Account found with a matching name → pass
                  value:
                    code: SUCCESS
                    message: OK
                    data:
                      status: found with kyc
                      similarity: 96.5
                      suggestion: pass
                    request_id: 8b1d6f3e-9a02-4c5d-9f7a-2c8e1b3d4f5a
                    pricing: PAID
                found_without_kyc:
                  summary: Account found but holder has not completed KYC → review
                  value:
                    code: SUCCESS
                    message: OK
                    data:
                      status: found without kyc
                      similarity: 0
                      suggestion: review
                    request_id: a7e3c1d0-2b4f-4a6c-8e90-1f2a3b4c5d6e
                    pricing: PAID
                not_found:
                  summary: No account on this wallet → reject (still billed)
                  value:
                    code: SUCCESS
                    message: OK
                    data:
                      status: not found
                      similarity: 0
                      suggestion: reject
                    request_id: f1c2a3b4-5d6e-7f80-9012-345678901234
                    pricing: PAID
        '400':
          description: |
            `CLIENT_ERROR` — the request is invalid (empty `request_id`,
            invalid phone, empty name, unknown `ewallet_code`, or
            malformed JSON).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FailureResponse'
              example:
                code: CLIENT_ERROR
                message: Error message
                request_id: 8b1d6f3e-9a02-4c5d-9f7a-2c8e1b3d4f5a
                pricing: FREE
        '401':
          description: |
            `UNAUTHORIZED` — missing or invalid token, or your IP is not
            on the credential allowlist.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FailureResponse'
              example:
                code: UNAUTHORIZED
                message: You don't have authorization to access.
                request_id: 8b1d6f3e-9a02-4c5d-9f7a-2c8e1b3d4f5a
                pricing: FREE
        '402':
          description: |
            `INSUFFICIENT_BALANCE` — your prepaid balance is below the
            fee for this call. Top up from the dashboard.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FailureResponse'
              example:
                code: INSUFFICIENT_BALANCE
                message: Balance is not enough, please top up
                request_id: 8b1d6f3e-9a02-4c5d-9f7a-2c8e1b3d4f5a
                pricing: FREE
        '409':
          description: |
            `DUPLICATE_REFERENCE` — this `request_id` was already used
            with a different body.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FailureResponse'
              example:
                code: DUPLICATE_REFERENCE
                message: Duplicate reference number
                request_id: 8b1d6f3e-9a02-4c5d-9f7a-2c8e1b3d4f5a
                pricing: FREE
        '429':
          description: |
            You have exceeded the rate limit. Wait for the number of
            seconds in `Retry-After` before retrying.
          headers:
            Retry-After:
              schema:
                type: integer
                minimum: 1
              description: Seconds to wait before retrying.
            X-RateLimit-Limit:
              schema:
                type: integer
            X-RateLimit-Remaining:
              schema:
                type: integer
            X-RateLimit-Reset:
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FailureResponse'
        '500':
          description: |
            `INTERNAL_ERROR` — an unexpected error occurred. You were not
            charged.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FailureResponse'
              example:
                code: INTERNAL_ERROR
                message: Error, please try again later
                request_id: 8b1d6f3e-9a02-4c5d-9f7a-2c8e1b3d4f5a
                pricing: FREE
        '502':
          description: |
            `SERVER_ERROR` — the e-wallet lookup could not be completed.
            You were not charged.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FailureResponse'
              example:
                code: SERVER_ERROR
                message: Error, please try again later
                request_id: 8b1d6f3e-9a02-4c5d-9f7a-2c8e1b3d4f5a
                pricing: FREE
components:
  schemas:
    VerifyRequest:
      type: object
      required:
        - request_id
        - phone_number
        - name
        - ewallet_code
      properties:
        request_id:
          type: string
          description: |
            Your unique reference for this check. Use a new UUID for each
            new verification. Sending the same key with the same body
            returns the original result and is billed once.
          example: 8b1d6f3e-9a02-4c5d-9f7a-2c8e1b3d4f5a
        phone_number:
          type: string
          pattern: ^(0|62)[1-9][0-9]{7,11}$
          description: |
            E-wallet phone number. Use `08…` or `62…` (9–13 digits). No
            spaces or `+`.
          example: '081234567890'
        name:
          type: string
          minLength: 1
          maxLength: 200
          description: |
            Name to compare with the registered wallet holder. Extra spaces
            and common honorifics are ignored when scoring.
          example: Budi Santoso
        ewallet_code:
          type: string
          enum:
            - DANA
            - SHOPEEPAY
            - GOPAY
            - OVO
          description: |
            Wallet to check. Only this wallet is queried.
    VerifyResponse:
      type: object
      required:
        - code
        - message
        - data
        - request_id
        - pricing
      properties:
        code:
          type: string
          enum:
            - SUCCESS
          description: Always `SUCCESS` on this response. Read `data` for the outcome.
          example: SUCCESS
        message:
          type: string
          enum:
            - OK
          description: Always `OK`. Use `code` and `data`, not this field, to branch.
          example: OK
        data:
          $ref: '#/components/schemas/VerifyData'
        request_id:
          type: string
          description: The `request_id` you sent.
          example: 8b1d6f3e-9a02-4c5d-9f7a-2c8e1b3d4f5a
        pricing:
          type: string
          enum:
            - PAID
          description: |
            Always `PAID` on this response — including when
            `data.status` is `not found`.
          example: PAID
    FailureResponse:
      type: object
      required:
        - code
        - message
        - request_id
        - pricing
      description: |
        Error response for e-wallet and bank requests.
        Failures are never billed (`pricing=FREE`).
      properties:
        code:
          type: string
          enum:
            - DUPLICATE_REFERENCE
            - SERVER_ERROR
            - INTERNAL_ERROR
            - CLIENT_ERROR
            - UNAUTHORIZED
            - INSUFFICIENT_BALANCE
          description: |
            What went wrong. Use this field to decide how to retry or
            surface the error.
          example: CLIENT_ERROR
        message:
          type: string
          description: Human-readable explanation.
          example: Error message
        request_id:
          type: string
          description: |
            The `request_id` you sent, when we could read it. Empty if the
            body could not be parsed.
          example: 8b1d6f3e-9a02-4c5d-9f7a-2c8e1b3d4f5a
        pricing:
          type: string
          enum:
            - FREE
          description: Failures are never billed.
          example: FREE
    VerifyData:
      type: object
      required:
        - status
        - similarity
        - suggestion
      properties:
        status:
          type: string
          enum:
            - not found
            - found without kyc
            - found with kyc
          description: |
            Account state on the named wallet:
            - `not found` — no account for this phone number.
            - `found without kyc` — an account exists, but the holder has
              not completed KYC, so there is no real registered name.
            - `found with kyc` — an account exists with a registered name.
        similarity:
          type: number
          format: float
          minimum: 0
          maximum: 100
          description: |
            How closely the name you sent matches the registered wallet
            name (0–100). `0` when `status` is `not found` or
            `found without kyc`.
        suggestion:
          type: string
          enum:
            - pass
            - review
            - reject
          description: |
            Recommended action based on `similarity` and the pass
            threshold for this wallet.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        Access token from `POST /api/v1/kyc/auth/get-auth-token`.
        Valid for one hour.

````