openapi: 3.0.3
info:
  title: ManTech Public API v1
  version: '1.0'
  description: |
    Canonical machine-readable contract for developer-consumable public APIs.

    Covered operations: IFSC lookup/search, GST calculation, HSN lookup/search,
    SAC lookup/search.

    This document describes **public data APIs**. Authentication may be a
    ManTech API key **or** an OAuth 2.0 access token from
    `POST /oauth/token` (client credentials). Use one credential, not both.
    Developer management endpoints (`/api/v1/developer/**`) use Firebase ID
    tokens and are not part of this specification. The token endpoint is
    documented separately from this public resource contract.

    v1 is the current public API version. Breaking changes require a new major
    version. Non-breaking additions may occur within v1. No `/v2` or support
    lifetime is promised here.

    **Credential environments:** `test` and `live` are bound to the API key.
    The same `/api/v1` base URL serves both. Environment is never selected by
    path, query, body, or `X-Environment`. For current IFSC/GST/HSN/SAC
    operations, domain results are the same; quota windows are separate.

    **Deployment:** These endpoints are implemented in this repository. Public
    Cloud Functions for `/api/v1` are not yet deployed. `https://mantechstudio.in`
    is the intended production host, not a claim that the APIs are currently live.
  contact:
    name: ManTech Studio
    url: https://mantechstudio.in/developers/docs
servers:
  - url: https://mantechstudio.in
    description: Intended production host. Public v1 functions are not yet deployed.
  - url: /
    description: Same-origin relative base (local hosting / emulator when configured).
tags:
  - name: IFSC
    description: Indian Financial System Code lookup and bounded bank-code search.
  - name: GST
    description: Deterministic GST calculation from supplied inputs. Not GSTN or filing.
  - name: HSN
    description: Curated HSN goods catalog lookup and bounded search. Not a full CBIC schedule.
  - name: SAC
    description: Curated SAC (Chapter 99) catalog lookup and bounded search.
security:
  - BearerAuth: []
  - ApiKeyHeader: []
