openapi: 3.1.0
info:
  title: Boni Verification API
  version: 1.0.0
  description: >-
    Tenant-scoped Aadhaar, PAN, GSTIN, VAHAN and FASTag verification APIs.
    Production requests use Boni's approved ULIP data rail. Sandbox requests
    are authenticated, deterministic fixtures and never call production data
    sources. Successful production data responses are metered; validation,
    no-record, rate-limit and upstream-failure responses are not billable usage.
servers:
  - url: https://api.boni.one/v1
    description: Production
  - url: https://sandbox.api.boni.one/v1
    description: Authenticated fixture-only sandbox
security:
  - clientId: []
    clientSecret: []
tags:
  - name: Identity
  - name: Business
  - name: Vehicle
  - name: Usage
paths:
  /verify/aadhaar:
    post:
      operationId: verifyAadhaar
      tags: [Identity]
      summary: Start owner-consented Aadhaar verification
      description: >-
        Returns a verified result immediately when consent completes without an
        OTP branch, or status otp_required with a five-minute verification_id.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/AadhaarRequest'}
            examples:
              sandbox:
                value:
                  aadhaar: '999999990019'
                  name: Sandbox User
                  date_of_birth: '1990-01-01'
                  gender: F
                  mobile: '9999999999'
                  consent: true
      responses:
        '200': {$ref: '#/components/responses/Verified'}
        '202': {$ref: '#/components/responses/OtpRequired'}
        '400': {$ref: '#/components/responses/BadRequest'}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '403': {$ref: '#/components/responses/Forbidden'}
        '409': {$ref: '#/components/responses/Conflict'}
        '422': {$ref: '#/components/responses/Rejected'}
        '429': {$ref: '#/components/responses/RateLimited'}
        '503': {$ref: '#/components/responses/Unavailable'}
  /verify/aadhaar/otp:
    post:
      operationId: confirmAadhaarOtp
      tags: [Identity]
      summary: Complete Aadhaar verification with OTP
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/OtpRequest'}
      responses:
        '200': {$ref: '#/components/responses/Verified'}
        '400': {$ref: '#/components/responses/BadRequest'}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '403': {$ref: '#/components/responses/Forbidden'}
        '409': {$ref: '#/components/responses/Conflict'}
        '410': {$ref: '#/components/responses/Expired'}
        '422': {$ref: '#/components/responses/Rejected'}
        '429': {$ref: '#/components/responses/RateLimited'}
        '503': {$ref: '#/components/responses/Unavailable'}
  /verify/pan:
    post:
      operationId: verifyPan
      tags: [Identity]
      summary: Start owner-consented PAN verification
      description: >-
        Retrieves the owner's PAN through the consent flow and compares it with
        the supplied PAN. The response includes match.pan.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/PanRequest'}
            examples:
              sandbox:
                value:
                  pan: ABCDE1234F
                  aadhaar: '999999990019'
                  name: Sandbox User
                  date_of_birth: '1990-01-01'
                  gender: F
                  mobile: '9999999999'
                  consent: true
      responses:
        '200': {$ref: '#/components/responses/Verified'}
        '202': {$ref: '#/components/responses/OtpRequired'}
        '400': {$ref: '#/components/responses/BadRequest'}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '403': {$ref: '#/components/responses/Forbidden'}
        '409': {$ref: '#/components/responses/Conflict'}
        '422': {$ref: '#/components/responses/Rejected'}
        '429': {$ref: '#/components/responses/RateLimited'}
        '503': {$ref: '#/components/responses/Unavailable'}
  /verify/pan/otp:
    post:
      operationId: confirmPanOtp
      tags: [Identity]
      summary: Complete PAN verification with OTP
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/OtpRequest'}
      responses:
        '200': {$ref: '#/components/responses/Verified'}
        '400': {$ref: '#/components/responses/BadRequest'}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '403': {$ref: '#/components/responses/Forbidden'}
        '409': {$ref: '#/components/responses/Conflict'}
        '410': {$ref: '#/components/responses/Expired'}
        '422': {$ref: '#/components/responses/Rejected'}
        '429': {$ref: '#/components/responses/RateLimited'}
        '503': {$ref: '#/components/responses/Unavailable'}
  /verify/gstin:
    post:
      operationId: verifyGstin
      tags: [Business]
      summary: Verify a GSTIN
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/GstinRequest'}
            examples:
              sandbox: {value: {gstin: 22AAAAA0000A1Z5}}
      responses:
        '200': {$ref: '#/components/responses/VerifiedOrNotFound'}
        '400': {$ref: '#/components/responses/BadRequest'}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '403': {$ref: '#/components/responses/Forbidden'}
        '409': {$ref: '#/components/responses/Conflict'}
        '422': {$ref: '#/components/responses/Rejected'}
        '429': {$ref: '#/components/responses/RateLimited'}
        '503': {$ref: '#/components/responses/Unavailable'}
  /verify/vahan:
    post:
      operationId: verifyVahan
      tags: [Vehicle]
      summary: Verify vehicle registration and compliance details
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/VahanRequest'}
            examples:
              sandbox: {value: {vehicle_number: KA01AB1234}}
      responses:
        '200': {$ref: '#/components/responses/VerifiedOrNotFound'}
        '400': {$ref: '#/components/responses/BadRequest'}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '403': {$ref: '#/components/responses/Forbidden'}
        '409': {$ref: '#/components/responses/Conflict'}
        '422': {$ref: '#/components/responses/Rejected'}
        '429': {$ref: '#/components/responses/RateLimited'}
        '503': {$ref: '#/components/responses/Unavailable'}
  /verify/fastag:
    post:
      operationId: verifyFastag
      tags: [Vehicle]
      summary: Get FASTag toll observations from the rolling 72-hour window
      description: >-
        This is a sparse toll-observation feed. It is not GPS tracking and does
        not expose wallet balances, recharge history or debit transactions.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/FastagRequest'}
            examples:
              vehicle: {value: {vehicle_number: KA01AB1234}}
              chassis: {value: {chassis_number: MA1AA2BB3CC444444}}
      responses:
        '200': {$ref: '#/components/responses/VerifiedOrNotFound'}
        '400': {$ref: '#/components/responses/BadRequest'}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '403': {$ref: '#/components/responses/Forbidden'}
        '409': {$ref: '#/components/responses/Conflict'}
        '422': {$ref: '#/components/responses/Rejected'}
        '429': {$ref: '#/components/responses/RateLimited'}
        '503': {$ref: '#/components/responses/Unavailable'}
  /usage:
    get:
      operationId: getVerificationUsage
      tags: [Usage]
      summary: Get metered usage for the authenticated subscription
      parameters:
        - {name: from, in: query, schema: {type: string, format: date-time}}
        - {name: to, in: query, schema: {type: string, format: date-time}}
        - {name: endpoint, in: query, schema: {type: string, enum: [aadhaar, pan, gstin, vahan, fastag]}}
      responses:
        '200':
          description: Usage totals and endpoint/outcome breakdown for up to 31 days
          content:
            application/json:
              schema: {$ref: '#/components/schemas/UsageResponse'}
        '400': {$ref: '#/components/responses/BadRequest'}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '403': {$ref: '#/components/responses/Forbidden'}
