# Boni Verification API

Verify Aadhaar, PAN, GSTIN, vehicle records and recent FASTag toll observations through one simple tenant-scoped API.

## Start here

Boni gives you a separate client ID and client secret for each activated environment. Use the sandbox while integrating; it returns fixed test records and never calls production data sources.

| Environment | Base URL |
|---|---|
| Sandbox | `https://sandbox.api.boni.one/v1` |
| Production | `https://api.boni.one/v1` |

Send these headers on every request:

```http
x-boni-client-id: YOUR_CLIENT_ID
x-boni-client-secret: YOUR_CLIENT_SECRET
Content-Type: application/json
Idempotency-Key: a-unique-key-for-this-request
```

Keep credentials on your server. Do not put them in browser code, mobile apps, URLs or public repositories. Reuse an `Idempotency-Key` only when retrying the exact same request body.

## First request

```bash
curl https://sandbox.api.boni.one/v1/verify/gstin \
  -X POST \
  -H 'x-boni-client-id: YOUR_SANDBOX_CLIENT_ID' \
  -H 'x-boni-client-secret: YOUR_SANDBOX_CLIENT_SECRET' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: gstin-demo-0001' \
  -d '{"gstin":"22AAAAA0000A1Z5"}'
```

```json
{
  "success": true,
  "request_id": "req_...",
  "environment": "sandbox",
  "endpoint": "gstin",
  "status": "verified",
  "data": {
    "gstin": "22AAAAA0000A1Z5",
    "legal_name": "BONI SANDBOX PRIVATE LIMITED",
    "trade_name": "BONI SANDBOX",
    "status": "ACTIVE",
    "taxpayer_type": "Regular",
    "registration_date": "2019-07-01",
    "e_invoice_enabled": true,
    "principal_place": {"state": "Chhattisgarh", "pincode": "492001"},
    "sandbox": true
  }
}
```

## Endpoints and input

Every verification is a `POST`. Unknown fields are rejected. Spaces and hyphens in vehicle identifiers are normalized.

| What | Endpoint | JSON input |
|---|---|---|
| Aadhaar | `/verify/aadhaar` | `aadhaar`, `name`, `date_of_birth`, `gender`, `mobile`, `consent` |
| PAN | `/verify/pan` | `pan` plus all Aadhaar input fields |
| GSTIN | `/verify/gstin` | `gstin` |
| Vehicle | `/verify/vahan` | `vehicle_number` |
| FASTag | `/verify/fastag` | exactly one of `vehicle_number` or `chassis_number` |

`date_of_birth` uses `YYYY-MM-DD`, `gender` is `M` or `F`, and `consent` must be `true`. Aadhaar and PAN requests must be made with the document owner's consent.

## Sandbox test values

| Field | Value |
|---|---|
| Aadhaar | `999999990019` |
| PAN | `ABCDE1234F` |
| GSTIN | `22AAAAA0000A1Z5` |
| Vehicle number | `KA01AB1234` |
| Chassis number | `MA1AA2BB3CC444444` |
| Name | `Sandbox User` |
| Date of birth | `1990-01-01` |
| Mobile | `9999999999` |

Sandbox Aadhaar and PAN finish immediately and do not require an OTP. Other valid-looking sandbox values return `status: not_found` so you can test that branch.

## Aadhaar input and output

```json
{
  "aadhaar": "999999990019",
  "name": "Sandbox User",
  "date_of_birth": "1990-01-01",
  "gender": "F",
  "mobile": "9999999999",
  "consent": true
}
```

A successful response uses the common envelope and returns the complete approved data payload:

```json
{
  "success": true,
  "request_id": "req_...",
  "environment": "sandbox",
  "endpoint": "aadhaar",
  "status": "verified",
  "data": {
    "aadhaar": "999999990019",
    "name": "Sandbox User",
    "date_of_birth": "1990-01-01",
    "gender": "F",
    "status": "VERIFIED",
    "sandbox": true
  }
}
```

Production `data` preserves the complete provider data structure. Boni removes only upstream authentication credentials and tokens, which are Boni platform secrets rather than verification data.

## PAN input and output

```json
{
  "pan": "ABCDE1234F",
  "aadhaar": "999999990019",
  "name": "Sandbox User",
  "date_of_birth": "1990-01-01",
  "gender": "F",
  "mobile": "9999999999",
  "consent": true
}
```

