openapi: 3.1.0
info:
  title: 1 Vision USA API
  description: |
    Versioned HTTPS interface for authorized integrations.

    **Versioning.** `/api/v1` is the current major version. Breaking
    changes fork to a new major version with a 6-month coexistence
    minimum.

    **Authentication.** Bearer service JWT (HS256) on every request.

    **Idempotency.** Each write carries a client-supplied
    `correlation_id` (UUID). Replays with the same id return the
    original response; same id with a different body returns 422.

    **Rate limit.** 60 requests/min per credential. Standard IETF
    `RateLimit-*` response headers on every response.
  version: "1.0.0"
  contact:
    name: API Support
    email: api@1visionusa.com
  license:
    name: Proprietary
servers:
  - url: https://api.1visionusa.com
    description: Production
tags:
  - name: Health
    description: Operational endpoints
  - name: Auth
    description: Authentication
  - name: Leads
    description: Lead intake
  - name: Webhooks
    description: Event subscriptions

paths:
  /.well-known/jwks.json:
    get:
      tags: [Auth]
      summary: JWKS endpoint
      operationId: getJwks
      security: []
      responses:
        '200':
          description: JWKS document
          content:
            application/jwk-set+json:
              schema:
                $ref: '#/components/schemas/JWKS'

  /api/v1/healthz:
    get:
      tags: [Health]
      summary: Health check
      operationId: getHealthz
      security: []
      responses:
        '200':
          description: All dependencies healthy
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthzOk'
        '503':
          description: One or more dependencies unhealthy
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthzFail'

  /api/v1/partners/{partner_slug}/leads:
    post:
      tags: [Leads]
      summary: Create a lead
      description: |
        Submit a single lead. The request is idempotent on
        `correlation_id`.

        Required scope: `<partner_slug>:leads:write`.
      operationId: createLead
      parameters:
        - $ref: '#/components/parameters/PartnerSlug'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LeadCreate'
      responses:
        '201':
          description: Lead accepted
          headers:
            RateLimit-Limit:
              $ref: '#/components/headers/RateLimitLimit'
            RateLimit-Remaining:
              $ref: '#/components/headers/RateLimitRemaining'
            RateLimit-Reset:
              $ref: '#/components/headers/RateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LeadEnvelope'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/ValidationFailed'
        '429':
          $ref: '#/components/responses/RateLimited'

  /api/v1/partners/{partner_slug}/webhook-subscriptions:
    post:
      tags: [Webhooks]
      summary: Create a webhook subscription
      description: |
        Register a URL to receive HMAC-signed event deliveries.
        Failed deliveries retry with exponential backoff before
        being marked dead-letter.

        Required scope: `<partner_slug>:webhooks:write`.
      operationId: createWebhookSubscription
      parameters:
        - $ref: '#/components/parameters/PartnerSlug'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookSubscriptionCreate'
      responses:
        '201':
          description: Subscription created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookSubscriptionEnvelope'
    get:
      tags: [Webhooks]
      summary: List webhook subscriptions
      operationId: listWebhookSubscriptions
      parameters:
        - $ref: '#/components/parameters/PartnerSlug'
      responses:
        '200':
          description: Subscription list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookSubscriptionList'