paths:
  /api/v1/ifsc/{code}:
    get:
      operationId: getIfsc
      security:
        - BearerAuth: []
        - ApiKeyHeader: []
        - OAuth2:
            - ifsc.read
      tags: [IFSC]
      summary: Look up one IFSC
      description: |
        Exact lookup of an Indian Financial System Code.

        Normalization: URI-decode the path segment, trim, uppercase, then validate
        `[A-Z]{4}0[A-Z0-9]{6}`. No fuzzy correction.

        400 = malformed format. 404 = valid format but absent from the imported dataset.

        Dataset: razorpay/ifsc by-bank import, vintage 2026-07-15 / v2.0.61.
        Manual refresh. Not a live RBI feed.
      parameters:
        - $ref: '#/components/parameters/IfscCode'
      responses:
        '200':
          description: Public IFSC resource
          headers:
            X-Request-Id:
              $ref: '#/components/headers/X-Request-Id'
            ETag:
              $ref: '#/components/headers/ETag'
            Cache-Control:
              $ref: '#/components/headers/CacheControlPrivateHour'
            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/IfscResource'
              example:
                ifsc: SBIN0000691
                bank: STATE BANK OF INDIA
                bankCode: SBIN
                branch: Parliament Street
                address: '11, Parliament Street, New Delhi'
                city: NEW DELHI
                district: NEW DELHI
                state: DELHI
                micr: '110002001'
                contact: 011-23374000
                supports:
                  neft: true
                  rtgs: true
                  imps: true
                  upi: true
                bankName: STATE BANK OF INDIA
                neft: true
                rtgs: true
                imps: true
                upi: true
        '304':
          $ref: '#/components/responses/NotModified'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/DailyQuotaExceeded'
        '500':
          $ref: '#/components/responses/InternalError'
  /api/v1/ifsc/search:
    get:
      operationId: searchIfsc
      security:
        - BearerAuth: []
        - ApiKeyHeader: []
        - OAuth2:
            - ifsc.search
      tags: [IFSC]
      summary: Search IFSC records by bank code
      description: |
        Bounded search. Query parameter `bank` is preferred (4-character bank code).
        `bankCode` is a compatibility alias. Optional `state` and `city` filters
        (max 128 characters). Results are capped at 100.

        This is a certified v1 route. The Developer Playground currently exposes
        IFSC lookup only.
      parameters:
        - name: bank
          in: query
          required: false
          description: Preferred 4-character bank code (e.g. SBIN). Required unless bankCode is sent.
          schema:
            type: string
            pattern: '^[A-Za-z0-9]{4}$'
            example: SBIN
        - name: bankCode
          in: query
          required: false
          description: Compatibility alias for bank.
          schema:
            type: string
            pattern: '^[A-Za-z0-9]{4}$'
        - name: state
          in: query
          required: false
          schema:
            type: string
            maxLength: 128
        - name: city
          in: query
          required: false
          schema:
            type: string
            maxLength: 128
      responses:
        '200':
          description: Search envelope
          headers:
            X-Request-Id:
              $ref: '#/components/headers/X-Request-Id'
            Cache-Control:
              $ref: '#/components/headers/CacheControlPrivateHour'
            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/IfscSearchResponse'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/DailyQuotaExceeded'
        '500':
          $ref: '#/components/responses/InternalError'
  /api/v1/gst/calculate:
    post:
      operationId: calculateGst
      security:
        - BearerAuth: []
        - ApiKeyHeader: []
        - OAuth2:
            - gst.calculate
      tags: [GST]
      summary: Calculate GST for a single amount
      description: |
        Deterministic calculation from supplied inputs. `taxRate` is a percent
        (`18` means 18%, not 0.18). Supported range 0–100.

        `amount` is rupees (number or decimal string, max 10,000,000,000, max 2
        decimal places). Output money fields are decimal strings with 2 places.

        `pricingMode` defaults to `exclusive` (amount = taxable value).
        `inclusive` treats amount as gross including GST.

        `supplyType` defaults to `intra_state` (CGST + SGST). `inter_state` uses IGST.
        Optional seller/buyer state codes, when both present, set supply type by
        equality. That is not full place-of-supply law.

        Unknown JSON fields are rejected. Request body max size is 32 KiB.
        Content-Type must be application/json.

        This API does not file GST returns, connect to GSTN, generate e-invoices,
        apply exemptions, or compute cess.
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GstCalculationRequest'
            example:
              amount: '1000.00'
              taxRate: 18
              pricingMode: exclusive
              supplyType: intra_state
      responses:
        '200':
          description: GST calculation result
          headers:
            X-Request-Id:
              $ref: '#/components/headers/X-Request-Id'
            Cache-Control:
              $ref: '#/components/headers/CacheControlNoStore'
            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/GstCalculationResult'
              example:
                currency: INR
                pricingMode: exclusive
                supplyType: intra_state
                determinationMethod: explicit
                taxRate: 18
                inputAmount: '1000.00'
                taxableAmount: '1000.00'
                cgst: '90.00'
                sgst: '90.00'
                igst: '0.00'
                totalTax: '180.00'
                totalAmount: '1180.00'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '415':
          description: Content-Type must be application/json
          headers:
            X-Request-Id:
              $ref: '#/components/headers/X-Request-Id'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
        '429':
          $ref: '#/components/responses/DailyQuotaExceeded'
        '500':
          $ref: '#/components/responses/InternalError'
  /api/v1/hsn/{code}:
    get:
      operationId: getHsn
      security:
        - BearerAuth: []
        - ApiKeyHeader: []
        - OAuth2:
            - hsn.read
      tags: [HSN]
      summary: Look up an HSN goods code
      description: |
        Exact catalog lookup. Codes are strings (2 / 4 / 6 / 8 digits, not Chapter 99).
        Normalization: trim; spaces and hyphens removed; no number cast, padding,
        truncation, or fuzzy correction. Leading zeros are significant.

        400 = invalid format. 404 = well-formed code absent from the curated catalog
        (52-row ManTech catalog, dataset id mantech-hsn-sac-catalog-2026.08).

        `gstRate` is the value stored in that catalog, not live tax law.
      parameters:
        - $ref: '#/components/parameters/HsnCode'
      responses:
        '200':
          description: HSN resource
          headers:
            X-Request-Id:
              $ref: '#/components/headers/X-Request-Id'
            ETag:
              $ref: '#/components/headers/ETag'
            Cache-Control:
              $ref: '#/components/headers/CacheControlPrivateHour'
            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/HsnResource'
              example:
                type: hsn
                code: '84713010'
                description: Personal computers (laptops, notebooks, palmtops)
                chapter: '84'
                heading: '8471'
                gstRate: 18
        '304':
          $ref: '#/components/responses/NotModified'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/DailyQuotaExceeded'
        '500':
          $ref: '#/components/responses/InternalError'
  /api/v1/hsn/search:
    get:
      operationId: searchHsn
      security:
        - BearerAuth: []
        - ApiKeyHeader: []
        - OAuth2:
            - hsn.search
      tags: [HSN]
      summary: Search curated HSN goods codes
      description: |
        Bounded deterministic token/substring search over the ManTech catalog.
        Not AI, not LLM, not semantic search, not a confidence ranker.

        `q` is required (2–100 characters). `query` is a compatibility alias.
        Optional `limit` is capped at 20 (invalid or omitted values use 20).
      parameters:
        - $ref: '#/components/parameters/SearchQueryQ'
        - $ref: '#/components/parameters/SearchQueryAlias'
        - $ref: '#/components/parameters/SearchLimit'
      responses:
        '200':
          description: Search results
          headers:
            X-Request-Id:
              $ref: '#/components/headers/X-Request-Id'
            Cache-Control:
              $ref: '#/components/headers/CacheControlPrivateMinute'
            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/ClassificationSearchResult'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/DailyQuotaExceeded'
        '500':
          $ref: '#/components/responses/InternalError'
  /api/v1/sac/{code}:
    get:
      operationId: getSac
      security:
        - BearerAuth: []
        - ApiKeyHeader: []
        - OAuth2:
            - sac.read
      tags: [SAC]
      summary: Look up an SAC service code
      description: |
        Exact catalog lookup. Codes are strings: 4 or 6 digits starting with 99.
        Same normalization as HSN (trim; spaces/hyphens removed; no padding or fuzzy correction).

        400 = invalid format. 404 = well-formed code absent from the curated catalog.
      parameters:
        - $ref: '#/components/parameters/SacCode'
      responses:
        '200':
          description: SAC resource
          headers:
            X-Request-Id:
              $ref: '#/components/headers/X-Request-Id'
            ETag:
              $ref: '#/components/headers/ETag'
            Cache-Control:
              $ref: '#/components/headers/CacheControlPrivateHour'
            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/SacResource'
              example:
                type: sac
                code: '998314'
                description: Information technology (IT) design and development services (custom software, mobile apps, web applications)
                chapter: '99'
                gstRate: 18
        '304':
          $ref: '#/components/responses/NotModified'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/DailyQuotaExceeded'
        '500':
          $ref: '#/components/responses/InternalError'
  /api/v1/sac/search:
    get:
      operationId: searchSac
      security:
        - BearerAuth: []
        - ApiKeyHeader: []
        - OAuth2:
            - sac.search
      tags: [SAC]
      summary: Search curated SAC service codes
      description: |
        Bounded deterministic token/substring search over the ManTech Chapter 99 catalog.
        Same query rules as HSN search (`q` required, 2–100 chars, max 20 results).
      parameters:
        - $ref: '#/components/parameters/SearchQueryQ'
        - $ref: '#/components/parameters/SearchQueryAlias'
        - $ref: '#/components/parameters/SearchLimit'
      responses:
        '200':
          description: Search results
          headers:
            X-Request-Id:
              $ref: '#/components/headers/X-Request-Id'
            Cache-Control:
              $ref: '#/components/headers/CacheControlPrivateMinute'
            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/ClassificationSearchResult'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '429':
          $ref: '#/components/responses/DailyQuotaExceeded'
        '500':
          $ref: '#/components/responses/InternalError'
