openapi: 3.0.3
info:
  title: STI-SP OpenAPI
  version: 1.0.0
  description: |
    STI-SP provides REST APIs for STIR/SHAKEN call signing and verification.

    Before API traffic is accepted, approve the source IP in the STI-SP portal.
    Signing requests also require an uploaded STIR/SHAKEN certificate/private key
    and an ANI attestation policy or explicit `attest` value.
  contact:
    name: STI-SP
    url: https://sti-sp.com/
servers:
  - url: https://api.sti-sp.com
    description: Production REST API through api.sti-sp.com
tags:
  - name: Signing
    description: Generate a PASSporT Identity header for outbound calls.
  - name: Verification
    description: Verify a received PASSporT Identity token for inbound calls.
paths:
  /v1/sign:
    post:
      tags:
        - Signing
      operationId: signCall
      summary: Sign an outbound call
      description: |
        Generates a STIR/SHAKEN PASSporT Identity header for an outbound call.
        The request must come from an allowed source IP. If `cert_id` is omitted,
        STI-SP selects an eligible certificate configured for that source IP.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SignRequest"
            examples:
              signWithCertificate:
                summary: Sign with an uploaded certificate
                value:
                  orig: "+12125550123"
                  dest:
                    - "+14155550100"
                  attest: "A"
                  cert_id: "cert_7F3A"
      responses:
        "200":
          description: Identity header generated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SignResponse"
              examples:
                signed:
                  value:
                    identity: "eyJhbGciOiJFUzI1NiIsInBwdCI6InNoYWtlbiIsInR5cCI6InBhc3Nwb3J0In0...;info=<https://certs.sti-sp.com/cert_7F3A.pem>;alg=ES256;ppt=shaken"
                    origid: "550e8400-e29b-41d4-a716-446655440000"
                    attest: "A"
                    cert_id: "cert_7F3A"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/SourceIpNotAllowed"
        "404":
          description: Certificate or ANI attestation policy was not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          $ref: "#/components/responses/RateLimited"
  /v1/verify:
    post:
      tags:
        - Verification
      operationId: verifyCall
      summary: Verify an inbound Identity token
      description: |
        Submit the received STIR/SHAKEN PASSporT Identity token. STI-SP decodes
        the token and validates the PASSporT signature, certificate chain,
        certificate dates, `iat` freshness, attestation, originating identity,
        and destination claims. Verification failures are returned as a normal
        response with `valid: false` and a STIR/SHAKEN reason code.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/VerifyRequest"
            examples:
              verifyIdentity:
                summary: Verify a received Identity token
                value:
                  identity_token: "eyJhbGciOiJFUzI1NiIsInBwdCI6InNoYWtlbiIsInR5cCI6InBhc3Nwb3J0In0..."
                  expected_orig: "+12125550123"
                  expected_dest:
                    - "+14155550100"
      responses:
        "200":
          description: Verification completed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VerifyResponse"
              examples:
                valid:
                  value:
                    valid: true
                    verstat: "TN-Validation-Passed"
                    reasoncode: 0
                    reasontext: ""
                    reasondesc: ""
                    attest: "A"
                    origid: "550e8400-e29b-41d4-a716-446655440000"
                    decoded_identity:
                      orig:
                        tn: "+12125550123"
                      dest:
                        tn:
                          - "+14155550100"
                      iat: 1778091963
                      attest: "A"
                      origid: "550e8400-e29b-41d4-a716-446655440000"
                invalid:
                  value:
                    valid: false
                    verstat: "TN-Validation-Failed"
                    reasoncode: 438
                    reasontext: "Invalid Identity Header"
                    reasondesc: "Signature validation failed."
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/SourceIpNotAllowed"
        "429":
          $ref: "#/components/responses/RateLimited"
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API key
      description: API key generated from the STI-SP portal.
  schemas:
    Tn:
      type: string
      pattern: "^\\+?[1-9][0-9]{7,14}$"
      example: "+12125550123"
      description: Telephone number in E.164 format. A leading `+` is recommended.
    SignRequest:
      type: object
      required:
        - orig
        - dest
      properties:
        orig:
          $ref: "#/components/schemas/Tn"
        dest:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/Tn"
        attest:
          type: string
          enum:
            - A
            - B
            - C
          description: |
            Optional explicit attestation. If omitted, STI-SP uses the ANI or
            prefix attestation rules configured in the portal, then the default
            attestation fallback.
        cert_id:
          type: string
          example: "cert_7F3A"
          description: Optional uploaded certificate to use for signing.
        origid:
          type: string
          format: uuid
          description: Optional PASSporT origid. If omitted, STI-SP generates one.
        iat:
          type: integer
          minimum: 0
          description: Optional issued-at Unix timestamp. If omitted, STI-SP uses server time.
    SignResponse:
      type: object
      required:
        - identity
        - origid
      properties:
        identity:
          type: string
          description: Full SIP Identity header value.
        origid:
          type: string
          format: uuid
        attest:
          type: string
          enum:
            - A
            - B
            - C
        cert_id:
          type: string
    VerifyRequest:
      type: object
      required:
        - identity_token
      properties:
        identity_token:
          type: string
          minLength: 24
          description: |
            PASSporT token from the received SIP Identity header. If the full
            SIP Identity header is supplied, STI-SP extracts the PASSporT token
            before validation.
        expected_orig:
          allOf:
            - $ref: "#/components/schemas/Tn"
          description: Optional ANI expected by your application. When supplied,
            STI-SP compares it with the decoded PASSporT `orig.tn`.
        expected_dest:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/Tn"
          description: Optional DNIS list expected by your application. When
            supplied, STI-SP compares it with the decoded PASSporT `dest.tn`.
        validation_time:
          type: integer
          minimum: 0
          description: Optional Unix timestamp used for verification freshness checks.
    VerifyResponse:
      type: object
      required:
        - valid
        - verstat
        - reasoncode
        - reasontext
        - reasondesc
      properties:
        valid:
          type: boolean
        verstat:
          type: string
          enum:
            - TN-Validation-Passed
            - TN-Validation-Failed
            - No-TN-Validation
        reasoncode:
          type: integer
          description: "`0` means passed. Non-zero values are STIR/SHAKEN failure reason codes."
          enum:
            - 0
            - 403
            - 436
            - 437
            - 438
        reasontext:
          type: string
          example: "Invalid Identity Header"
        reasondesc:
          type: string
          example: "Signature validation failed."
        attest:
          type: string
          enum:
            - A
            - B
            - C
        origid:
          type: string
          format: uuid
        cert_url:
          type: string
          format: uri
        decoded_identity:
          type: object
          description: Decoded PASSporT claims from `identity_token`.
          additionalProperties: true
    ErrorResponse:
      type: object
      required:
        - error
        - message
      properties:
        error:
          type: string
          example: "source_ip_not_allowed"
        message:
          type: string
          example: "Approve this source IP in the STI-SP portal before sending API requests."
        request_id:
          type: string
          example: "req_01HY..."
  responses:
    BadRequest:
      description: Request body is missing required call data or contains invalid values.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Unauthorized:
      description: API key is missing or invalid.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    SourceIpNotAllowed:
      description: Source IP is not approved for this API action.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    RateLimited:
      description: Request rate exceeded the account or source-IP limit.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
