openapi: 3.1.0
info:
  title: NEXOS Scan Partner API
  version: 2.3.4
  description: |
    NEXOS Scan Partner API — programmatic access to NexosIQ invoice and receipt
    extraction, CostIQ line-item categorization, vendor management, and
    NexosIQ Search (semantic search) for white-label partners.

    ## Authentication

    All endpoints require an API key in the `X-API-Key` header. Keys are
    issued per partner from the admin dashboard and can be scoped to
    specific capabilities (extraction, storage, webhooks).

    ```
    X-API-Key: pnr_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    ```

    ## Tiers

    - **Extraction API** ($0.50/doc) — NexosIQ extraction only
    - **Extraction + Storage** ($0.85/doc) — includes cloud archive and
      NexosIQ Search across your partner's documents
    - **Platform Embed** (custom) — full white-label NEXOS Scan embedded in
      the partner's product via deep links + SAML/OIDC SSO

    ## Rate limits

    - 1,000 requests/minute per API key on Extraction API
    - 5,000 requests/minute on Extraction + Storage
    - Platform Embed: unlimited

  contact:
    name: NEXOS Scan Partner Support
    email: partners@nexosscan.com
    url: https://nexosscan.com/partners
  license:
    name: Proprietary
    url: https://nexosscan.com/terms
  x-powered-by: NexosIQ
  x-vendor: Titan Innovations LLC

servers:
  - url: https://api.nexosscan.com
    description: Production

security:
  - ApiKeyAuth: []

tags:
  - name: Invoices
    description: Upload documents for NexosIQ extraction. 99% accuracy.
  - name: Receipts
    description: Upload receipts for NexosIQ extraction with CostIQ categorization.
  - name: Vendors
    description: Vendor management and price tracking.
  - name: Search
    description: NexosIQ Search — semantic search across all indexed documents.
  - name: Webhooks
    description: Register endpoints for real-time event delivery.
  - name: Tenants
    description: Platform Embed tier only — manage sub-tenants under your partner account.

