openapi: 3.1.0
info:
  title: Sharp Trust — Transaction Decisioning API
  version: 0.1.2
  description: >
    Cross-scheme transaction risk decisioning for agentic commerce.
    Partner integrations use the versioned /v1 merchant API. For each
    agent-initiated transaction, Sharp Trust returns a pre-authorization
    decision (PROCEED / REVIEW / BLOCK / BLOCKED_BY_SCHEME), a transaction risk
    score in [0,1], machine-readable reason codes, and an immutable audit
    reference. Sharp Trust NEVER receives, processes, or stores a full PAN, CVV,
    track data, or usable cryptogram — only secure token references and metadata.
    Partners self-serve test keys at /portal on the
    production host. Unversioned /assessments routes still exist for
    internal/legacy use; they are not the partner contract.
  contact:
    name: Sharp Labs
  license:
    name: Proprietary

servers:
  - url: http://localhost:3000
    description: Local development (mock adapters)
  - url: https://trust.sharp-labs.com
    description: Production — partner test keys via /portal

tags:
  - name: Decisioning
    description: Core pre-authorization risk decision on a transaction.
  - name: Events
    description: Post-transaction outcome ingestion (the learning loop).
  - name: Registry
    description: Internal — register platforms, providers, and agents. Not partner-facing.
    x-internal: true
  - name: System
    description: Health and operational endpoints.
  - name: TAP Test
    description: Temporary, gated Visa TAP interoperability testing. Not partner-facing.
    x-internal: true

security:
  - BearerAuth: []
  - ApiKeyAuth: []

