# Slip's QR Code Verification API

> Verifies QR codes printed on Thai bank transfer slips. A request submits the raw QR
> string from a slip; the API validates it against the bank verification network and
> returns the parsed transaction (amount, timestamp, sender, receiver). Each successful
> verification consumes one credit from the caller's balance.

This file is a condensed, machine-readable version of the public API documentation.
Full human-readable docs: https://api-document.vercel.app (see also API_DOC.md and
API_DOC_THAI.md in the documentation repository).

Replace `{{baseUrl}}` below with the base URL issued to you with your API key.

## Endpoint

POST {{baseUrl}}/api/v1/verify

Single endpoint. No other public routes.

## Authentication

Every request carries an API key in a header. There is no OAuth flow, no bearer token,
and no session; the key both authenticates the request and identifies the account whose
balance is charged.

| Header | Type | Required | Description |
| --- | --- | --- | --- |
| `x-api-key` | string | yes | The account's API key |
| `Content-Type` | string | yes | Must be `application/json` |

## Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `qrcode` | string | yes | The raw QR code payload read from the bank slip |

```json
{
  "qrcode": "0002010102115302TH5416Bank_Slip_QR_Data6204ABCD62070503***631041F4"
}
```

## Success response

HTTP `200 OK`

```json
{
  "status": "success",
  "data": {
    "amount": 100.38,
    "dateTime": "2025-10-05T22:09:59+07:00",
    "timestamp": 1759676999,
    "transactionId": "015278220959BPP13419",
    "senderBankAccountNumber": "xxxxx1234x",
    "senderName": "นาย สมชาย ใจดี",
    "senderNameEN": "MR. Somchai Jaidee",
    "senderBank": "KBANK",
    "senderBankCode": "004",
    "receiverBankAccountNumber": "xxxxxxxxx5678",
    "receiverName": "บริษัท ร้านค้าตัวอย่าง จำกัด",
    "receiverNameEN": "SAMPLE STORE COMPANY LTD",
    "receiverBank": "KTB",
    "receiverBankCode": "006",
    "receiverPromptpay": "xxxxxxxxx5678",
    "receiverProxyType": "NATID",
    "rawData": { "...": "verbatim payload from the verification service" }
  },
  "message": "Bank slip QR code verified successfully",
  "qrcode": "0041000600000101030040220015278220959BPP134195102TH9104B31B"
}
```

### Field reference

| Field | Type | Description |
| --- | --- | --- |
| `amount` | number | Transaction amount |
| `dateTime` | string | ISO 8601 date and time with timezone |
| `timestamp` | number | Unix timestamp |
| `transactionId` | string | Unique transaction identifier |
| `senderBankAccountNumber` | string | Masked sender account number |
| `senderName` | string | Sender's name in Thai |
| `senderNameEN` | string | Sender's name in English |
| `senderBank` | string | Sender's bank name |
| `senderBankCode` | string | Sender's bank code |
| `receiverBankAccountNumber` | string | Masked receiver account number |
| `receiverName` | string | Receiver's name in Thai |
| `receiverNameEN` | string | Receiver's name in English |
| `receiverBank` | string | Receiver's bank name |
| `receiverBankCode` | string | Receiver's bank code |
| `receiverPromptpay` | string | Receiver's PromptPay identifier |
| `receiverProxyType` | string | Receiver proxy type, e.g. `NATID` |
| `rawData` | object | Unmodified response from the verification service |
| `message` | string | Human-readable result message |
| `qrcode` | string | Echo of the QR code that was submitted |

Account names and account numbers arrive already masked by the bank network. The API
does not unmask them.

## Errors

Authentication and validation failures use HTTP status codes. Every error body carries
`status: "error"` and a numeric `code`.

| Code | HTTP | `message` | Meaning |
| --- | --- | --- | --- |
| 1020 | 401 | `api key is missing` | No `x-api-key` header was sent |
| 1021 | 401 | `invalid api key` | The key does not match any account |
| 1022 | 400 | `user not found` | The key resolved to no account |
| 1023 | 400 | `insufficient fund` | The account has no credits left |

```json
{
  "status": "error",
  "code": 1023,
  "message": "insufficient fund"
}
```

Important exception: a failure inside the external verification network — service down,
network timeout, malformed QR string — returns **HTTP 200** with an error body and no
`code` field:

```json
{
  "status": "error",
  "message": "verification service timed out"
}
```

Clients must therefore branch on the `status` field, not on the HTTP status code alone.

## Behaviour to account for

- **Credits.** A successful verification decrements the account balance by 1 and
  increments its usage count by 1. Failed verifications are free.
- **Timeout.** Requests to the external verification service time out after 10 seconds,
  after which the API returns the HTTP 200 error shape above.
- **Load balancing.** Each request is dispatched to a randomly chosen upstream domain
  configuration, so response latency varies between calls.
- **Logging.** Successful verifications are stored as `VerifyTransaction` records and
  failed ones as `RequestError` records, for auditing.
- **Idempotency.** None. Sending the same `qrcode` twice performs two verifications and
  consumes two credits.

## Examples

### curl

```bash
curl -X POST {{baseUrl}}/api/v1/verify \
  -H "Content-Type: application/json" \
  -H "x-api-key: your_actual_api_key" \
  -d '{"qrcode": "your_qr_code_data"}'
```

### JavaScript

```javascript
const response = await fetch("{{baseUrl}}/api/v1/verify", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-api-key": "your_actual_api_key",
  },
  body: JSON.stringify({ qrcode: "your_qr_code_data" }),
})

const result = await response.json()
if (result.status !== "success") throw new Error(result.message)
```

### Python

```python
import requests

response = requests.post(
    "{{baseUrl}}/api/v1/verify",
    headers={
        "Content-Type": "application/json",
        "x-api-key": "your_actual_api_key",
    },
    json={"qrcode": "your_qr_code_data"},
    timeout=15,
)

result = response.json()
if result["status"] != "success":
    raise RuntimeError(result["message"])
```

## Postman

A ready-to-import Postman collection covering this endpoint and every error case is
published alongside this file at `/postman_collection.json`. It defines two collection
variables: `baseUrl` and `apiKey`.
