openapi: 3.0.3

info:
  title: TrustSol Verification API
  version: 1.0.0
  description: |
    ## Overview

    TrustSol provides identity verification services for Vietnamese consumer lending
    and enterprise use cases. Clients submit data they already hold about a person;
    TrustSol verifies each field against authoritative national databases (BCA/GTel,
    VNeID, BHXH) and returns a field-level `true / false / null` result.

    ## Authentication

    All endpoints (except `/health`) require an API key passed in the request header:

    ```
    X-API-Key: ts_live_xxxxxxxxxxxxxxxxxxxx
    ```

    Click **Authorize** above and enter your API key to test live endpoints.

    **Demo key** (synthetic data, rate-limited to 5 req/min):
    ```
    ts_demo_testkey0000000000
    ```

    ## Billing

    One **check unit** = one field verified in one request.
    A request with 5 fields in `checks` costs 5 units.
    Fields that return `null` (source unavailable) are not billed.

    ## Rate Limits

    | Tier | Requests/min | Requests/day |
    |---|---|---|
    | Demo key | 5 | 100 |
    | Standard | 60 | 50,000 |
    | Enterprise | 300 | Unlimited |

    Rate limit status is returned in every response header:
    `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`

  contact:
    name: TrustSol API Support
    email: api@trustsol.vn
  license:
    name: Proprietary
    url: https://trustsol.vn/terms

servers:
  - url: https://api.trustsol.vn
    description: Production
  - url: https://api-staging.trustsol.vn
    description: Staging (test data, not billed)

tags:
  - name: Verification
    description: Identity and data verification
  - name: Usage
    description: Usage logs and billing summaries
  - name: System
    description: Health and status

security:
  - ApiKeyAuth: []