components:
  parameters:
    PartnerSlug:
      in: path
      name: partner_slug
      required: true
      schema:
        type: string
        pattern: '^[a-z][a-z0-9_]+$'
      example: acme

  securitySchemes:
    serviceJwt:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        Bearer service JWT. Required claims: `iss`, `sub`, `aud`,
        `exp`, `iat`, `jti`, `scopes[]`, `kid`.

  headers:
    RateLimitLimit:
      schema: { type: integer }
      description: IETF RateLimit header
    RateLimitRemaining:
      schema: { type: integer }
    RateLimitReset:
      schema: { type: integer }

  schemas:
    JWKS:
      type: object
      required: [keys]
      properties:
        keys:
          type: array
          items: { type: object }

    HealthzOk:
      type: object
      required: [ok, build, checks]
      properties:
        ok: { type: boolean, enum: [true] }
        build: { type: string }
        checks:
          type: object
          properties:
            db: { type: boolean }
            redis: { type: boolean }
            partitions_provisioned: { type: boolean }

    HealthzFail:
      type: object
      required: [ok, failed_checks]
      properties:
        ok: { type: boolean, enum: [false] }
        failed_checks:
          type: array
          items: { type: string }

    LeadCreate:
      type: object
      required: [correlation_id, customer]
      properties:
        correlation_id:
          type: string
          format: uuid
          description: Client-supplied UUID. Idempotency key.
        customer:
          $ref: '#/components/schemas/Customer'
        offer:
          $ref: '#/components/schemas/Offer'
        commission:
          $ref: '#/components/schemas/Commission'
        partner_metadata:
          type: object
          additionalProperties: true
          description: Free-form annotations.

    Customer:
      type: object
      required: [first_name, last_name, phone]
      properties:
        first_name: { type: string, maxLength: 100 }
        last_name: { type: string, maxLength: 100 }
        email: { type: string, format: email, maxLength: 200 }
        phone:
          type: string
          description: E.164 preferred.
        address:
          type: object
          properties:
            line1: { type: string, maxLength: 200 }
            city: { type: string, maxLength: 100 }
            state: { type: string, minLength: 2, maxLength: 2 }
            postal_code: { type: string, maxLength: 20 }

    Offer:
      type: object
      properties:
        type: { type: string }
        monthly_amount_cents: { type: integer, minimum: 0 }
        term_months: { type: integer, minimum: 1, maximum: 120 }
        notes: { type: string, maxLength: 2000 }

    Commission:
      type: object
      properties:
        flat_cents: { type: integer, minimum: 0 }
        rev_share_bps:
          type: integer
          minimum: 0
          maximum: 10000
          description: Basis points (100 = 1%).

    Lead:
      type: object
      required: [lead_id, partner_slug, correlation_id, status, received_at]
      properties:
        lead_id: { type: string, format: uuid }
        partner_slug: { type: string }
        correlation_id: { type: string, format: uuid }
        status:
          type: string
          enum: [received, hubspot_upserted, dispatched, completed, failed]
        received_at: { type: string, format: date-time }
        hubspot_deal_id: { type: string, nullable: true }
        next_action:
          type: string
          enum: [pending_orchestrator, manual_review]

    LeadEnvelope:
      type: object
      required: [ok, data]
      properties:
        ok: { type: boolean, enum: [true] }
        data: { $ref: '#/components/schemas/Lead' }

    WebhookSubscriptionCreate:
      type: object
      required: [url, events]
      properties:
        url:
          type: string
          format: uri
          description: HTTPS URL. Must respond 2xx within 10s.
        events:
          type: array
          minItems: 1
          items:
            type: string
          description: |
            Event filters. Specific values (`lead.created`) or
            wildcards (`lead.*`, `*`).
        description: { type: string, maxLength: 500 }

    WebhookSubscription:
      allOf:
        - $ref: '#/components/schemas/WebhookSubscriptionCreate'
        - type: object
          required: [id, hmac_secret, created_at]
          properties:
            id: { type: string, format: uuid }
            hmac_secret:
              type: string
              description: |
                Verify the `X-Fulfillment-Signature` header on
                deliveries: hex HMAC-SHA256 of the raw body
                using this secret.
            active: { type: boolean }
            created_at: { type: string, format: date-time }
            last_success_at: { type: string, format: date-time, nullable: true }
            last_failure_at: { type: string, format: date-time, nullable: true }
            consecutive_failures: { type: integer }

    WebhookSubscriptionEnvelope:
      type: object
      required: [ok, data]
      properties:
        ok: { type: boolean, enum: [true] }
        data: { $ref: '#/components/schemas/WebhookSubscription' }

    WebhookSubscriptionList:
      type: object
      required: [ok, data]
      properties:
        ok: { type: boolean, enum: [true] }
        data:
          type: object
          properties:
            items:
              type: array
              items: { $ref: '#/components/schemas/WebhookSubscription' }
            count: { type: integer }

    ErrorEnvelope:
      type: object
      required: [ok, error]
      properties:
        ok: { type: boolean, enum: [false] }
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
              enum:
                - UNAUTHORIZED
                - FORBIDDEN
                - NOT_FOUND
                - VALIDATION_FAILED
                - CONFLICT
                - IDEMPOTENCY_MISMATCH
                - JTI_REPLAY
                - RATE_LIMITED
                - JWT_ALG_NOT_SUPPORTED
                - JWT_INVALID
                - JWT_EXPIRED
                - INTERNAL
            message: { type: string }
            details:
              type: object
              additionalProperties: true

  responses:
    Unauthorized:
      description: Missing or invalid credential
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ErrorEnvelope' }
    Forbidden:
      description: Insufficient scope
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ErrorEnvelope' }
    ValidationFailed:
      description: Request did not match the schema
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ErrorEnvelope' }
    RateLimited:
      description: Quota exceeded
      headers:
        Retry-After:
          schema: { type: integer }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ErrorEnvelope' }

security:
  - serviceJwt: []