components:
  securitySchemes:
    clientId:
      type: apiKey
      in: header
      name: x-boni-client-id
    clientSecret:
      type: apiKey
      in: header
      name: x-boni-client-secret
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: >-
        A unique 8-128 character key for this logical request. Reuse the same
        key only when retrying the same body.
      schema: {type: string, minLength: 8, maxLength: 128, pattern: '^[A-Za-z0-9][A-Za-z0-9._:-]+$'}
  schemas:
    AadhaarRequest:
      type: object
      additionalProperties: false
      required: [aadhaar, name, date_of_birth, gender, mobile, consent]
      properties:
        aadhaar: {type: string, pattern: '^\d{12}$'}
        name: {type: string, minLength: 2, maxLength: 120}
        date_of_birth: {type: string, format: date}
        gender: {type: string, enum: [M, F]}
        mobile: {type: string, pattern: '^\d{10}$'}
        consent: {type: boolean, const: true}
    PanRequest:
      allOf:
        - $ref: '#/components/schemas/AadhaarRequest'
        - type: object
          required: [pan]
          properties:
            pan: {type: string, pattern: '^[A-Z]{5}[0-9]{4}[A-Z]$'}
    OtpRequest:
      type: object
      additionalProperties: false
      required: [verification_id, otp]
      properties:
        verification_id: {type: string, pattern: '^ver_[0-9a-f-]{36}$'}
        otp: {type: string, pattern: '^\d{4,8}$'}
    GstinRequest:
      type: object
      additionalProperties: false
      required: [gstin]
      properties:
        gstin: {type: string, pattern: '^\d{2}[A-Z]{5}\d{4}[A-Z][A-Z\d][A-Z][A-Z\d]$'}
    VahanRequest:
      type: object
      additionalProperties: false
      required: [vehicle_number]
      properties:
        vehicle_number: {type: string, pattern: '^[A-Z0-9]{5,11}$'}
    FastagRequest:
      type: object
      additionalProperties: false
      oneOf:
        - required: [vehicle_number]
          properties:
            vehicle_number: {type: string, pattern: '^[A-Z0-9]{5,11}$'}
        - required: [chassis_number]
          properties:
            chassis_number: {type: string, pattern: '^[A-Z0-9]{17,20}$'}
    VerificationResponse:
      type: object
      required: [success, request_id, environment, endpoint, status, data]
      properties:
        success: {type: boolean, const: true}
        request_id: {type: string}
        environment: {type: string, enum: [sandbox, production]}
        endpoint: {type: string, enum: [aadhaar, pan, gstin, vahan, fastag]}
        status: {type: string, enum: [verified, not_found]}
        match: {type: object, additionalProperties: {type: boolean}}
        data: {type: [object, array, 'null'], additionalProperties: true}
    OtpRequiredResponse:
      type: object
      required: [success, request_id, environment, endpoint, status, verification_id, expires_at]
      properties:
        success: {type: boolean, const: true}
        request_id: {type: string}
        environment: {type: string, enum: [production]}
        endpoint: {type: string, enum: [aadhaar, pan]}
        status: {type: string, const: otp_required}
        verification_id: {type: string}
        expires_at: {type: string, format: date-time}
    ErrorResponse:
      type: object
      required: [success, request_id, error]
      properties:
        success: {type: boolean, const: false}
        request_id: {type: string}
        error:
          type: object
          required: [code, message]
          properties:
            code: {type: string}
            message: {type: string}
            retry_after_seconds: {type: integer, minimum: 1}
            details: {type: object, additionalProperties: true}
    UsageResponse:
      type: object
      required: [success, request_id, data]
      properties:
        success: {type: boolean, const: true}
        request_id: {type: string}
        data:
          type: object
          required: [subscription_id, environment, period, totals, breakdown]
          properties:
            subscription_id: {type: string}
            environment: {type: string, enum: [sandbox, production]}
            period: {type: object, properties: {from: {type: string, format: date-time}, to: {type: string, format: date-time}}}
            totals: {type: object, properties: {requests: {type: integer}, billableUnits: {type: integer}}}
            breakdown: {type: array, items: {type: object, additionalProperties: true}}
  headers:
    RequestId:
      description: Boni request identifier for support and audit correlation
      schema: {type: string}
    RateLimitSecondLimit: {schema: {type: integer}}
    RateLimitSecondRemaining: {schema: {type: integer}}
    RateLimitSecondReset: {schema: {type: string, format: date-time}}
    RateLimitMinuteLimit: {schema: {type: integer}}
    RateLimitMinuteRemaining: {schema: {type: integer}}
    RateLimitMinuteReset: {schema: {type: string, format: date-time}}
    RateLimitHourLimit: {schema: {type: integer}}
    RateLimitHourRemaining: {schema: {type: integer}}
    RateLimitHourReset: {schema: {type: string, format: date-time}}
    RateLimitDayLimit: {schema: {type: integer}}
    RateLimitDayRemaining: {schema: {type: integer}}
    RateLimitDayReset: {schema: {type: string, format: date-time}}
  responses:
    Verified:
      description: Verification completed
      headers:
        X-Request-Id: {$ref: '#/components/headers/RequestId'}
      content:
        application/json:
          schema: {$ref: '#/components/schemas/VerificationResponse'}
    VerifiedOrNotFound:
      description: Verification completed or no matching record was found
      headers:
        X-Request-Id: {$ref: '#/components/headers/RequestId'}
      content:
        application/json:
          schema: {$ref: '#/components/schemas/VerificationResponse'}
    OtpRequired:
      description: Consent flow requires OTP confirmation
      content:
        application/json:
          schema: {$ref: '#/components/schemas/OtpRequiredResponse'}
    BadRequest: {$ref: '#/components/responses/Error'}
    Unauthorized: {$ref: '#/components/responses/Error'}
    Forbidden: {$ref: '#/components/responses/Error'}
    Conflict: {$ref: '#/components/responses/Error'}
    Expired: {$ref: '#/components/responses/Error'}
    Rejected: {$ref: '#/components/responses/Error'}
    Unavailable: {$ref: '#/components/responses/Error'}
    RateLimited:
      description: One configured quota window was exhausted
      headers:
        Retry-After: {schema: {type: integer}}
        X-RateLimit-Second-Limit: {$ref: '#/components/headers/RateLimitSecondLimit'}
        X-RateLimit-Second-Remaining: {$ref: '#/components/headers/RateLimitSecondRemaining'}
        X-RateLimit-Second-Reset: {$ref: '#/components/headers/RateLimitSecondReset'}
        X-RateLimit-Minute-Limit: {$ref: '#/components/headers/RateLimitMinuteLimit'}
        X-RateLimit-Minute-Remaining: {$ref: '#/components/headers/RateLimitMinuteRemaining'}
        X-RateLimit-Minute-Reset: {$ref: '#/components/headers/RateLimitMinuteReset'}
        X-RateLimit-Hour-Limit: {$ref: '#/components/headers/RateLimitHourLimit'}
        X-RateLimit-Hour-Remaining: {$ref: '#/components/headers/RateLimitHourRemaining'}
        X-RateLimit-Hour-Reset: {$ref: '#/components/headers/RateLimitHourReset'}
        X-RateLimit-Day-Limit: {$ref: '#/components/headers/RateLimitDayLimit'}
        X-RateLimit-Day-Remaining: {$ref: '#/components/headers/RateLimitDayRemaining'}
        X-RateLimit-Day-Reset: {$ref: '#/components/headers/RateLimitDayReset'}
      content:
        application/json:
          schema: {$ref: '#/components/schemas/ErrorResponse'}
    Error:
      description: Request failed
      content:
        application/json:
          schema: {$ref: '#/components/schemas/ErrorResponse'}
