> ## 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.

# Get an access token

> Sign `{client_id}:{timestamp}` with your `client_secret` using
HMAC-SHA256 and send the hex digest as `signature`. On success
you receive a Bearer token.

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




## OpenAPI

````yaml https://core.singapay.id/identity-verification/docs/swagger.json post /api/v1/kyc/auth/get-auth-token
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/auth/get-auth-token:
    post:
      tags:
        - Authentication
      summary: Get an access token
      description: |
        Sign `{client_id}:{timestamp}` with your `client_secret` using
        HMAC-SHA256 and send the hex digest as `signature`. On success
        you receive a Bearer token.

        `timestamp` must be within 5 minutes of our server clock.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GetAuthTokenRequest'
            examples:
              standard:
                summary: Typical request
                value:
                  client_id: kc_live_a3f2c4
                  timestamp: '2026-05-26T07:30:00Z'
                  signature: >-
                    9d4e7a8b1f3c2e5d6a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f
      responses:
        '200':
          description: Access token issued.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccessToken'
              examples:
                standard:
                  summary: Typical response
                  value:
                    access_token: >-
                      eyJhbGciOiJSUzI1NiIsImtpZCI6ImsxIn0.eyJpc3MiOiJraWMtdmVyaWZ5IiwiYXVkIjoia3ljLWFwaSIsImV4cCI6MTcyNDgyMTYwMH0...
                    token_type: Bearer
                    expires_in: 3600
                    audience: kyc-api
        '400':
          description: >-
            The request body is missing a field or the timestamp is not valid
            RFC 3339.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: |
            The signature did not match, or the credentials are unknown
            or no longer active.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security: []
components:
  schemas:
    GetAuthTokenRequest:
      type: object
      required:
        - client_id
        - timestamp
        - signature
      properties:
        client_id:
          type: string
          description: Credential ID from the KYC dashboard.
          example: kc_live_a3f2c4
        timestamp:
          type: string
          format: date-time
          description: Current UTC time in RFC 3339. Must be within 5 minutes of our clock.
          example: '2026-05-26T07:30:00Z'
        signature:
          type: string
          pattern: ^[0-9a-fA-F]{64}$
          description: |
            Hex-encoded HMAC-SHA256 of `{client_id}:{timestamp}`, signed
            with your `client_secret`.
          example: 9d4e7a8b1f3c2e5d6a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f
    AccessToken:
      type: object
      required:
        - access_token
        - token_type
        - expires_in
        - audience
      properties:
        access_token:
          type: string
          description: |
            Bearer token. Send it as `Authorization: Bearer <access_token>`
            on every other request.
        token_type:
          type: string
          enum:
            - Bearer
        expires_in:
          type: integer
          minimum: 1
          description: Seconds until the token expires (usually 3600).
          example: 3600
        audience:
          type: string
          enum:
            - kyc-api
          description: Always `kyc-api`.
    Error:
      type: object
      required:
        - error
        - message
      properties:
        error:
          type: string
          description: Error code for authentication endpoints.
          example: INVALID_SIGNATURE
        message:
          type: string
          description: Human-readable explanation.
          example: signature did not match
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        Access token from `POST /api/v1/kyc/auth/get-auth-token`.
        Valid for one hour.

````