openapi: 3.1.0
info:
  title: CivicMarketplace Partner API
  version: "1.0.0"
  description: |
    Read-only partner API for accessing contracts, suppliers, and reference
    taxonomy. Authenticate with a partner-scoped API key.
  contact:
    name: CivicMarketplace Engineering
    email: engineering@civicmarketplace.com

servers:
  - url: https://prod-api.civicmarketplace.com
    description: Production
  - url: https://uat-api.civicmarketplace.com
    description: UAT
  - url: https://dev-api.civicmarketplace.com
    description: Dev

security:
  - apiKey: []

tags:
  - name: Contracts
  - name: Suppliers
  - name: Taxonomy

paths:
  /health:
    get:
      summary: Liveness check
      security: []
      responses:
        "200":
          description: OK

  /v1/contracts:
    get:
      tags: [Contracts]
      summary: List contracts
      parameters:
        - { in: query, name: lead_entity, schema: { type: string } }
        - { in: query, name: coop, schema: { type: string } }
        - { in: query, name: supplier, schema: { type: string } }
        - { in: query, name: city_service, schema: { type: string } }
        - { in: query, name: q, schema: { type: string, maxLength: 200 } }
        - { in: query, name: updated_since, schema: { type: string, format: date-time } }
        - { in: query, name: cursor, schema: { type: string } }
        - {
            in: query,
            name: limit,
            schema: { type: integer, minimum: 1, maximum: 100, default: 25 },
          }
      responses:
        "200":
          description: Contract page
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ContractListResponse" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /v1/contracts/{id}:
    get:
      tags: [Contracts]
      summary: Get contract by id
      parameters:
        - { in: path, name: id, required: true, schema: { type: string } }
      responses:
        "200":
          description: Contract
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Contract" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/suppliers:
    get:
      tags: [Suppliers]
      summary: List suppliers
      parameters:
        - {
            in: query,
            name: q,
            description: "Free-text search across name, short intro, and about.",
            schema: { type: string, maxLength: 200 },
          }
        - {
            in: query,
            name: state,
            description: "Two-letter USPS code; matches suppliers serving or based in that state.",
            schema: { type: string, pattern: "^[A-Za-z]{2}$" },
          }
        - {
            in: query,
            name: certification,
            description: "Comma-separated certifications a supplier must hold (ALL of them). Each value is an abbreviation or name from /v1/certifications (e.g. DBE, MBE, WOSB, ISO 37001). Covers SBE and technical certifications.",
            schema: { type: string },
          }
        - {
            in: query,
            name: city_service,
            description: "City-service id from /v1/city-services; matches suppliers offering that service.",
            schema: { type: string },
          }
        - { in: query, name: updated_since, schema: { type: string, format: date-time } }
        - { in: query, name: cursor, schema: { type: string } }
        - {
            in: query,
            name: limit,
            schema: { type: integer, minimum: 1, maximum: 100, default: 25 },
          }
      responses:
        "200":
          description: Supplier page
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SupplierListResponse" }

  /v1/suppliers/{id}:
    get:
      tags: [Suppliers]
      summary: Get supplier by id
      parameters:
        - { in: path, name: id, required: true, schema: { type: string } }
      responses:
        "200":
          description: Supplier
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Supplier" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/lead-entities:
    get:
      tags: [Taxonomy]
      summary: List lead entities
      responses:
        "200":
          description: Named-ref page
          content:
            application/json:
              schema: { $ref: "#/components/schemas/NamedRefListResponse" }

  /v1/coops:
    get:
      tags: [Taxonomy]
      summary: List coops
      responses:
        "200":
          description: Named-ref page
          content:
            application/json:
              schema: { $ref: "#/components/schemas/NamedRefListResponse" }

  /v1/city-services:
    get:
      tags: [Taxonomy]
      summary: List city services
      responses:
        "200":
          description: Named-ref page
          content:
            application/json:
              schema: { $ref: "#/components/schemas/NamedRefListResponse" }

  /v1/certifications:
    get:
      tags: [Taxonomy]
      summary: List certifications
      description: >-
        Master list of certifications suppliers can hold — SBE/diversity
        (DBE, MBE, HUB, WOSB…) and technical (ISO 37001, AZ-104, A+…). Use the
        returned abbreviation or name as the `certification` filter on
        /v1/suppliers.
      parameters:
        - {
            in: query,
            name: q,
            description: "Search by name or abbreviation.",
            schema: { type: string, maxLength: 200 },
          }
        - {
            in: query,
            name: type,
            description: "Limit to one family.",
            schema: { type: string, enum: [sbe, technical] },
          }
        - { in: query, name: cursor, schema: { type: string } }
        - {
            in: query,
            name: limit,
            schema: { type: integer, minimum: 1, maximum: 100, default: 25 },
          }
      responses:
        "200":
          description: Certification page
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CertificationListResponse" }

