{
  "openapi": "3.1.0",
  "info": {
    "title":       "fivecell",
    "description": "A clearinghouse for data, built for agents.  Sell, subscribe, share — in one standard format, across any cloud.",
    "version":     "0.1.0",
    "contact":     { "email": "operator@fivecell.com" }
  },
  "servers": [
    { "url": "https://fivecell.com",      "description": "Coordinator: catalogue + auth + billing" },
    { "url": "https://api.fivecell.com",  "description": "Engine: data queries + receipts" }
  ],
  "tags": [
    { "name": "discovery", "description": "Public agent-facing discovery — no token required" },
    { "name": "buyer",     "description": "Authenticated buyer operations" },
    { "name": "seller",    "description": "Authenticated seller operations" },
    { "name": "operator",  "description": "Operator-only endpoints" },
    { "name": "engine",    "description": "Data plane — runs the queries" }
  ],
  "paths": {
    "/.well-known/fivecell.json": {
      "get": {
        "tags": ["discovery"],
        "summary": "Top-level discovery anchor",
        "description": "Static JSON document listing operator email, every public endpoint URL, supported transaction modes (sell, subscribe, share), data formats (arrow, parquet, json), and the build version.  Cached one hour.  An agent that doesn't already know fivecell's URL conventions hits this first.",
        "responses": {
          "200": {
            "description": "Discovery document",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/WellKnown" }
              }
            }
          }
        }
      }
    },
    "/v1/inventory": {
      "get": {
        "tags": ["discovery"],
        "summary": "Public paginated catalogue",
        "description": "All active listings with full metadata and per-listing `acquire` hints.  No token required.  Use the filters to narrow; use `limit` and `offset` to paginate.",
        "parameters": [
          { "name": "q",       "in": "query", "schema": { "type": "string" }, "description": "BM25 text search over title, description, tags" },
          { "name": "tag",     "in": "query", "schema": { "type": "string" } },
          { "name": "seller",  "in": "query", "schema": { "type": "string" }, "description": "Filter by seller URN" },
          { "name": "dataset", "in": "query", "schema": { "type": "string" }, "description": "Filter by dataset URN" },
          { "name": "limit",   "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 500, "default": 100 } },
          { "name": "offset",  "in": "query", "schema": { "type": "integer", "minimum": 0, "default": 0 } }
        ],
        "responses": {
          "200": {
            "description": "Paginated inventory",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/InventoryPage" }
              }
            }
          }
        }
      }
    },
    "/v1/inventory/{id}": {
      "get": {
        "tags": ["discovery"],
        "summary": "One listing's full metadata",
        "description": "Returns a single InventoryEntry with the `listing` block + the `acquire` hint.  No token required.",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "Listing with acquire hint",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/InventoryEntry" }
              }
            }
          },
          "404": { "description": "No such listing" }
        }
      }
    },
    "/v1/tokens": {
      "post": {
        "tags": ["operator"],
        "summary": "Mint a capability JWT",
        "description": "Gated by the bootstrap `x-operator-key` (out-of-band shared secret).  Returns a signed RS256 JWT plus the parsed claims.  Per-role TTL caps: operator 365d, seller 365d, buyer 30d, agent 1d.",
        "security": [{ "operatorKey": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/MintRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Minted token + claims",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/MintResponse" }
              }
            }
          },
          "400": { "description": "Invalid request shape, or `ttl_seconds` out of range for role" },
          "401": { "description": "Missing or wrong `x-operator-key`" }
        }
      }
    },
    "/v1/q": {
      "post": {
        "tags": ["engine", "buyer"],
        "summary": "Run a query against a dataset",
        "description": "The engine entry.  Scope, terms, and quota are enforced inline; the response carries an `x-fivecell-sigchain` header an agent can pin in its reasoning trace.  Lives on `api.fivecell.com`.",
        "servers": [{ "url": "https://api.fivecell.com" }],
        "security": [{ "buyerToken": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/QueryEnvelope" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Query result + sigchain header",
            "headers": {
              "x-fivecell-sigchain": {
                "description": "Base64-URL JSON envelope: request hash, response hash, serving node, RSA signature.  Verify offline with `fivecell-verify --keys jwks.json`.",
                "schema": { "type": "string" }
              }
            }
          },
          "401": { "description": "Token missing, invalid, or revoked" },
          "403": { "description": "Scope or terms denied the call" },
          "429": { "description": "Quota exceeded; `X-Quota-Reset` header gives reset seconds" }
        }
      }
    },
    "/v1/listings": {
      "get": {
        "tags": ["buyer"],
        "summary": "Authenticated catalogue search",
        "description": "Same data and filters as `/v1/inventory`, but token-gated.  Useful when the operator wants to attribute discovery traffic to a principal.",
        "security": [{ "buyerToken": [] }],
        "responses": {
          "200": { "description": "Listings array" }
        }
      },
      "post": {
        "tags": ["seller"],
        "summary": "Publish a new listing",
        "security": [{ "sellerToken": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/NewListing" }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Listing" }
              }
            }
          }
        }
      }
    },
    "/.well-known/jwks.json": {
      "get": {
        "tags": ["discovery"],
        "summary": "Operator public signing keys",
        "description": "JSON Web Key Set used to verify capability JWTs and sigchain receipts.  Cache locally; check on cache miss only.",
        "responses": {
          "200": { "description": "JWKS document" }
        }
      }
    },
    "/v1/revocations": {
      "get": {
        "tags": ["discovery"],
        "summary": "Revoked JWT jtis",
        "description": "List of revoked token `jti` values.  Stratum nodes poll every 60s; agents that cache JWTs should re-check before reusing a long-lived token.",
        "responses": {
          "200": { "description": "Revocation list with `updated_at`" }
        }
      }
    },
    "/v1/health": {
      "get": {
        "tags": ["discovery"],
        "summary": "Liveness check",
        "responses": {
          "200": { "description": "OK" }
        }
      }
    },
    "/v1/feedback": {
      "post": {
        "tags": ["discovery"],
        "summary": "Submit a note on the open feedback channel",
        "description": "Free-form note for data requests, characteristic requests, platform feedback, or anything else.  Token OPTIONAL — agents browsing /v1/inventory without credentials can submit; authenticated submitters get their `claims.sub` captured as `reporter_urn`.  Triggers a `feedback_filed` webhook event addressed to the note's `target`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/NewNote" }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Note" }
              }
            }
          }
        }
      },
      "get": {
        "tags": ["discovery"],
        "summary": "List notes (public)",
        "description": "Browse the open feedback channel.  Public read so prospective sellers can see what data buyers are requesting.",
        "parameters": [
          { "name": "target", "in": "query", "schema": { "type": "string" } },
          { "name": "kind",   "in": "query", "schema": { "type": "string" } },
          { "name": "status", "in": "query", "schema": { "type": "string" } },
          { "name": "limit",  "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 500, "default": 100 } }
        ],
        "responses": {
          "200": {
            "description": "Notes array",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": { "$ref": "#/components/schemas/Note" }
                }
              }
            }
          }
        }
      }
    },
    "/v1/feedback/{id}": {
      "get": {
        "tags": ["discovery"],
        "summary": "Read one note (public)",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "Note" } }
      },
      "patch": {
        "tags": ["operator"],
        "summary": "Update note status / append response log",
        "description": "Operator-only.  Other principals must request changes via operator@fivecell.com.  Appended lines get a UTC ISO-8601 prefix and the actor's URN.",
        "security": [{ "buyerToken": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status":          { "type": "string" },
                  "append_response": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Updated note" },
          "403": { "description": "Not an operator token" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "operatorKey": {
        "type":   "apiKey",
        "in":     "header",
        "name":   "x-operator-key",
        "description": "Bootstrap key (out-of-band shared secret).  Gates `/v1/tokens` only; everything else uses capability JWTs."
      },
      "buyerToken": {
        "type":   "apiKey",
        "in":     "header",
        "name":   "x-fivecell-token",
        "description": "Capability JWT (RS256) minted with role=buyer.  Scope and terms enforced inline."
      },
      "sellerToken": {
        "type":   "apiKey",
        "in":     "header",
        "name":   "x-fivecell-token",
        "description": "Capability JWT (RS256) minted with role=seller."
      }
    },
    "schemas": {
      "WellKnown": {
        "type": "object",
        "properties": {
          "service":        { "type": "string" },
          "version":        { "type": "string" },
          "operator_email": { "type": "string", "format": "email" },
          "modes":          { "type": "array", "items": { "type": "string", "enum": ["sell","subscribe","share"] } },
          "endpoints":      { "type": "object", "additionalProperties": { "type": "string" } },
          "data_format":    { "type": "array", "items": { "type": "string" } },
          "audit":          { "type": "string" }
        }
      },
      "InventoryPage": {
        "type": "object",
        "required": ["total","limit","offset","items"],
        "properties": {
          "total":  { "type": "integer" },
          "limit":  { "type": "integer" },
          "offset": { "type": "integer" },
          "items":  { "type": "array", "items": { "$ref": "#/components/schemas/InventoryEntry" } }
        }
      },
      "InventoryEntry": {
        "type": "object",
        "description": "A listing with its `acquire` hint flattened together.",
        "allOf": [
          { "$ref": "#/components/schemas/Listing" },
          { "type": "object",
            "required": ["acquire"],
            "properties": {
              "acquire": { "$ref": "#/components/schemas/AcquireHint" }
            }
          }
        ]
      },
      "Listing": {
        "type": "object",
        "required": ["id","dataset_urn","seller_urn","title","description","terms","pricing","hosted_by","status","created_at","updated_at"],
        "properties": {
          "id":          { "type": "string" },
          "dataset_urn": { "type": "string", "example": "fivecell://global-financials/research/q1@2026Q1" },
          "seller_urn":  { "type": "string" },
          "title":       { "type": "string" },
          "description": { "type": "string" },
          "terms":       { "$ref": "#/components/schemas/Terms" },
          "pricing":     { "$ref": "#/components/schemas/Pricing" },
          "sample":      { "type": "string", "nullable": true },
          "schema":      { "$ref": "#/components/schemas/DatasetSchema" },
          "hosted_by":   { "$ref": "#/components/schemas/HostedBy" },
          "tags":        { "type": "array", "items": { "type": "string" } },
          "status":      { "type": "string", "enum": ["draft","active","retired"] },
          "created_at":  { "type": "string", "format": "date-time" },
          "updated_at":  { "type": "string", "format": "date-time" }
        }
      },
      "NewListing": {
        "type": "object",
        "required": ["dataset_urn","title","description","terms","pricing"],
        "properties": {
          "dataset_urn": { "type": "string" },
          "title":       { "type": "string" },
          "description": { "type": "string" },
          "terms":       { "$ref": "#/components/schemas/Terms" },
          "pricing":     { "$ref": "#/components/schemas/Pricing" },
          "sample":      { "type": "string", "nullable": true },
          "schema":      { "$ref": "#/components/schemas/DatasetSchema" },
          "hosted_by":   {
            "description": "Optional.  If omitted, the listing is operator-managed and the gateway URL is generated from the new listing's id.  Sellers MUST NOT submit `kind: \"operator\"` — that path is reserved for the operator-default flow and rejected if a seller tries to use it.  Submitting `kind: \"seller\"` runs the URL through validation (https-only, no fivecell-domain impersonation, no private IPs, etc.).",
            "$ref": "#/components/schemas/HostedBy",
            "nullable": true
          },
          "tags":        { "type": "array", "items": { "type": "string" } }
        }
      },
      "HostedBy": {
        "description": "Where the dataset bytes physically live.  Operator-mode: fivecell runs the Arrow Flight endpoint on the seller's behalf at flight.fivecell.com.  Seller-mode: the seller runs their own Flight server and fivecell vouches by signing the listing (verify with /.well-known/jwks.json before trusting `url`).  In either mode the trust contract is the same: the signed listing covers this block, so any tampering invalidates the signature.",
        "oneOf": [
          {
            "type": "object",
            "required": ["kind","url"],
            "properties": {
              "kind": { "type": "string", "const": "operator" },
              "url":  { "type": "string", "example": "https://flight.fivecell.com/datasets/d1b89c1e-..." }
            }
          },
          {
            "type": "object",
            "required": ["kind","url"],
            "properties": {
              "kind":          { "type": "string", "const": "seller" },
              "url":           { "type": "string", "example": "https://flight.acme.example/datasets/q1" },
              "last_attested": { "type": "string", "format": "date-time", "nullable": true,
                                 "description": "When the attestation cron last successfully pinged this endpoint and confirmed the schema fingerprint matched.  `null` for listings that have not yet been attested." },
              "status":        { "type": "string", "enum": ["live","degraded","offline"], "default": "live",
                                 "description": "Latest attestation outcome.  Inventory hides `offline` by default; `degraded` is surfaced with a warning." }
            }
          }
        ]
      },
      "Terms": {
        "type": "object",
        "properties": {
          "version":          { "type": "integer", "const": 1 },
          "license":          { "type": "object" },
          "allowed_ops":      { "type": "array", "items": { "type": "string" } },
          "export_bytes_cap": { "type": "integer", "format": "int64" },
          "privacy_mode":     { "type": "object" },
          "embargo_until":    { "type": "string", "format": "date-time", "nullable": true },
          "expires_at":       { "type": "string", "format": "date-time", "nullable": true }
        }
      },
      "Pricing": {
        "type": "object",
        "properties": {
          "model": {
            "oneOf": [
              { "type": "object", "properties": { "model": { "const": "free" } } },
              { "type": "object", "properties": { "model": { "const": "per_query" },    "cents":           { "type": "integer" } } },
              { "type": "object", "properties": { "model": { "const": "per_row" },      "milli_cents":     { "type": "integer" } } },
              { "type": "object", "properties": { "model": { "const": "per_byte" },     "micro_cents":     { "type": "integer" } } },
              { "type": "object", "properties": { "model": { "const": "subscription" }, "cents_per_month": { "type": "integer" } } }
            ]
          },
          "note": { "type": "string", "nullable": true }
        }
      },
      "DatasetSchema": {
        "type": "object",
        "nullable": true,
        "properties": {
          "columns": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["name","dtype"],
              "properties": {
                "name":        { "type": "string" },
                "dtype":       { "type": "string", "example": "float64" },
                "nullable":    { "type": "boolean", "default": true },
                "description": { "type": "string", "nullable": true }
              }
            }
          },
          "row_count_estimate": { "type": "integer", "format": "int64", "nullable": true },
          "format":             { "type": "string", "example": "parquet", "nullable": true }
        }
      },
      "AcquireHint": {
        "type": "object",
        "required": ["method","url","headers","body_template","allowed_ops","required_scope","pricing","mint_token_at","flight_url","flight_hosted_by"],
        "properties": {
          "method":         { "type": "string", "example": "POST" },
          "url":            { "type": "string", "example": "https://api.fivecell.com/v1/q",
                              "description": "Control-plane query endpoint.  For small typed results the agent POSTs here; for bulk data the agent uses `flight_url` instead." },
          "headers": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name":        { "type": "string" },
                "value":       { "type": "string" },
                "description": { "type": "string" }
              }
            }
          },
          "body_template":   { "type": "object", "description": "JSON template with placeholders the agent fills in." },
          "allowed_ops":     { "type": "array", "items": { "type": "string" } },
          "required_scope": {
            "type": "object",
            "properties": {
              "urn_pattern": { "type": "string" },
              "ops":         { "type": "array", "items": { "type": "string" } }
            }
          },
          "pricing":         { "$ref": "#/components/schemas/Pricing" },
          "mint_token_at":   { "type": "string", "example": "https://fivecell.com/v1/tokens" },
          "flight_url":      { "type": "string", "example": "https://flight.fivecell.com/datasets/d1b89c1e-...",
                               "description": "Arrow Flight endpoint serving the bulk dataset bytes (gRPC + TLS).  Derived from the listing's `hosted_by`: either operator-managed at flight.fivecell.com, or seller-operated with fivecell's signature on the listing as the vouching anchor.  Agents that want bulk data connect here directly rather than going through the control-plane query endpoint above." },
          "flight_hosted_by":{ "type": "string", "enum": ["operator","seller"],
                               "description": "`operator` — fivecell runs the Flight server.  `seller` — the seller runs it themselves; verify the listing signature with /.well-known/jwks.json before trusting flight_url." }
        }
      },
      "MintRequest": {
        "type": "object",
        "required": ["sub","role","scope","ttl_seconds"],
        "properties": {
          "sub":  { "type": "string", "example": "fivecell://acme/agents/scout" },
          "role": { "type": "string", "enum": ["operator","seller","buyer","agent"] },
          "scope": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "urn_pattern": { "type": "string", "example": "fivecell://acme/*/*" },
                "ops":         { "type": "array", "items": { "type": "string" } }
              }
            }
          },
          "ttl_seconds":   { "type": "integer", "example": 2592000 },
          "terms_version": { "type": "integer", "nullable": true }
        }
      },
      "MintResponse": {
        "type": "object",
        "properties": {
          "token":  { "type": "string", "description": "Compact RS256 JWT" },
          "claims": { "type": "object" }
        }
      },
      "QueryEnvelope": {
        "type": "object",
        "required": ["op","dataset"],
        "properties": {
          "op":      { "type": "string", "example": "aggregate" },
          "dataset": { "type": "string", "example": "fivecell://global-financials/research/q1@2026Q1" },
          "columns": { "type": "array", "items": { "type": "string" } },
          "aggregate": { "type": "string", "nullable": true, "example": "sum" }
        }
      },
      "NewNote": {
        "type": "object",
        "required": ["subject","body"],
        "properties": {
          "target":  { "type": "string", "default": "operator", "description": "Free-text: \"operator\", a seller URN, \"any seller\", a dataset URN, \"platform\"." },
          "kind":    { "type": "string", "default": "feedback", "description": "Free-text classification: \"data_request\", \"feedback\", \"bug\", ..." },
          "subject": { "type": "string" },
          "body":    { "type": "string" },
          "contact": { "type": "string", "nullable": true, "description": "Optional reply-to (email or URL) for anonymous submitters." }
        }
      },
      "Note": {
        "type": "object",
        "required": ["id","target","kind","subject","body","status","response_log","created_at","updated_at"],
        "properties": {
          "id":           { "type": "string" },
          "target":       { "type": "string" },
          "kind":         { "type": "string" },
          "subject":      { "type": "string" },
          "body":         { "type": "string" },
          "reporter_urn": { "type": "string", "nullable": true },
          "contact":      { "type": "string", "nullable": true },
          "status":       { "type": "string" },
          "response_log": { "type": "string" },
          "created_at":   { "type": "string", "format": "date-time" },
          "updated_at":   { "type": "string", "format": "date-time" }
        }
      }
    }
  }
}