paths:
  /api/v1/partner/invoices/upload:
    post:
      tags: [Invoices]
      summary: Upload an invoice for NexosIQ extraction
      description: |
        Submit an invoice (PDF, JPG, PNG, WEBP) for extraction. NexosIQ processes
        the document synchronously and returns structured data within 60 seconds.

        Multi-page PDFs are handled automatically. Multi-vendor PDFs are
        auto-split into separate invoice records.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
                  description: Invoice file (max 25 MB)
                tenantId:
                  type: string
                  format: uuid
                  description: Tenant ID if submitting on behalf of a sub-tenant (Platform Embed only)
                storeId:
                  type: string
                  format: uuid
                  description: Store ID to assign the invoice to
      responses:
        "201":
          description: Invoice extracted successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ExtractedInvoice"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"

  /api/v1/partner/invoices/{id}:
    get:
      tags: [Invoices]
      summary: Get an extracted invoice by ID
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: Invoice details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ExtractedInvoice"

  /api/v1/partner/receipts/upload:
    post:
      tags: [Receipts]
      summary: Upload a receipt for NexosIQ extraction with CostIQ categorization
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
                useType:
                  type: string
                  enum: [business, personal, mixed]
                  default: business
      responses:
        "201":
          description: Receipt extracted with CostIQ categories assigned
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ExtractedReceipt"

  /api/v1/partner/search:
    get:
      tags: [Search]
      summary: NexosIQ Search — semantic search across indexed documents
      description: |
        Semantic search over indexed tenant documents — 512-dimension vector
        embeddings stored in pgvector. Finds invoices, receipts, and vendors
        by meaning, not just keywords.

        Example queries:
        - "Korean food distributor"
        - "cleaning supplies"
        - "invoices over $500 from January"
      parameters:
        - name: q
          in: query
          required: true
          schema: { type: string, minLength: 2 }
          description: Search query
        - name: type
          in: query
          schema:
            type: string
            enum: [invoice, receipt, vendor]
          description: Filter to a single entity type
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 50, default: 10 }
      responses:
        "200":
          description: Ranked results by semantic similarity
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  data:
                    type: object
                    properties:
                      results:
                        type: array
                        items:
                          $ref: "#/components/schemas/SearchResult"
                      query: { type: string }
                      totalResults: { type: integer }

  /api/v1/partner/vendors:
    get:
      tags: [Vendors]
      summary: List vendors
      parameters:
        - name: limit
          in: query
          schema: { type: integer, default: 50, maximum: 200 }
        - name: offset
          in: query
          schema: { type: integer, default: 0 }
      responses:
        "200":
          description: Vendor list
          content:
            application/json:
              schema:
                type: object
                properties:
                  vendors:
                    type: array
                    items:
                      $ref: "#/components/schemas/Vendor"

  /api/v1/partner/webhooks:
    post:
      tags: [Webhooks]
      summary: Register a webhook endpoint
      description: |
        Subscribes an HTTPS endpoint to NEXOS Scan events. Payloads are
        signed with HMAC-SHA256 using your webhook secret.

        Events:
        - `invoice.created`
        - `invoice.processed`
        - `invoice.failed`
        - `receipt.processed`
        - `vendor.created`
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url, events]
              properties:
                url:
                  type: string
                  format: uri
                events:
                  type: array
                  items:
                    type: string
                    enum:
                      - invoice.created
                      - invoice.processed
                      - invoice.failed
                      - receipt.processed
                      - vendor.created
      responses:
        "201":
          description: Webhook registered
          content:
            application/json:
              schema:
                type: object
                properties:
                  id: { type: string, format: uuid }
                  secret:
                    type: string
                    description: HMAC secret — shown once, store it securely
                  url: { type: string }
                  events: { type: array, items: { type: string } }

  /api/v1/partner/tenants:
    post:
      tags: [Tenants]
      summary: Create a sub-tenant under your partner account (Platform Embed only)
      description: |
        Provisions a new NEXOS Scan tenant under your partner account. Returns
        a setup link that can be deep-linked into your product for the tenant
        to complete onboarding (optionally with SAML/OIDC SSO).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, email]
              properties:
                name: { type: string }
                email: { type: string, format: email }
                maxLocations: { type: integer, default: 1 }
                ssoProvider:
                  type: string
                  enum: [saml, oidc, null]
      responses:
        "201":
          description: Tenant provisioned
          content:
            application/json:
              schema:
                type: object
                properties:
                  tenantId: { type: string, format: uuid }
                  setupUrl: { type: string, format: uri }

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key

  schemas:
    ExtractedInvoice:
      type: object
      properties:
        id: { type: string, format: uuid }
        vendorName: { type: string }
        invoiceNumber: { type: string }
        invoiceDate: { type: string, format: date }
        dueDate: { type: string, format: date }
        subtotal: { type: number }
        tax: { type: number }
        total: { type: number }
        currency: { type: string, example: "USD" }
        extractionConfidence:
          type: number
          minimum: 0
          maximum: 100
        status: { type: string, enum: [processing, processed, failed] }
        lineItems:
          type: array
          items:
            $ref: "#/components/schemas/LineItem"
        costiqCategories:
          type: array
          items: { type: string }
          description: Unique CostIQ category labels present on this invoice
        createdAt: { type: string, format: date-time }

    ExtractedReceipt:
      type: object
      properties:
        id: { type: string, format: uuid }
        merchantName: { type: string }
        receiptDate: { type: string, format: date }
        subtotal: { type: number }
        tax: { type: number }
        tip: { type: number }
        total: { type: number }
        taxCategory: { type: string, example: "Meals" }
        useType: { type: string, enum: [business, personal, mixed] }
        lineItems:
          type: array
          items:
            $ref: "#/components/schemas/LineItem"

    LineItem:
      type: object
      properties:
        description: { type: string }
        sku: { type: string }
        quantity: { type: number }
        unit: { type: string }
        unitPrice: { type: number }
        total: { type: number }
        costiqCategory:
          type: string
          description: One of 19 CostIQ categories (food_cost, supply_cost, equipment, etc.)

    Vendor:
      type: object
      properties:
        id: { type: string, format: uuid }
        name: { type: string }
        normalizedName: { type: string }
        invoiceCount: { type: integer }
        totalSpent: { type: number }

    SearchResult:
      type: object
      properties:
        type: { type: string, enum: [invoice, receipt, vendor] }
        id: { type: string, format: uuid }
        similarity:
          type: integer
          description: Semantic similarity percentage (0-100)
        preview: { type: string }
        vendorName: { type: string }
        merchantName: { type: string }
        total: { type: number }
        date: { type: string, format: date }

  responses:
    BadRequest:
      description: Request validation failed
      content:
        application/json:
          schema:
            type: object
            properties:
              error: { type: string }
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema:
            type: object
            properties:
              error: { type: string }
    RateLimited:
      description: Rate limit exceeded
      content:
        application/json:
          schema:
            type: object
            properties:
              error: { type: string }
              retryAfter: { type: integer }