components:
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      bearerFormat: "cmp_(live|test)_*"

  schemas:
    NamedRef:
      type: object
      required: [id, name]
      properties:
        id: { type: string }
        name: { type: string }

    SupplierRef:
      type: object
      required: [id, name, states_served]
      properties:
        id: { type: string }
        name: { type: string }
        location: { type: string, nullable: true, example: "Austin, TX" }
        logo: { type: string, format: uri, nullable: true }
        email: { type: string, format: email, nullable: true }
        website: { type: string, format: uri, nullable: true }
        states_served:
          type: array
          description: USPS two-letter codes for the U.S. states this supplier serves, sorted alphabetically. Empty when no service areas are configured. Present on suppliers embedded in a contract as well as on the suppliers endpoint.
          items: { type: string, minLength: 2, maxLength: 2, example: "TX" }
          example: ["CA", "TX"]

    Supplier:
      allOf:
        - $ref: "#/components/schemas/SupplierRef"
        - type: object
          required: [updated_at, certifications]
          properties:
            updated_at: { type: string, format: date-time }
            certifications:
              type: array
              description: Capped preview of the supplier's certifications (abbreviation or name), SBE + technical. Full set on the supplier detail endpoint.
              items: { type: string, example: "DBE" }

    Contract:
      type: object
      required: [id, name, suppliers, city_services, updated_at]
      properties:
        id: { type: string }
        name: { type: string }
        summary: { type: string, nullable: true }
        description: { type: string, nullable: true }
        lead_entity: { $ref: "#/components/schemas/NamedRef", nullable: true }
        coop: { $ref: "#/components/schemas/NamedRef", nullable: true }
        suppliers:
          type: array
          items: { $ref: "#/components/schemas/SupplierRef" }
        city_services:
          type: array
          items: { $ref: "#/components/schemas/NamedRef" }
        updated_at: { type: string, format: date-time }

    PageMeta:
      type: object
      required: [next_cursor, limit]
      properties:
        next_cursor: { type: string, nullable: true }
        limit: { type: integer }

    ContractListResponse:
      type: object
      required: [data, meta]
      properties:
        data: { type: array, items: { $ref: "#/components/schemas/Contract" } }
        meta: { $ref: "#/components/schemas/PageMeta" }

    SupplierListResponse:
      type: object
      required: [data, meta]
      properties:
        data: { type: array, items: { $ref: "#/components/schemas/Supplier" } }
        meta: { $ref: "#/components/schemas/PageMeta" }

    NamedRefListResponse:
      type: object
      required: [data, meta]
      properties:
        data: { type: array, items: { $ref: "#/components/schemas/NamedRef" } }
        meta: { $ref: "#/components/schemas/PageMeta" }

    Certification:
      type: object
      required: [name, type]
      properties:
        name: { type: string, example: "ISO 37001" }
        abbreviation: { type: string, nullable: true, example: "DBE" }
        type:
          type: string
          enum: [sbe, technical]
          description: "sbe = SBE/diversity certification; technical = technical/vendor certification."

    CertificationListResponse:
      type: object
      required: [data, meta]
      properties:
        data: { type: array, items: { $ref: "#/components/schemas/Certification" } }
        meta: { $ref: "#/components/schemas/PageMeta" }

    Error:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message, request_id]
          properties:
            code:
              type: string
              enum:
                - unauthorized
                - forbidden
                - not_found
                - rate_limited
                - validation_failed
                - internal_error
            message: { type: string }
            request_id: { type: string }

  responses:
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    NotFound:
      description: Resource not found or not accessible to this partner
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    RateLimited:
      description: Rate limit exceeded
      headers:
        Retry-After: { schema: { type: integer } }
        X-RateLimit-Limit: { schema: { type: integer } }
        X-RateLimit-Remaining: { schema: { type: integer } }
        X-RateLimit-Reset: { schema: { type: integer } }
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