paths:
  /health:
    get:
      tags: [System]
      summary: Liveness/readiness probe
      security: []
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthStatus"

  /tap/visa/e2e:
    get:
      tags: [TAP Test]
      summary: Verify a Visa-originated TAP signed GET
      description: >
        Temporary interoperability endpoint. It verifies the HTTP Message
        Signature against the live Visa Agentic Directory without accepting a
        request body or persisting signed material. Query parameters are not
        supported. The route returns 404 unless VISA_TAP_E2E_ENABLED=true.
      operationId: verifyVisaTapE2e
      x-internal: true
      security: []
      parameters:
        - name: Signature-Input
          in: header
          required: true
          schema:
            type: string
        - name: Signature
          in: header
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Signature and active Directory identity verified
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VisaTapE2eResult"
        "400":
          description: Missing, ambiguous, malformed, or unsupported signed request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VisaTapE2eResult"
        "401":
          description: Invalid, expired, or replayed signature
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VisaTapE2eResult"
        "403":
          description: Unknown, inactive, revoked, or expired Directory identity
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VisaTapE2eResult"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          description: Temporary endpoint rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VisaTapE2eResult"
        "503":
          description: Visa Agentic Directory unavailable
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VisaTapE2eResult"

  /v1/assessments:
    post:
      tags: [Decisioning]
      summary: Assess a single agent-initiated transaction (pre-authorization)
      description: >
        Submit one agent-initiated transaction with its context and any
        available evidence. Returns a decision before authorization. Idempotent
        per merchant via the Idempotency-Key header. The minimum request
        (platform_transaction_ref, buyer_agent.external_ref, amount) is accepted
        and typically returns REVIEW when no identity, mandate, scheme, or
        historical evidence is supplied.
      operationId: createAssessment
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AssessmentRequest"
            examples:
              minimum:
                summary: Minimum partner request (cold start)
                value:
                  platform_transaction_ref: your-own-transaction-id
                  buyer_agent:
                    external_ref: the-agent-identifier
                  amount:
                    value: "48.00"
                    currency: USD
                  merchant_meta:
                    name: acme-subscriptions
      responses:
        "200":
          description: Decision produced
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Assessment"
              examples:
                coldStartReview:
                  summary: Minimum request with no evidence returns REVIEW
                  value:
                    decision: REVIEW
                    trust_score: 0.7
                    confidence: 0.5
                    reason_codes:
                      - SIGNATURE_NOT_PROVIDED
                      - MANDATE_NOT_PROVIDED
                      - NO_VELOCITY_BASELINE
                      - NEW_AGENT_LOW_HISTORY
                      - CONTEXT_DEFAULT_PRIOR
                      - SELLER_LOW_HISTORY
                    assessment_id: 0716d54b-c12f-4a36-a58c-5d42c6388fee
                    audit_ref: audit_0716d54b-c12f-4a36-a58c-5d42c6388fee
                    created_at: "2026-08-14T21:10:00.000Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "422":
          $ref: "#/components/responses/UnprocessableEntity"
    get:
      tags: [Decisioning]
      summary: List this merchant's recent assessments
      operationId: listAssessments
      parameters:
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
      responses:
        "200":
          description: Assessments for the authenticated merchant
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/AssessmentListItem"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"

  /v1/assessments/{assessment_id}:
    get:
      tags: [Decisioning]
      summary: Retrieve a previously produced decision
      operationId: getAssessment
      parameters:
        - name: assessment_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: The assessment
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Assessment"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"

  /v1/events:
    post:
      tags: [Events]
      summary: Report a post-transaction outcome event
      description: >
        Authenticated, merchant-attributable outcome events (payment completed,
        refund, dispute, chargeback, fulfillment, etc.). These feed reputation
        aggregation and serve as ML training labels. De-duplicated per merchant
        via source_event_id. Optional; not required for the first integration.
      operationId: createEvent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EventRequest"
      responses:
        "202":
          description: Event accepted for processing
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventAck"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "409":
          description: Duplicate event (already ingested)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventAck"

  /v1/stats:
    get:
      tags: [Decisioning]
      summary: Decision counts for the authenticated merchant
      operationId: getDecisionStats
      responses:
        "200":
          description: Totals by decision
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DecisionStats"
        "401":
          $ref: "#/components/responses/Unauthorized"

  /registry/platforms:
    post:
      tags: [Registry]
      summary: Register an integrating platform (PSP, micropayment platform, MoR)
      x-internal: true
      operationId: createPlatform
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PlatformCreate"
      responses:
        "201":
          description: Platform created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Platform"
        "400":
          $ref: "#/components/responses/BadRequest"

  /registry/providers:
    post:
      tags: [Registry]
      summary: Register a provider (organization operating agents)
      x-internal: true
      operationId: createProvider
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProviderCreate"
      responses:
        "201":
          description: Provider created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Provider"

  /registry/agents:
    post:
      tags: [Registry]
      summary: Register a buyer or seller agent
      x-internal: true
      operationId: createAgent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AgentCreate"
      responses:
        "201":
          description: Agent created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Agent"

  /v1/outcomes:
    post:
      tags: [Outcomes]
      summary: Report a transaction outcome (feeds reputation and the audit trail)
      operationId: createOutcome
      security:
        - BearerAuth: []
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/OutcomeReport"
      responses:
        "202":
          description: Accepted (recorded, or accepted as preliminary / non-reputational)
        "400":
          description: Invalid outcome
        "404":
          description: Unknown transaction_ref and no explicit subject refs supplied

  /v1/audit-trail:
    get:
      tags: [Outcomes]
      summary: Read the tamper-evident outcome audit trail
      operationId: getAuditTrail
      security:
        - BearerAuth: []
        - ApiKeyAuth: []
      responses:
        "200":
          description: The hash-chained outcome log and a verification flag
          content:
            application/json:
              schema:
                type: object
                properties:
                  count:
                    type: integer
                  verified:
                    type: boolean
                  entries:
                    type: array
                    items:
                      type: object
                      additionalProperties: true

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: >
        Per-merchant API key. Test keys look like shai_test_...; live keys
        look like shai_live_.... Send Authorization: Bearer <key>. Stored
        only as a hash server-side.
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: >
        Same per-merchant key as Bearer, sent as X-API-Key. Do not send both
        headers with different values.

  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: Unique key per platform to make assessment creation idempotent.
      schema:
        type: string
        maxLength: 255

  responses:
    BadRequest:
      description: Malformed request
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    UnprocessableEntity:
      description: Well-formed but semantically invalid (e.g., unknown agent ref)
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"

  schemas:
    # ---------- Shared primitives ----------
    Money:
      type: object
      additionalProperties: false
      required: [value, currency]
      properties:
        value:
          type: string
          description: Decimal amount as a string to avoid float rounding (e.g. "48.00").
          example: "48.00"
        currency:
          type: string
          description: ISO 4217 currency code.
          minLength: 3
          maxLength: 3
          example: "USD"

    RailHint:
      type: string
      description: Informational hint about the intended settlement rail.
      enum:
        - card_visa
        - card_mastercard
        - card_other
        - stablecoin
        - bank_transfer
        - aggregated_micropayment
        - unknown

    Decision:
      type: string
      enum: [PROCEED, REVIEW, BLOCK, BLOCKED_BY_SCHEME]

    Recommendation:
      type: string
      enum: [APPROVE, REVIEW, DECLINE]
      description: Plain APPROVE / REVIEW / DECLINE, mapped from decision.

    OutcomeReport:
      type: object
      required: [transaction_ref, type]
      description: PSP outcome contract for POST /v1/outcomes. Opaque ids only, never a PAN.
      properties:
        transaction_ref:
          type: string
        type:
          type: string
          enum: [authorization, capture, settlement, reversal, refund, dispute, chargeback, fraud]
        final:
          type: boolean
          default: true
          description: Only final, reputationally-meaningful outcomes move reputation.
        source:
          type: string
          enum: [network, psp, merchant, platform, unknown]
        occurred_at:
          type: string
          format: date-time
        amount:
          type: number
        merchant_ref:
          type: string
        mandate_ref:
          type: string
        agent_ref:
          type: string
        seller_ref:
          type: string

    Enforcement:
      type: string
      enum: [advisory, hard_block]

    AssessmentMode:
      type: string
      enum: [decisioning_only, scheme_verification]

    Explainability:
      type: string
      enum: [summary, full]
      default: summary

    AdapterName:
      type: string
      enum: [agent_signature, mandates, scheme, intent, velocity, reputation, counterparty, merchant_legitimacy, session]

    AdapterStatus:
      type: string
      enum: [verified, failed, unavailable, skipped, mock]

    # ---------- Request: /v1/assessments ----------
    AssessmentRequest:
      type: object
      additionalProperties: false
      required: [platform_transaction_ref, buyer_agent, amount]
      properties:
        platform_transaction_ref:
          type: string
          description: Platform-side correlation id for this transaction.
        mode:
          $ref: "#/components/schemas/AssessmentMode"
        enforcement:
          $ref: "#/components/schemas/Enforcement"
          description: Optional override of the platform's default enforcement.
        explainability:
          $ref: "#/components/schemas/Explainability"
        require_scheme_check:
          type: boolean
          default: false
          description: If true, a hard scheme-signal failure yields BLOCKED_BY_SCHEME.
        buyer_agent:
          $ref: "#/components/schemas/AgentRef"
        seller_agent:
          $ref: "#/components/schemas/AgentRef"
        amount:
          $ref: "#/components/schemas/Money"
        rail_hint:
          $ref: "#/components/schemas/RailHint"
        merchant_meta:
          type: object
          description: >
            Merchant context: name, plus url (or domain) and lei to enable merchant-legitimacy
            checks (domain age, certificate transparency, GLEIF). MUST NOT contain a PAN.
          additionalProperties: true
        intent_summary:
          type: string
          description: Structured or natural-language purchase intent.
        token_reference:
          type: string
          description: Secure token reference ONLY. Never a PAN, CVV, or cryptogram.
        evidence:
          $ref: "#/components/schemas/Evidence"

    AgentRef:
      type: object
      additionalProperties: false
      description: Reference an existing agent by id, or describe one inline.
      properties:
        agent_id:
          type: string
          format: uuid
        external_ref:
          type: string
        jwks_uri:
          type: string
          format: uri
      anyOf:
        - required: [agent_id]
        - required: [external_ref]

    Evidence:
      type: object
      additionalProperties: false
      description: Optional verification material. TAP evidence is verified before decisioning and is never persisted verbatim.
      properties:
        agent_signature:
          $ref: "#/components/schemas/TapSignatureEvidence"
        source_ip:
          type: string
          description: The agent's source IP, cross-checked against the provider's published infrastructure ranges.
        mandates:
          type: object
          additionalProperties: true
          description: >
            The user's authorization. Fields: amount_cap, currency, counterparty, effective_until;
            optional amount_tolerance_percent / amount_tolerance_fixed (tax, shipping, tips, FX,
            pre-auth drift); user_confirmed and assurance (inferred | user_confirmed |
            authenticated | cryptographic); instruction for a natural-language mandate we derive;
            allowance_id for a Basis Theory allowance; ap2 for an AP2 verifiable mandate.
        intent:
          type: object
          additionalProperties: true
          description: >
            instruction (the user's goal), agent_context (untrusted text the agent read, scanned
            for prompt-injection), and item_description (for intent-vs-purchase alignment).
        session:
          type: object
          additionalProperties: true
          description: >
            Consumer/session assurance: human_verified, session_bound, account_flagged,
            delegation { scopes, expires_at }, assurance, provider. A valid agent may still act
            for a compromised account.
        scheme:
          type: object
          description: Read-only scheme context signals. TAP identity is evaluated independently.
          additionalProperties: true

    TapSignatureEvidence:
      type: object
      additionalProperties: false
      required: [signature_input, signature, authority, path]
      description: >
        RFC 9421 HTTP Message Signature material forwarded by the merchant or PSP.
        Signature values, raw bodies, and headers are redacted before audit persistence.
      properties:
        signature_input:
          type: string
          description: >
            Signature-Input structured field containing covered components and TAP parameters.
            Visa requires @authority and @path plus created, keyid, expires, tag, alg, and nonce.
        signature:
          type: string
          description: Signature structured field.
        method:
          type: string
          description: Required only when @method is a covered signature component.
          example: POST
        authority:
          type: string
          example: merchant.example
        path:
          type: string
          example: /checkout
        headers:
          type: object
          additionalProperties:
            type: string
          description: Only headers named by Signature-Input, excluding credentials.
        content_digest:
          type: string
          description: RFC content digest, required when it is a covered component.
        body:
          type: string
          writeOnly: true
          description: Body used only to verify content_digest; it is not retained.
        signature_agent:
          type: string
          description: >
            Open Web Bot Auth: the Signature-Agent value naming the agent provider's own
            published key directory. When present, the signature is verified against that
            directory instead of a scheme directory.
    # ---------- Response: Assessment ----------
    Assessment:
      type: object
      required:
        - assessment_id
        - transaction_id
        - mode
        - decision
        - recommendation
        - trust_score
        - confidence
        - enforcement
        - reason_codes
        - model_version
        - audit_ref
        - created_at
      properties:
        assessment_id:
          type: string
          format: uuid
        transaction_id:
          type: string
          format: uuid
        mode:
          $ref: "#/components/schemas/AssessmentMode"
        decision:
          $ref: "#/components/schemas/Decision"
        recommendation:
          $ref: "#/components/schemas/Recommendation"
        trust_score:
          type: number
          minimum: 0
          maximum: 1
          description: Overall trust, 1 = most trustworthy.
        agent_trust:
          type: number
          minimum: 0
          maximum: 1
          description: Trust in the agent and its authorization (identity, session, mandate, agent reputation).
        transaction_risk:
          type: number
          minimum: 0
          maximum: 1
          description: Risk of this specific transaction (velocity, intent, merchant). Higher is riskier.
        confidence:
          type: number
          minimum: 0
          maximum: 1
          description: Alias of evidence_confidence.
        evidence_confidence:
          type: number
          minimum: 0
          maximum: 1
          description: How much real evidence backed the assessment (coverage).
        enforcement:
          $ref: "#/components/schemas/Enforcement"
        reason_codes:
          type: array
          items:
            type: string
          example: [SIGNATURE_NOT_PROVIDED, MANDATE_NOT_PROVIDED, NO_VELOCITY_BASELINE]
        explanations:
          type: object
          description: Present when explainability=full. Maps reason codes to human text.
          additionalProperties: true
        adapter_results:
          type: array
          items:
            $ref: "#/components/schemas/AdapterResult"
        model_version:
          type: string
          example: "rules-v0.2"
        audit_ref:
          type: string
          description: Reference to the immutable audit record for this decision.
        created_at:
          type: string
          format: date-time

    AdapterResult:
      type: object
      required: [adapter, status]
      properties:
        adapter:
          $ref: "#/components/schemas/AdapterName"
        status:
          $ref: "#/components/schemas/AdapterStatus"
        detail:
          type: string
        artifacts:
          type: object
          description: Audit/scoring artifacts (no credentials).
          additionalProperties: true

    AssessmentListItem:
      type: object
      description: Compact assessment row returned by GET /v1/assessments.
      properties:
        assessment_id:
          type: string
          format: uuid
        created_at:
          type: string
          format: date-time
        decision:
          $ref: "#/components/schemas/Decision"
        trust_score:
          type: number
        confidence:
          type: number
        enforcement:
          $ref: "#/components/schemas/Enforcement"
        reason_codes:
          type: array
          items:
            type: string
        model_version:
          type: string
        transaction:
          type: object
          additionalProperties: true
        adapter_results:
          type: array
          items:
            $ref: "#/components/schemas/AdapterResult"

    DecisionStats:
      type: object
      required: [total, counts]
      properties:
        total:
          type: integer
          minimum: 0
        counts:
          type: object
          additionalProperties:
            type: integer
          properties:
            PROCEED:
              type: integer
            REVIEW:
              type: integer
            BLOCK:
              type: integer
            BLOCKED_BY_SCHEME:
              type: integer

    # ---------- Request: /v1/events ----------
    EventRequest:
      type: object
      additionalProperties: false
      required: [source_event_id, event_type, occurred_at]
      properties:
        source_event_id:
          type: string
          description: Platform-unique id for de-duplication.
        transaction_id:
          type: string
          format: uuid
        assessment_id:
          type: string
          format: uuid
        event_type:
          type: string
          enum:
            - payment_initiated
            - payment_completed
            - payment_failed
            - aggregation_settled
            - refund
            - dispute_opened
            - dispute_resolved
            - chargeback
            - fulfillment_confirmed
            - fulfillment_failed
        occurred_at:
          type: string
          format: date-time
        payload:
          type: object
          additionalProperties: true
          description: Event metadata only. Raw PAN, CVV/CVC, cryptograms, and track data are rejected.

    EventAck:
      type: object
      required: [event_id, status]
      properties:
        event_id:
          type: string
          format: uuid
        status:
          type: string
          enum: [accepted, duplicate]

    # ---------- Registry ----------
    PlatformCreate:
      type: object
      additionalProperties: false
      required: [name]
      properties:
        name:
          type: string
        webhook_url:
          type: string
          format: uri
        enforcement_default:
          $ref: "#/components/schemas/Enforcement"

    Platform:
      type: object
      required: [platform_id, name, enforcement_default, status, created_at]
      properties:
        platform_id:
          type: string
          format: uuid
        name:
          type: string
        webhook_url:
          type: string
          format: uri
        enforcement_default:
          $ref: "#/components/schemas/Enforcement"
        status:
          type: string
          enum: [active, suspended]
        api_key:
          type: string
          description: Returned ONCE at creation. Stored only as a hash thereafter.
        created_at:
          type: string
          format: date-time

    ProviderCreate:
      type: object
      additionalProperties: false
      required: [name]
      properties:
        name:
          type: string
        verification_status:
          type: string
          enum: [unverified, verified, certified]
        external_ids:
          type: object
          additionalProperties: true

    Provider:
      type: object
      required: [provider_id, name, verification_status, created_at]
      properties:
        provider_id:
          type: string
          format: uuid
        name:
          type: string
        verification_status:
          type: string
          enum: [unverified, verified, certified]
        external_ids:
          type: object
          additionalProperties: true
        created_at:
          type: string
          format: date-time

    AgentCreate:
      type: object
      additionalProperties: false
      required: [external_ref]
      properties:
        provider_id:
          type: string
          format: uuid
        external_ref:
          type: string
        jwks_uri:
          type: string
          format: uri
        tap_enrolled:
          type: boolean

    Agent:
      type: object
      required: [agent_id, external_ref, created_at]
      properties:
        agent_id:
          type: string
          format: uuid
        provider_id:
          type: string
          format: uuid
        external_ref:
          type: string
        jwks_uri:
          type: string
          format: uri
        tap_enrolled:
          type: boolean
        first_seen_at:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time

    # ---------- System ----------
    HealthStatus:
      type: object
      required: [status]
      properties:
        status:
          type: string
          enum: [ok]
        version:
          type: string
        time:
          type: string
          format: date-time

    VisaTapE2eResult:
      type: object
      required: [test_id, received_at, verification, decision, reason_codes, timing]
      properties:
        test_id:
          type: string
          format: uuid
        received_at:
          type: string
          format: date-time
        verification:
          type: string
          enum: [VERIFIED, FAILED, UNAVAILABLE]
        decision:
          type: string
          enum: [PROCEED, BLOCK, BLOCKED_BY_SCHEME]
        reason_codes:
          type: array
          items:
            type: string
        agent:
          type: object
          additionalProperties: false
          properties:
            agent_type:
              type: string
            key_algorithm:
              type: string
            key_status:
              type: string
            key_expires_at:
              type: string
              format: date-time
        timing:
          type: object
          additionalProperties: false
          properties:
            directory_latency_ms:
              type: number
              minimum: 0

    Error:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
            message:
              type: string
            details:
              type: object
              additionalProperties: true