paths:

  # ─────────────────────────────────────────
  # SYSTEM
  # ─────────────────────────────────────────

  /health:
    get:
      tags: [System]
      summary: Health check
      description: Returns service status and upstream GTel connectivity. No API key required.
      security: []
      operationId: getHealth
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"
              example:
                status: ok
                version: 1.0.0
                timestamp: "2026-02-25T08:30:00Z"
                upstream:
                  gtel: ok
        "503":
          description: One or more upstream services are unavailable
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"
              example:
                status: degraded
                version: 1.0.0
                timestamp: "2026-02-25T08:30:00Z"
                upstream:
                  gtel: unavailable

  # ─────────────────────────────────────────
  # VERIFICATION
  # ─────────────────────────────────────────

  /v1/verify/individual:
    post:
      tags: [Verification]
      summary: Verify individual identity fields
      description: |
        Verifies one or more data fields about an individual against national
        databases (BCA/GTel, VNeID, BHXH Ministry of Labour).

        The client provides a **CCCD number** plus the specific fields they want
        to verify under `checks`. Only the fields included in `checks` are
        queried and billed.

        **Verifiable field groups:**
        - **Identity** — phone ownership, full name, gender, date of birth, ID issue/expiry dates
        - **Social Insurance (BHXH)** — BHXH code, monthly contribution amount
        - **Address (VNeID)** — permanent address, current residential address
      operationId: verifyIndividual
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/IndividualVerifyRequest"
            examples:
              full_kyc:
                summary: Full KYC (all fields)
                value:
                  cccd: "079201012345"
                  checks:
                    phone: "0901234567"
                    full_name: "Nguyen Van A"
                    gender: MALE
                    date_of_birth: "1990-05-15"
                    id_issue_date: "2021-03-10"
                    id_expiry_date: "2031-03-10"
                    social_insurance_code: "7901234567"
                    social_insurance_contribution: 1500000
                    permanent_address: "123 Nguyen Trai, Phuong 2, Quan 5, TP.HCM"
                    current_address: "456 Le Van Sy, Phuong 14, Quan 3, TP.HCM"
                  metadata:
                    client_ref_id: "LOAN-APP-20260225-001"
                    purpose: consumer_lending
              minimal_check:
                summary: Minimal check (phone + name only)
                value:
                  cccd: "079201012345"
                  checks:
                    phone: "0901234567"
                    full_name: "Nguyen Van A"
                  metadata:
                    client_ref_id: "LOAN-APP-20260225-002"
      responses:
        "200":
          description: Verification completed (check individual field results for match status)
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IndividualVerifyResponse"
              example:
                request_id: ts_req_01JKZ8ABCDE
                cccd: "079201012345"
                status: COMPLETED
                results:
                  phone:
                    match: true
                    verified_at: "2026-02-25T08:30:00Z"
                  full_name:
                    match: true
                    verified_at: "2026-02-25T08:30:00Z"
                  gender:
                    match: true
                    verified_at: "2026-02-25T08:30:00Z"
                  date_of_birth:
                    match: true
                    verified_at: "2026-02-25T08:30:00Z"
                  id_issue_date:
                    match: true
                    verified_at: "2026-02-25T08:30:00Z"
                  id_expiry_date:
                    match: false
                    verified_at: "2026-02-25T08:30:00Z"
                  social_insurance_code:
                    match: true
                    verified_at: "2026-02-25T08:30:00Z"
                  social_insurance_contribution:
                    match: false
                    verified_at: "2026-02-25T08:30:00Z"
                  permanent_address:
                    match: true
                    verified_at: "2026-02-25T08:30:00Z"
                  current_address:
                    match: true
                    verified_at: "2026-02-25T08:30:00Z"
                billing:
                  units_consumed: 10
                  unit_type: check
                metadata:
                  client_ref_id: LOAN-APP-20260225-001
        "400":
          description: Bad request — invalid input
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                invalid_cccd:
                  summary: Invalid CCCD
                  value:
                    error:
                      code: INVALID_CCCD
                      message: The provided CCCD number is not valid.
                      request_id: ts_req_01JKZ8XXXXX
                no_checks:
                  summary: No checks provided
                  value:
                    error:
                      code: NO_CHECKS_PROVIDED
                      message: "The 'checks' object must contain at least one field."
                      request_id: ts_req_01JKZ8XXXXX
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimitExceeded"
        "503":
          $ref: "#/components/responses/UpstreamUnavailable"
        "500":
          $ref: "#/components/responses/InternalError"

  # ─────────────────────────────────────────
  # USAGE
  # ─────────────────────────────────────────

  /v1/usage/summary:
    get:
      tags: [Usage]
      summary: Get usage summary
      description: |
        Returns aggregated usage metrics for the authenticated client for a
        given time window. Defaults to the current calendar month.
      operationId: getUsageSummary
      parameters:
        - name: from
          in: query
          description: Start of period (ISO 8601 datetime). Defaults to start of current month.
          schema:
            type: string
            format: date-time
          example: "2026-02-01T00:00:00Z"
        - name: to
          in: query
          description: End of period (ISO 8601 datetime). Defaults to now.
          schema:
            type: string
            format: date-time
          example: "2026-02-25T23:59:59Z"
      responses:
        "200":
          description: Usage summary for the period
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UsageSummaryResponse"
              example:
                client_id: cl_fecredit_prod
                period:
                  from: "2026-02-01T00:00:00Z"
                  to: "2026-02-25T23:59:59Z"
                total_requests: 15230
                total_checks: 87450
                breakdown_by_check_type:
                  phone: 15230
                  full_name: 15230
                  date_of_birth: 15180
                  gender: 14900
                  id_issue_date: 9500
                  id_expiry_date: 9500
                  social_insurance_code: 3210
                  social_insurance_contribution: 1800
                  permanent_address: 1800
                  current_address: 1300
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"

  /v1/usage/transactions:
    get:
      tags: [Usage]
      summary: Get transaction log
      description: |
        Returns a paginated list of individual API calls made by the
        authenticated client. Useful for reconciliation and audit.
      operationId: getUsageTransactions
      parameters:
        - name: from
          in: query
          description: Start of period (ISO 8601 datetime). Defaults to start of current month.
          schema:
            type: string
            format: date-time
        - name: to
          in: query
          description: End of period (ISO 8601 datetime). Defaults to now.
          schema:
            type: string
            format: date-time
        - name: page
          in: query
          schema:
            type: integer
            minimum: 1
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
      responses:
        "200":
          description: Paginated transaction log
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TransactionLogResponse"
              example:
                page: 1
                limit: 50
                total: 15230
                transactions:
                  - request_id: ts_req_01JKZ8ABCDE
                    client_ref_id: LOAN-APP-20260225-001
                    requested_at: "2026-02-25T08:30:00Z"
                    status: COMPLETED
                    checks_requested: 10
                    checks_succeeded: 9
                    checks_billed: 10
                    latency_ms: 320
                  - request_id: ts_req_01JKZ8FGHIJ
                    client_ref_id: LOAN-APP-20260225-002
                    requested_at: "2026-02-25T08:31:15Z"
                    status: COMPLETED
                    checks_requested: 2
                    checks_succeeded: 2
                    checks_billed: 2
                    latency_ms: 218
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"

