{
  "openapi": "3.1.0",
  "info": {
    "title": "Quindart Public API",
    "version": "0.2.0",
    "description": "Public service surface of Quindart. The catalog lists machine-readable services; orders follow a RAILS-aligned lifecycle (negotiation → binding → execution → evidence → verification → settlement → finality) with WebCrypto-signed receipts at finality. Humans pay via Stripe Checkout; unauthenticated order creation returns HTTP 402 with a checkout link (x402 skeleton). Read /llms.txt and /.well-known/agent.md before use."
  },
  "servers": [
    { "url": "https://quindart.com" }
  ],
  "paths": {
    "/api/catalog": {
      "get": {
        "summary": "List buyable services",
        "description": "Returns the full catalog with slug, name, description, price (null for quote-based), unit, and delivery mode.",
        "responses": {
          "200": {
            "description": "Catalog",
            "content": {
              "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" }, "services": { "type": "array" } } } }
            }
          },
          "503": { "description": "Catalog temporarily unavailable" }
        }
      }
    },
    "/api/orders": {
      "post": {
        "summary": "Create an order (RAILS negotiation)",
        "description": "Body: { serviceSlug, tier?, name, email, company?, notes?, reference? }. Returns the order. Unauthenticated requests get HTTP 402 with x-quindart-payment-required header and a checkout_url; Bearer quindart_… key holders get 201.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["serviceSlug", "name", "email"],
                "properties": {
                  "serviceSlug": { "type": "string" },
                  "tier": { "type": "string" },
                  "name": { "type": "string", "minLength": 2, "maxLength": 160 },
                  "email": { "type": "string", "format": "email", "maxLength": 254 },
                  "company": { "type": "string", "maxLength": 200 },
                  "notes": { "type": "string", "maxLength": 2000 },
                  "reference": { "type": "string", "maxLength": 200 }
                }
              }
            }
          }
        },
        "responses": {
          "201": { "description": "Order created (authenticated)" },
          "402": { "description": "Payment required — includes checkout_url" },
          "404": { "description": "Unknown service" },
          "503": { "description": "Orders temporarily unavailable" }
        }
      },
      "get": {
        "summary": "List orders (admin only)",
        "description": "Requires x-quindart-admin header. Returns recent orders with their RAILS status.",
        "responses": {
          "200": { "description": "Orders" },
          "401": { "description": "Admin credentials required" }
        }
      }
    },
    "/api/orders/{id}": {
      "get": {
        "summary": "Get order status",
        "description": "Returns the order without contact details.",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "Order" },
          "404": { "description": "Order not found" }
        }
      }
    },
    "/api/orders/{id}/advance": {
      "post": {
        "summary": "Advance an order (admin only)",
        "description": "Requires x-quindart-admin. Body: { status } with one of execution, evidence, verification, settlement, finality, cancelled. Forward moves only.",
        "responses": {
          "200": { "description": "Order advanced" },
          "401": { "description": "Admin credentials required" },
          "409": { "description": "Invalid transition" }
        }
      }
    },
    "/api/orders/{id}/receipt": {
      "get": {
        "summary": "Signed receipt (only after finality)",
        "description": "Returns { receipt, signature } where receipt is the canonical payload and signature the HMAC-SHA256 over it. Verify with POST to the same path.",
        "responses": {
          "200": { "description": "Receipt + signature" },
          "409": { "description": "Order has not reached finality" },
          "503": { "description": "Receipt signing not configured" }
        }
      },
      "post": {
        "summary": "Verify a receipt signature",
        "description": "Body: { receipt, signature }. No account required.",
        "responses": {
          "200": {
            "description": "Verification result",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" }, "valid": { "type": "boolean" } } } } }
          }
        }
      }
    },
    "/api/checkout": {
      "post": {
        "summary": "Open Stripe Checkout for an order",
        "description": "Body: { orderId }. Only fixed-price services check out; quote-based services return 409. The checkout session is bound to the order.",
        "responses": {
          "200": { "description": "Checkout session created with url" },
          "404": { "description": "Order not found" },
          "409": { "description": "Quote-based or already open" },
          "503": { "description": "Checkout unavailable" }
        }
      }
    },
    "/api/keys": {
      "post": {
        "summary": "Issue an API key (admin only)",
        "description": "Requires x-quindart-admin. Body: { name }. Returns the full key once (format quindart_…).",
        "responses": {
          "201": { "description": "Key issued" },
          "401": { "description": "Admin credentials required" }
        }
      },
      "get": {
        "summary": "List API keys (admin only)",
        "responses": {
          "200": { "description": "Key summaries without secrets" },
          "401": { "description": "Admin credentials required" }
        }
      }
    },
    "/api/seller-onboarding": {
      "post": {
        "summary": "Request a seller listing",
        "description": "Businesses list their services so AI agents can buy them. Form data; delivered to a human reviewer.",
        "responses": {
          "202": { "description": "Listing request accepted" },
          "400": { "description": "Validation failed" }
        }
      }
    },
    "/api/assessment": {
      "post": {
        "summary": "Submit a workflow assessment inquiry",
        "description": "Accepts application/x-www-form-urlencoded or multipart/form-data with the assessment fields. Returns an audit request id; a qualified Workflow Audit submission can continue to Stripe Checkout.",
        "requestBody": {
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": { "$ref": "#/components/schemas/AssessmentBody" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Assessment accepted",
            "content": {
              "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" }, "requestId": { "type": "string" } } } }
            }
          },
          "400": { "description": "Validation failed" }
        }
      }
    },
    "/api/project-inquiry": {
      "post": {
        "summary": "Submit a project inquiry for a listed service",
        "description": "Accepts application/x-www-form-urlencoded or multipart/form-data. Delivered to a human owner for scoping.",
        "requestBody": {
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": { "$ref": "#/components/schemas/ProjectInquiryBody" }
            }
          }
        },
        "responses": {
          "200": { "description": "Inquiry accepted" },
          "400": { "description": "Validation failed" },
          "415": { "description": "Send the project inquiry as form data" }
        }
      }
    },
    "/api/ai-contact/challenge": {
      "post": {
        "summary": "Issue a proof-of-work challenge for agent contact",
        "description": "Consent-first contact flow. See /ai/ai-agent-contact.md. Returns a challenge that must be solved and submitted to /api/ai-contact/request.",
        "responses": {
          "200": { "description": "Challenge issued" }
        }
      }
    },
    "/api/ai-contact/request": {
      "post": {
        "summary": "Submit a solved challenge plus a relevant inquiry",
        "description": "The request body is JSON: { prefix, nonce, user: { name?, email?, company? }, inquiry: { solutionSlug, summary, tools?, desiredOutcome?, context? } }. solutionSlug must be a known solution slug.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/AiContactRequest" }
            }
          }
        },
        "responses": {
          "200": { "description": "Inquiry accepted" },
          "400": { "description": "Challenge invalid or summary too short" }
        }
      }
    },
    "/api/stripe/checkout": {
      "post": {
        "summary": "Create a Stripe Checkout session for the Workflow Audit",
        "description": "Body: { tier: 'focused' | 'connected' | 'enterprise', email?, promotionCode?, bookingToken?, attribution fields }. Returns { url } for hosted checkout. Enterprise confirmation happens before payment.",
        "responses": {
          "200": { "description": "Checkout session created" },
          "400": { "description": "Tier or coupon invalid" },
          "409": { "description": "Enterprise scope requires confirmation" },
          "503": { "description": "Payments temporarily unavailable" }
        }
      }
    },
    "/api/stripe/session": {
      "get": {
        "summary": "Check the status of a Stripe Checkout session",
        "parameters": [
          { "name": "session_id", "in": "query", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "Session status and verification result" }
        }
      }
    },
    "/api/stripe/webhook": {
      "post": {
        "summary": "Stripe webhook endpoint (not for direct use)",
        "responses": {
          "200": { "description": "Event processed" },
          "400": { "description": "Signature validation failed" }
        }
      }
    },
    "/api/careers/apply": {
      "post": {
        "summary": "Submit a job application",
        "responses": {
          "200": { "description": "Application received" },
          "400": { "description": "Validation failed" }
        }
      }
    },
    "/oauth/token": {
      "post": {
        "summary": "OAuth 2.0 client-credentials token exchange",
        "description": "Exchange a Quindart API key (client_id = key prefix, client_secret = full key) for a Bearer access token. Only grant_type=client_credentials, scope=commerce.",
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": ["grant_type", "client_id", "client_secret"],
                "properties": {
                  "grant_type": { "type": "string", "enum": ["client_credentials"] },
                  "client_id": { "type": "string" },
                  "client_secret": { "type": "string" },
                  "scope": { "type": "string", "default": "commerce" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Token issued" },
          "400": { "description": "invalid_request / unsupported_grant_type" },
          "401": { "description": "invalid_client" }
        }
      }
    },
    "/mcp": {
      "post": {
        "summary": "MCP streamable HTTP endpoint (JSON-RPC)",
        "description": "initialize, tools/list, tools/call (catalog, create_order, get_order, receipt). Accepts application/json or text/event-stream.",
        "responses": {
          "200": { "description": "JSON-RPC response" },
          "202": { "description": "Notification accepted" },
          "400": { "description": "Parse error" }
        }
      },
      "get": {
        "summary": "MCP server descriptor",
        "responses": { "200": { "description": "Server information" } }
      }
    }
  },
  "components": {
    "schemas": {
      "AssessmentBody": {
        "type": "object",
        "required": ["requestType", "auditTier", "businessType", "repetitiveWork", "name", "email"],
        "properties": {
          "requestType": { "type": "string", "enum": ["free-fit-call", "workflow-audit"] },
          "auditTier": { "type": "string", "enum": ["focused", "connected", "enterprise"] },
          "businessType": { "type": "string", "maxLength": 160 },
          "repetitiveWork": { "type": "string", "minLength": 10, "maxLength": 4000 },
          "tools": { "type": "string", "maxLength": 1000 },
          "name": { "type": "string", "minLength": 2, "maxLength": 160 },
          "email": { "type": "string", "format": "email", "maxLength": 254 }
        }
      },
      "ProjectInquiryBody": {
        "type": "object",
        "required": ["service", "name", "email", "company", "summary"],
        "properties": {
          "service": { "type": "string", "enum": ["operations-coordination", "team-handoffs", "payments-billing", "reporting-visibility", "real-world-to-3d-training", "translation-services", "custom-product"] },
          "name": { "type": "string", "minLength": 2, "maxLength": 160 },
          "email": { "type": "string", "format": "email", "maxLength": 254 },
          "company": { "type": "string", "minLength": 2, "maxLength": 200 },
          "summary": { "type": "string", "minLength": 20, "maxLength": 4000 }
        }
      },
      "AiContactRequest": {
        "type": "object",
        "required": ["prefix", "nonce", "user", "inquiry"],
        "properties": {
          "prefix": { "type": "string" },
          "nonce": { "type": "string" },
          "user": { "type": "object", "properties": { "name": { "type": "string", "maxLength": 160 }, "email": { "type": "string", "format": "email" }, "company": { "type": "string", "maxLength": 180 } } },
          "inquiry": {
            "type": "object",
            "required": ["solutionSlug", "summary"],
            "properties": {
              "solutionSlug": { "type": "string" },
              "summary": { "type": "string", "minLength": 20, "maxLength": 4000 },
              "tools": { "type": "array", "items": { "type": "string", "maxLength": 80 }, "maxItems": 20 },
              "desiredOutcome": { "type": "string", "maxLength": 1000 }
            }
          }
        }
      }
    }
  }
}