components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: ManTech API key or OAuth access token
      description: |
        Recommended. Send either a ManTech API key
        (`mantech_test_<key-id>.<secret>` / `mantech_live_<key-id>.<secret>`)
        or a short-lived OAuth access token (`mat_…`) obtained from
        `POST /oauth/token`. This is not a Firebase ID token.
        Documentation examples use
        `mantech_test_0123456789abcdef.example_secret`. Prefer TEST keys for
        development. A test key is still a secret. Access tokens are also secrets.
    ApiKeyHeader:
      type: apiKey
      in: header
      name: X-API-Key
      description: |
        Supported compatibility header for API keys only. Send the same secret
        as the Bearer API key (without the "Bearer " prefix). OAuth access
        tokens are not accepted on this header. Prefer Authorization: Bearer.
    OAuth2:
      type: oauth2
      description: |
        Machine-to-machine client credentials. Token endpoint
        `POST /oauth/token` authenticates the OAuth client with HTTP Basic.
        No refresh tokens. No authorization-code or user consent. Not deployed.
        Scopes match server capabilities. Requested scopes must be a subset of
        the client's allow-list.
      flows:
        clientCredentials:
          tokenUrl: /oauth/token
          scopes:
            ifsc.read: Look up one IFSC
            ifsc.search: Search IFSC records by bank code
            gst.calculate: Calculate GST from supplied inputs
            hsn.read: Look up one HSN code
            hsn.search: Search the HSN catalog
            sac.read: Look up one SAC code
            sac.search: Search the SAC catalog
  parameters:
    IfscCode:
      name: code
      in: path
      required: true
      description: 11-character IFSC (path parameter name is `code`).
      schema:
        type: string
        pattern: '^[A-Za-z]{4}0[A-Za-z0-9]{6}$'
        example: SBIN0000691
    HsnCode:
      name: code
      in: path
      required: true
      description: HSN goods code as a string (2, 4, 6, or 8 digits).
      schema:
        type: string
        example: '84713010'
    SacCode:
      name: code
      in: path
      required: true
      description: SAC service code as a string (4 or 6 digits starting with 99).
      schema:
        type: string
        example: '998314'
    SearchQueryQ:
      name: q
      in: query
      required: true
      description: Search query. 2–100 characters after trim.
      schema:
        type: string
        minLength: 2
        maxLength: 100
        example: laptop
    SearchQueryAlias:
      name: query
      in: query
      required: false
      description: Compatibility alias for q. Prefer q.
      schema:
        type: string
        minLength: 2
        maxLength: 100
    SearchLimit:
      name: limit
      in: query
      required: false
      description: Requested result cap. Invalid or omitted values use 20. Hard cap 20.
      schema:
        type: integer
        minimum: 1
        maximum: 20
        default: 20
  headers:
    X-Request-Id:
      description: Server-issued request identifier. Also present on the error envelope as error.request_id. Retain it when reporting a failed request. Not distributed tracing.
      schema:
        type: string
        example: req_0123456789abcdef0123456789abcdef
    X-RateLimit-Limit:
      description: Daily request quota for this application and environment. Not a burst limiter.
      schema:
        type: integer
        example: 1000
    X-RateLimit-Remaining:
      description: Remaining units after this admitted request, or 0 when the quota is exceeded. Never negative.
      schema:
        type: integer
        minimum: 0
    X-RateLimit-Reset:
      description: Unix epoch seconds at the end of the current UTC calendar day.
      schema:
        type: integer
    Retry-After:
      description: Seconds until the current UTC quota window ends.
      schema:
        type: integer
        minimum: 1
    ETag:
      description: Entity tag for conditional GET. Send If-None-Match to receive 304 when unchanged.
      schema:
        type: string
    CacheControlPrivateHour:
      description: Authenticated v1 success cache policy.
      schema:
        type: string
        example: 'private, max-age=3600'
    CacheControlPrivateMinute:
      description: Authenticated v1 search/404 cache policy.
      schema:
        type: string
        example: 'private, max-age=60'
    CacheControlNoStore:
      description: Non-cacheable authenticated response.
      schema:
        type: string
        example: 'private, no-store'
  responses:
    NotModified:
      description: Conditional cache hit (If-None-Match matched ETag). Supported on IFSC, HSN, and SAC exact lookup only.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/X-Request-Id'
        ETag:
          $ref: '#/components/headers/ETag'
        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'
    ValidationError:
      description: Invalid request
      headers:
        X-Request-Id:
          $ref: '#/components/headers/X-Request-Id'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
          example:
            error:
              type: validation_error
              code: invalid_ifsc_format
              message: The IFSC code is invalid.
              request_id: req_0123456789abcdef0123456789abcdef
              param: ifsc
    Unauthorized:
      description: Missing or invalid API key
      headers:
        X-Request-Id:
          $ref: '#/components/headers/X-Request-Id'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
    Forbidden:
      description: Authenticated principal is not authorized (inactive organization or application)
      headers:
        X-Request-Id:
          $ref: '#/components/headers/X-Request-Id'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
    NotFound:
      description: Valid format but resource absent, or unknown /api/v1 route
      headers:
        X-Request-Id:
          $ref: '#/components/headers/X-Request-Id'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
    MethodNotAllowed:
      description: HTTP method not allowed for this route
      headers:
        X-Request-Id:
          $ref: '#/components/headers/X-Request-Id'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
    InternalError:
      description: Internal server error
      headers:
        X-Request-Id:
          $ref: '#/components/headers/X-Request-Id'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
    DailyQuotaExceeded:
      description: The application has exceeded its daily API quota or the short-window burst limit (burst_rate_exceeded) for this environment.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/X-Request-Id'
        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'
        Retry-After:
          $ref: '#/components/headers/Retry-After'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
          example:
            error:
              type: rate_limit_error
              code: daily_quota_exceeded
              message: The application has exceeded its daily API quota.
              request_id: req_0123456789abcdef0123456789abcdef
  schemas:
    ApiError:
      type: object
      additionalProperties: false
      required: [error]
      properties:
        error:
          type: object
          additionalProperties: false
          required: [type, code, message, request_id]
          properties:
            type:
              type: string
              enum:
                - validation_error
                - authentication_error
                - authorization_error
                - not_found
                - conflict_error
                - rate_limit_error
                - method_not_allowed
                - unsupported_media_type
                - internal_error
            code:
              type: string
            message:
              type: string
            request_id:
              type: string
            param:
              type: string
              description: Optional field or query parameter that failed validation.
    IfscSupports:
      type: object
      additionalProperties: false
      required: [neft, rtgs, imps, upi]
      properties:
        neft:
          type: boolean
          nullable: true
        rtgs:
          type: boolean
          nullable: true
        imps:
          type: boolean
          nullable: true
        upi:
          type: boolean
          nullable: true
    IfscResource:
      type: object
      additionalProperties: false
      required:
        - ifsc
        - bank
        - bankCode
        - branch
        - address
        - city
        - district
        - state
        - micr
        - contact
        - supports
        - bankName
        - neft
        - rtgs
        - imps
        - upi
      properties:
        ifsc:
          type: string
          nullable: true
        bank:
          type: string
          nullable: true
        bankCode:
          type: string
          nullable: true
        branch:
          type: string
          nullable: true
        address:
          type: string
          nullable: true
        city:
          type: string
          nullable: true
        district:
          type: string
          nullable: true
        state:
          type: string
          nullable: true
        micr:
          type: string
          nullable: true
        contact:
          type: string
          nullable: true
        supports:
          $ref: '#/components/schemas/IfscSupports'
        bankName:
          type: string
          nullable: true
          description: Compatibility alias for bank.
        neft:
          type: boolean
          nullable: true
          description: Compatibility alias for supports.neft.
        rtgs:
          type: boolean
          nullable: true
        imps:
          type: boolean
          nullable: true
        upi:
          type: boolean
          nullable: true
    IfscSearchResponse:
      type: object
      additionalProperties: false
      required: [success, count, data]
      properties:
        success:
          type: boolean
        count:
          type: integer
        data:
          type: array
          maxItems: 100
          items:
            $ref: '#/components/schemas/IfscResource'
    GstCalculationRequest:
      type: object
      additionalProperties: false
      required: [amount, taxRate]
      properties:
        amount:
          description: Rupees. Number or decimal string. Max 10,000,000,000. Max 2 decimal places.
          oneOf:
            - type: string
              pattern: '^(0|[1-9]\d*)(\.\d{1,2})?$'
            - type: number
              minimum: 0
              maximum: 10000000000
        taxRate:
          description: Percent. 18 means 18%. Range 0–100.
          oneOf:
            - type: number
              minimum: 0
              maximum: 100
            - type: string
        pricingMode:
          type: string
          enum: [exclusive, inclusive]
          default: exclusive
          description: exclusive = amount is taxable value. inclusive = amount is gross including GST. Default exclusive.
        supplyType:
          type: string
          enum: [intra_state, inter_state]
          default: intra_state
          description: Default intra_state.
        sellerStateCode:
          type: string
          pattern: '^[0-9]{2}$'
        buyerStateCode:
          type: string
          pattern: '^[0-9]{2}$'
        gst_rate:
          type: number
          description: Compatibility alias for taxRate. Prefer taxRate.
        calculation_type:
          type: string
          enum: [exclusive, inclusive]
          description: Compatibility alias for pricingMode. Prefer pricingMode.
        supply_type:
          type: string
          enum: [intra_state, inter_state]
          description: Compatibility alias for supplyType. Prefer supplyType.
        supplier_state_code:
          type: string
          description: Compatibility alias for sellerStateCode.
        place_of_supply_state_code:
          type: string
          description: Compatibility alias for buyerStateCode.
    GstCalculationResult:
      type: object
      additionalProperties: false
      required:
        - currency
        - pricingMode
        - supplyType
        - determinationMethod
        - taxRate
        - inputAmount
        - taxableAmount
        - cgst
        - sgst
        - igst
        - totalTax
        - totalAmount
      properties:
        currency:
          type: string
          enum: [INR]
        pricingMode:
          type: string
          enum: [exclusive, inclusive]
        supplyType:
          type: string
          enum: [intra_state, inter_state]
        determinationMethod:
          type: string
          enum: [explicit, state_code_comparison]
        taxRate:
          type: number
        inputAmount:
          type: string
          description: Decimal string, 2 places, rupees.
        taxableAmount:
          type: string
        cgst:
          type: string
        sgst:
          type: string
        igst:
          type: string
        totalTax:
          type: string
        totalAmount:
          type: string
    HsnResource:
      type: object
      additionalProperties: false
      required: [type, code, description, chapter, heading, gstRate]
      properties:
        type:
          type: string
          enum: [hsn]
        code:
          type: string
        description:
          type: string
        chapter:
          type: string
        heading:
          type: string
          nullable: true
        gstRate:
          type: number
          enum: [0, 3, 5, 12, 18, 28]
          description: Catalog-stored rate. Not live tax law.
    SacResource:
      type: object
      additionalProperties: false
      required: [type, code, description, chapter, gstRate]
      properties:
        type:
          type: string
          enum: [sac]
        code:
          type: string
        description:
          type: string
        chapter:
          type: string
          enum: ['99']
        gstRate:
          type: number
          enum: [0, 3, 5, 12, 18, 28]
          description: Catalog-stored rate. Not live tax law.
    ClassificationSearchHit:
      type: object
      additionalProperties: false
      required: [type, code, description, chapter, gstRate]
      properties:
        type:
          type: string
          enum: [hsn, sac]
        code:
          type: string
        description:
          type: string
        chapter:
          type: string
        gstRate:
          type: number
    ClassificationSearchResult:
      type: object
      additionalProperties: false
      required: [query, count, results]
      properties:
        query:
          type: string
        count:
          type: integer
        results:
          type: array
          maxItems: 20
          items:
            $ref: '#/components/schemas/ClassificationSearchHit'