# ─────────────────────────────────────────────────────────────────────────────
# COMPONENTS
# ─────────────────────────────────────────────────────────────────────────────

components:

  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: |
        API key issued by TrustSol. Contact api@trustsol.vn to obtain a key.

        **Demo key for testing:** `ts_demo_testkey0000000000`

  # ── Headers ──────────────────────────────

  headers:
    X-RateLimit-Limit:
      description: Maximum number of requests allowed per minute for this key.
      schema:
        type: integer
      example: 300
    X-RateLimit-Remaining:
      description: Number of requests remaining in the current rate-limit window.
      schema:
        type: integer
      example: 287
    X-RateLimit-Reset:
      description: Unix timestamp (seconds) when the rate-limit window resets.
      schema:
        type: integer
      example: 1740472200

  # ── Reusable Responses ───────────────────

  responses:
    Unauthorized:
      description: API key missing or invalid
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          examples:
            missing_key:
              summary: Missing API key
              value:
                error:
                  code: MISSING_API_KEY
                  message: The X-API-Key header is required.
                  request_id: ts_req_01JKZ8XXXXX
            invalid_key:
              summary: Invalid API key
              value:
                error:
                  code: INVALID_API_KEY
                  message: The provided API key is not recognized or has been revoked.
                  request_id: ts_req_01JKZ8XXXXX

    Forbidden:
      description: The API key does not have permission to use this endpoint or check type
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            error:
              code: SCOPE_NOT_PERMITTED
              message: Your API key does not have access to social_insurance checks.
              request_id: ts_req_01JKZ8XXXXX

    RateLimitExceeded:
      description: Rate limit exceeded
      headers:
        Retry-After:
          description: Seconds to wait before retrying
          schema:
            type: integer
          example: 42
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            error:
              code: RATE_LIMIT_EXCEEDED
              message: Too many requests. Retry after 42 seconds.
              request_id: ts_req_01JKZ8XXXXX

    UpstreamUnavailable:
      description: GTel / BCA upstream service is temporarily unavailable
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            error:
              code: UPSTREAM_UNAVAILABLE
              message: The upstream verification service is temporarily unavailable. Please retry in a few minutes.
              request_id: ts_req_01JKZ8XXXXX

    InternalError:
      description: Unexpected server error
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            error:
              code: INTERNAL_ERROR
              message: An unexpected error occurred. Our team has been notified.
              request_id: ts_req_01JKZ8XXXXX

  # ── Schemas ──────────────────────────────

  schemas:

    # ── Request ──

    IndividualVerifyRequest:
      type: object
      required:
        - cccd
        - checks
      properties:
        cccd:
          type: string
          description: 12-digit Vietnamese Citizen ID number (CCCD).
          pattern: '^\d{12}$'
          example: "079201012345"
        checks:
          $ref: "#/components/schemas/VerificationChecks"
        metadata:
          $ref: "#/components/schemas/RequestMetadata"

    VerificationChecks:
      type: object
      description: |
        Fields to verify. Include only the fields you want to check — each
        included field is one billable unit. At least one field is required.
      minProperties: 1
      properties:
        phone:
          type: string
          description: Vietnamese mobile phone number to verify as belonging to the CCCD holder.
          pattern: '^(0|\+84)[0-9]{9}$'
          example: "0901234567"
        full_name:
          type: string
          description: Full legal name as it appears on the national ID.
          example: "Nguyen Van A"
        gender:
          type: string
          enum: [MALE, FEMALE]
          description: Gender as recorded on the national ID.
          example: MALE
        date_of_birth:
          type: string
          format: date
          description: Date of birth (ISO 8601 `YYYY-MM-DD`).
          example: "1990-05-15"
        id_issue_date:
          type: string
          format: date
          description: Date the CCCD was issued (ISO 8601 `YYYY-MM-DD`).
          example: "2021-03-10"
        id_expiry_date:
          type: string
          format: date
          description: Expiry date of the CCCD (ISO 8601 `YYYY-MM-DD`).
          example: "2031-03-10"
        social_insurance_code:
          type: string
          description: 10-digit Social Insurance (BHXH) code.
          pattern: '^\d{10}$'
          example: "7901234567"
        social_insurance_contribution:
          type: integer
          description: Monthly social insurance contribution amount (VND).
          example: 1500000
        permanent_address:
          type: string
          description: Registered permanent address as recorded in VNeID.
          example: "123 Nguyen Trai, Phuong 2, Quan 5, TP.HCM"
        current_address:
          type: string
          description: Current residential address as recorded in VNeID.
          example: "456 Le Van Sy, Phuong 14, Quan 3, TP.HCM"

    RequestMetadata:
      type: object
      description: Optional client-supplied context. Echoed back in the response and stored in the transaction log.
      properties:
        client_ref_id:
          type: string
          description: Your internal reference ID (e.g., loan application number).
          maxLength: 128
          example: "LOAN-APP-20260225-001"
        purpose:
          type: string
          description: Intended use of the verification (logged for compliance).
          example: "consumer_lending"

    # ── Response ──

    IndividualVerifyResponse:
      type: object
      properties:
        request_id:
          type: string
          description: Unique TrustSol request identifier.
          example: "ts_req_01JKZ8ABCDE"
        cccd:
          type: string
          example: "079201012345"
        status:
          type: string
          enum: [COMPLETED, PARTIAL, FAILED]
          description: |
            - `COMPLETED` — all requested checks were processed (individual results may still be false or null)
            - `PARTIAL` — some checks could not be performed due to upstream issues
            - `FAILED` — no checks could be performed
          example: COMPLETED
        results:
          type: object
          description: Field-level verification results. Only fields that were included in the request appear here.
          properties:
            phone:
              $ref: "#/components/schemas/VerificationResult"
            full_name:
              $ref: "#/components/schemas/VerificationResult"
            gender:
              $ref: "#/components/schemas/VerificationResult"
            date_of_birth:
              $ref: "#/components/schemas/VerificationResult"
            id_issue_date:
              $ref: "#/components/schemas/VerificationResult"
            id_expiry_date:
              $ref: "#/components/schemas/VerificationResult"
            social_insurance_code:
              $ref: "#/components/schemas/VerificationResult"
            social_insurance_contribution:
              $ref: "#/components/schemas/VerificationResult"
            permanent_address:
              $ref: "#/components/schemas/VerificationResult"
            current_address:
              $ref: "#/components/schemas/VerificationResult"
        billing:
          $ref: "#/components/schemas/BillingSummary"
        metadata:
          type: object
          properties:
            client_ref_id:
              type: string
              example: "LOAN-APP-20260225-001"

    VerificationResult:
      type: object
      properties:
        match:
          nullable: true
          description: |
            - `true` — provided value matches the authoritative record
            - `false` — provided value does NOT match
            - `null` — could not determine (source unavailable or record not found); not billed
          example: true
        verified_at:
          type: string
          format: date-time
          description: Timestamp when this check was completed.
          example: "2026-02-25T08:30:00Z"

    BillingSummary:
      type: object
      properties:
        units_consumed:
          type: integer
          description: Number of billable check units for this request.
          example: 10
        unit_type:
          type: string
          enum: [check]
          example: check

    # ── Usage ──

    UsageSummaryResponse:
      type: object
      properties:
        client_id:
          type: string
          example: cl_fecredit_prod
        period:
          type: object
          properties:
            from:
              type: string
              format: date-time
              example: "2026-02-01T00:00:00Z"
            to:
              type: string
              format: date-time
              example: "2026-02-25T23:59:59Z"
        total_requests:
          type: integer
          example: 15230
        total_checks:
          type: integer
          description: Total billable check units consumed in the period.
          example: 87450
        breakdown_by_check_type:
          type: object
          description: Billable units consumed per check type.
          additionalProperties:
            type: integer
          example:
            phone: 15230
            full_name: 15230
            date_of_birth: 15180
            gender: 14900
            id_issue_date: 9500
            id_expiry_date: 9500
            social_insurance_code: 3210
            social_insurance_contribution: 1800
            permanent_address: 1800
            current_address: 1300

    TransactionLogResponse:
      type: object
      properties:
        page:
          type: integer
          example: 1
        limit:
          type: integer
          example: 50
        total:
          type: integer
          description: Total number of transactions matching the query.
          example: 15230
        transactions:
          type: array
          items:
            $ref: "#/components/schemas/TransactionRecord"

    TransactionRecord:
      type: object
      properties:
        request_id:
          type: string
          example: ts_req_01JKZ8ABCDE
        client_ref_id:
          type: string
          nullable: true
          example: LOAN-APP-20260225-001
        requested_at:
          type: string
          format: date-time
          example: "2026-02-25T08:30:00Z"
        status:
          type: string
          enum: [COMPLETED, PARTIAL, FAILED]
          example: COMPLETED
        checks_requested:
          type: integer
          example: 10
        checks_succeeded:
          type: integer
          description: Number of checks that returned true or false (not null).
          example: 9
        checks_billed:
          type: integer
          description: Number of billable units for this request (null results excluded).
          example: 10
        latency_ms:
          type: integer
          description: End-to-end request latency in milliseconds.
          example: 320

    # ── Health ──

    HealthResponse:
      type: object
      properties:
        status:
          type: string
          enum: [ok, degraded, down]
          example: ok
        version:
          type: string
          example: 1.0.0
        timestamp:
          type: string
          format: date-time
          example: "2026-02-25T08:30:00Z"
        upstream:
          type: object
          properties:
            gtel:
              type: string
              enum: [ok, unavailable]
              example: ok

    # ── Error ──

    ErrorResponse:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              description: Machine-readable error code.
              enum:
                - INVALID_CCCD
                - NO_CHECKS_PROVIDED
                - INVALID_FIELD_FORMAT
                - MISSING_API_KEY
                - INVALID_API_KEY
                - SCOPE_NOT_PERMITTED
                - RATE_LIMIT_EXCEEDED
                - UPSTREAM_UNAVAILABLE
                - INTERNAL_ERROR
              example: INVALID_CCCD
            message:
              type: string
              description: Human-readable description of the error.
              example: The provided CCCD number is not valid.
            request_id:
              type: string
              description: TrustSol request ID for support reference.
              example: ts_req_01JKZ8XXXXX
