openapi: 3.1.0
info:
  title: design.live Brand API
  version: 1.0.0
  description: Stable brand identity, context, asset, and evidence contracts for applications and agents.
servers:
  # The site origin serves the app and MCP routes; api.design.live serves the
  # stable brand/asset contracts. No development loopback belongs in a document
  # published at https://design.live/openapi.yaml.
  - url: https://design.live
    description: Site, MCP, and /api routes
  - url: https://api.design.live
    description: Stable brand and asset API
paths:
  /api/billing/status:
    get:
      servers:
        - url: https://design.live
      summary: Check whether founding checkout is accepting payment
      description: Returns configuration booleans only; no provider credentials or price identifiers are exposed.
      responses:
        "200":
          description: Current checkout readiness
          content:
            application/json:
              schema:
                type: object
                required: [plan, checkoutConfigured, webhookConfigured, ready]
                properties:
                  plan: { type: string, const: founding }
                  checkoutConfigured: { type: boolean }
                  webhookConfigured: { type: boolean }
                  ready: { type: boolean }
  /v1/coverage:
    get:
      summary: Get the current public evidence coverage snapshot
      description: Sanitized aggregate metadata from the canonical evidence store. It contains no private screen URLs or provider credentials.
      responses:
        "200":
          description: Current tracked-brand coverage
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/BrandCoverage" }
                  meta:
                    type: object
                    required: [schemaVersion, generatedAt, accessStatus, trackedBrands, screens, flows]
                    additionalProperties: true
  /v1/brands:
    get:
      summary: Search brands
      parameters:
        - in: query
          name: q
          schema: { type: string }
        - in: query
          name: limit
          schema: { type: integer, minimum: 1, maximum: 50, default: 20 }
        - in: query
          name: vertical
          description: Taxonomy slug such as financial-services or social-media.
          schema: { type: string }
        - in: query
          name: archetype
          description: Product archetype such as marketplace, saas, or streaming-platform.
          schema: { type: string }
        - in: query
          name: audience
          description: Audience slug such as developers, creators, consumers, or businesses.
          schema: { type: string }
        - in: query
          name: interaction
          description: Interaction domain such as payments-billing, collaboration, or search-discovery.
          schema: { type: string }
        - in: query
          name: style
          description: Exact visual-style attribute from normalized brand context.
          schema: { type: string }
        - in: query
          name: platform
          schema: { type: string, enum: [ios, android, web, site] }
        - in: query
          name: evidence
          description: Mobbin capture coverage status.
          schema: { type: string }
      responses:
        "200":
          description: Matching brands
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/BrandSummary" }
                  meta: { type: object, additionalProperties: true }
  /v1/brands/{identifier}:
    get:
      summary: Get a normalized brand record
      parameters:
        - in: path
          name: identifier
          required: true
          description: Canonical domain, slug, or exact brand name.
          schema: { type: string }
      responses:
        "200":
          description: Brand record
          content:
            application/json:
              schema:
                type: object
                required: [data, links]
                properties:
                  data: { $ref: "#/components/schemas/Brand" }
                  links: { type: object, additionalProperties: true }
        "404": { $ref: "#/components/responses/NotFound" }
  /v1/domain/{domain}/{variant}:
    get:
      servers:
        - url: https://cdn.design.live
      summary: Resolve the current approved brand asset
      parameters:
        - in: path
          name: domain
          required: true
          schema: { type: string }
        - in: path
          name: variant
          required: true
          schema: { type: string, enum: [icon, logo, symbol] }
        - in: query
          name: theme
          schema: { type: string, enum: [auto, light, dark], default: auto }
      responses:
        "307": { description: Temporary redirect to the current immutable asset }
        "404": { $ref: "#/components/responses/NotFound" }
  /v1/screens/{sha256}:
    get:
      summary: Resolve a canonical Mobbin screen by content hash
      description: Private in production. The local prototype allows direct access for development.
      parameters:
        - in: path
          name: sha256
          required: true
          schema: { type: string, pattern: "^[a-f0-9]{64}$" }
      responses:
        "200":
          description: Canonical screen image
          content:
            image/png: {}
            image/jpeg: {}
            image/webp: {}
        "401": { description: Private asset token required in production }
        "404": { $ref: "#/components/responses/NotFound" }
  /api/rights:
    post:
      servers:
        - url: https://design.live
      summary: Submit a correction, takedown, brand claim, or permission update
      description: Same-origin public intake. Requests are privately queued with replay protection, consent, and database-enforced abuse limits; raw IP addresses are not stored.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/RightsRequest" }
      responses:
        "201":
          description: A new request was accepted
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RightsReceipt" }
        "200":
          description: A duplicate request received its existing receipt
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RightsReceipt" }
        "400": { description: Invalid request fields or missing consent }
        "403": { description: Cross-origin request rejected }
        "413": { description: Request body exceeds 24 KB }
        "415": { description: Request body is not JSON }
        "429": { description: Fingerprint rate limit exceeded }
components:
  schemas:
    BrandCoverage:
      type: object
      required: [slug, name, domain, category, screens, annotatedScreens, flows, annotatedFlows, platforms]
      properties:
        slug: { type: string }
        name: { type: string }
        domain: { type: string }
        category: { type: string }
        screens: { type: integer, minimum: 0 }
        annotatedScreens: { type: integer, minimum: 0 }
        flows: { type: integer, minimum: 0 }
        annotatedFlows: { type: integer, minimum: 0 }
        platforms: { type: array, items: { type: string } }
        brandContextUpdatedAt: { type: [string, "null"], format: date-time }
    RightsRequest:
      type: object
      required: [requestType, requesterEmail, details, consent]
      properties:
        requestType: { type: string, enum: [correction, takedown, brand_claim, permission_update] }
        requesterName: { type: [string, "null"], maxLength: 200 }
        requesterEmail: { type: string, format: email, maxLength: 320 }
        evidenceUrl: { type: [string, "null"], format: uri, maxLength: 2000 }
        details: { type: string, minLength: 10, maxLength: 10000 }
        consent: { type: boolean, const: true }
    RightsReceipt:
      type: object
      required: [accepted, receipt, requestType, status, submittedAt]
      properties:
        accepted: { type: boolean, const: true }
        duplicate: { type: boolean }
        receipt: { type: string, format: uuid }
        requestType: { type: string }
        status: { type: string, enum: [received, triaging, approved, rejected, completed] }
        submittedAt: { type: string, format: date-time }
    BrandSummary:
      type: object
      required: [id, slug, name, domain, colors, evidence, updatedAt]
      properties:
        id: { type: string }
        slug: { type: string }
        name: { type: string }
        domain: { type: string }
        description: { type: [string, "null"] }
        colors: { type: array, items: { type: object, additionalProperties: true } }
        logo: { type: [string, "null"], format: uri }
        evidence: { type: object, additionalProperties: true }
        facets: { type: [object, "null"], additionalProperties: true }
        assetCoverage: { type: [object, "null"], additionalProperties: true }
        updatedAt: { type: string, format: date-time }
    Brand:
      allOf:
        - { $ref: "#/components/schemas/BrandSummary" }
        - type: object
          properties:
            fonts: { type: array, items: { type: object, additionalProperties: true } }
            context: { type: object, additionalProperties: true }
            assets: { type: array, items: { type: object, additionalProperties: true } }
            provenance: { type: array, items: { type: object, additionalProperties: true } }
  responses:
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: object
                required: [code, message]
                properties:
                  code: { type: string }
                  message: { type: string }
