> ## 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 a bank account

> Looks up the registered holder name on the bank for the given
account 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`).

This feature must be enabled on your account. If it is not, the
API returns `UNAUTHORIZED`.

### Outcomes

| Result | Response |
|---|---|
| Account found | `code=SUCCESS`, `data.status="found"`, `pricing=PAID` |
| No account at this bank | `code=SUCCESS`, `data.status="not found"`, `suggestion=reject`, `pricing=PAID` |
| Temporary service issue | `code=SERVER_ERROR`, `pricing=FREE` |
| Invalid request (account, bank, name, or `request_id`) | `code=CLIENT_ERROR`, `pricing=FREE` |
| Same `request_id` with a different body | `code=DUPLICATE_REFERENCE`, `pricing=FREE` |
| Missing token, feature not enabled, or IP not allowlisted | `code=UNAUTHORIZED`, `pricing=FREE` |
| Prepaid balance too low | `code=INSUFFICIENT_BALANCE`, `pricing=FREE` |

### Account numbers

You may include spaces, dashes, dots, or underscores. We ignore
those characters. After that, the number must be 6–20 digits.

### Bank codes

`bank_code` is a 3-digit Indonesian clearing code from the
supported-banks list in the dashboard. Values such as `2` or `14`
are accepted and padded to `002` / `014`. An unknown or disabled
code returns `CLIENT_ERROR` (not `not found`).

### 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 — unless the
previous attempt did not complete, in which case the check runs
again. 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/bank/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/bank/verify:
    post:
      tags:
        - Bank
      summary: Verify a name against a bank account
      description: >
        Looks up the registered holder name on the bank for the given

        account 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`).


        This feature must be enabled on your account. If it is not, the

        API returns `UNAUTHORIZED`.


        ### Outcomes


        | Result | Response |

        |---|---|

        | Account found | `code=SUCCESS`, `data.status="found"`, `pricing=PAID`
        |

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

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

        | Invalid request (account, bank, name, or `request_id`) |
        `code=CLIENT_ERROR`, `pricing=FREE` |

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

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

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


        ### Account numbers


        You may include spaces, dashes, dots, or underscores. We ignore

        those characters. After that, the number must be 6–20 digits.


        ### Bank codes


        `bank_code` is a 3-digit Indonesian clearing code from the

        supported-banks list in the dashboard. Values such as `2` or `14`

        are accepted and padded to `002` / `014`. An unknown or disabled

        code returns `CLIENT_ERROR` (not `not found`).


        ### 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 — unless the

        previous attempt did not complete, in which case the check runs

        again. A different body with the same `request_id` returns

        `DUPLICATE_REFERENCE`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VerifyRequest-2'
            examples:
              bri_match:
                summary: BRI lookup
                value:
                  request_id: TXN-20260727-001
                  account_number: '1234567890'
                  name: Budi Santoso
                  bank_code: '002'
              bca_partial:
                summary: BCA lookup with a partial name
                value:
                  request_id: f1c2a3b4-5d6e-7f80-9012-345678901234
                  account_number: 1234-5678-9012
                  name: Siti R.
                  bank_code: '014'
      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-2'
              examples:
                found:
                  summary: Account found with a matching name → pass
                  value:
                    code: SUCCESS
                    message: OK
                    data:
                      status: found
                      similarity: 100
                      suggestion: pass
                    request_id: TXN-20260727-001
                    pricing: PAID
                found_review:
                  summary: Account found with a partial name match → review
                  value:
                    code: SUCCESS
                    message: OK
                    data:
                      status: found
                      similarity: 55
                      suggestion: review
                    request_id: a7e3c1d0-2b4f-4a6c-8e90-1f2a3b4c5d6e
                    pricing: PAID
                not_found:
                  summary: No account at this bank → 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`,
            empty name, invalid account number, unsupported bank 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, this feature is
            not enabled on your account, 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 bank lookup could not be completed. You
            were not charged. Retrying the same `request_id` runs the
            check again.
          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-2:
      type: object
      required:
        - request_id
        - account_number
        - name
        - bank_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: TXN-20260727-001
        account_number:
          type: string
          description: |
            Bank account number. Spaces, dashes, dots, and underscores are
            ignored. After that, the number must be 6–20 digits.
          example: '1234567890'
        name:
          type: string
          minLength: 1
          maxLength: 200
          description: |
            Name to compare with the registered account holder.
          example: Budi Santoso
        bank_code:
          type: string
          description: |
            3-digit Indonesian bank clearing code from the supported-banks
            list in the dashboard. Values such as `2` or `14` are padded to
            `002` / `014`. Unknown or disabled codes return `CLIENT_ERROR`.
          example: '014'
    VerifyResponse-2:
      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-2'
        request_id:
          type: string
          description: The `request_id` you sent.
          example: TXN-20260727-001
        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-2:
      type: object
      required:
        - status
        - similarity
        - suggestion
      properties:
        status:
          type: string
          enum:
            - found
            - not found
          description: |
            Account state at this bank:
            - `found` — the account exists.
            - `not found` — no account for this number at this bank.
        similarity:
          type: number
          format: float
          minimum: 0
          maximum: 100
          description: |
            How closely the name you sent matches the registered account
            holder (0–100). `0` when `status` is `not found`.
        suggestion:
          type: string
          enum:
            - pass
            - review
            - reject
          description: |
            Recommended action based on `similarity` and the pass
            threshold. Always `reject` when `status` is `not found`.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        Access token from `POST /api/v1/kyc/auth/get-auth-token`.
        Valid for one hour.

````