```json
{
  "success": true,
  "request_id": "req_...",
  "environment": "sandbox",
  "endpoint": "pan",
  "status": "verified",
  "match": {"pan": true},
  "data": {
    "pan": "ABCDE1234F",
    "name": "SANDBOX USER",
    "status": "VALID",
    "category": "Individual",
    "sandbox": true
  }
}
```

## Vehicle input and output

```json
{"vehicle_number": "KA01AB1234"}
```

```json
{
  "success": true,
  "request_id": "req_...",
  "environment": "sandbox",
  "endpoint": "vahan",
  "status": "verified",
  "data": {
    "registration_number": "KA01AB1234",
    "registration_date": "2022-04-18",
    "owner_name": "SANDBOX OWNER",
    "vehicle_class": "Goods Carrier",
    "fuel_type": "DIESEL",
    "maker_model": "BONI SANDBOX TRUCK",
    "registration_status": "ACTIVE",
    "fitness_upto": "2027-04-17",
    "insurance_upto": "2027-01-31",
    "pucc_upto": "2026-12-31",
    "sandbox": true
  }
}
```

## FASTag input and output

Use either a vehicle number:

```json
{"vehicle_number": "KA01AB1234"}
```

or a chassis number:

```json
{"chassis_number": "MA1AA2BB3CC444444"}
```

```json
{
  "success": true,
  "request_id": "req_...",
  "environment": "sandbox",
  "endpoint": "fastag",
  "status": "verified",
  "data": {
    "vehicle_number": "KA01AB1234",
    "lookback_hours": 72,
    "events": [
      {"plaza": "Boni Sandbox Toll Plaza A", "event_time": "2026-08-16T08:30:00+05:30", "direction": "EAST"}
    ],
    "sandbox": true
  }
}
```

FASTag is a sparse toll-observation feed for the rolling 72-hour window. It is not GPS tracking and does not return wallet balances, recharges or debit transactions. An empty observation result is a valid result.

## Production OTP flow

Production Aadhaar or PAN may return HTTP `202` with an OTP challenge:

```json
{
  "success": true,
  "request_id": "req_...",
  "environment": "production",
  "endpoint": "aadhaar",
  "status": "otp_required",
  "verification_id": "ver_...",
  "expires_at": "2026-08-17T12:05:00.000Z"
}
```

Send the received OTP within five minutes to `/verify/aadhaar/otp` or `/verify/pan/otp` with a new idempotency key:

```json
{"verification_id": "ver_...", "otp": "123456"}
```

A challenge allows at most three attempts and is bound to the same client, subscription and environment.

## Common results

| HTTP | Meaning | What to do |
|---|---|---|
| `200` + `verified` | Data returned | Use `data` and keep `request_id` for support |
| `200` + `not_found` | No matching record | Treat as a normal no-record result |
| `202` + `otp_required` | Owner must provide OTP | Call the matching `/otp` endpoint |
| `400` | Invalid or unknown input | Fix the request; do not retry unchanged |
| `401` | Missing or invalid credentials | Check the client ID and secret |
| `403` | Environment, scope or endpoint not enabled | Use the correct credential or ask Boni to activate access |
| `409` | Idempotency or session conflict | Reuse a key only with its original body |
| `422` | Provider rejected the request | Check consent/input and follow the returned code |
| `429` | A quota window is full | Wait for `Retry-After` or the indicated reset |
| `503` or `504` | Temporary service/provider issue | Retry the same body with the same idempotency key |

Errors use one shape:

```json
{
  "success": false,
  "request_id": "req_...",
  "error": {"code": "rate_limit_exceeded", "message": "The minute request limit has been reached.", "retry_after_seconds": 12}
}
```

Quota headers report the configured second, minute, hour and day limits, remaining calls and reset times. Limits are assigned to your subscription and can differ by environment or endpoint.

## Usage

Call `GET /usage` with the same environment credential and the `verification.usage.read` scope. Optional query parameters are `from`, `to`, and `endpoint`; a query can cover up to 31 days. The response separates total requests from metered production units and groups them by endpoint and outcome. Sandbox calls, validation failures, no-record results, rate-limit responses and upstream failures have zero billable units.

## OpenAPI

Download the complete [Boni Verification API V1 OpenAPI file](/openapi/boni-verification-api-v1.openapi.yaml) for schemas, response types and code generation.

Canonical: https://docs.boni.one/developers/verification-api
Version: 1
Updated: 2026-08-17T14:25:34.771Z
