{
  "openapi": "3.1.0",
  "info": {
    "title": "pssst — Agentic API",
    "version": "1.0.0",
    "description": "Bearer-auth REST API for autonomous agents to discover topics, run on-demand scans, and read briefs. Reads + topic CRUD are free; on-demand scans cost €0.50/call (first 5 free per account). See /llms.txt for a plain-text agent guide.",
    "contact": {
      "name": "Aurolabs AB",
      "email": "hello@aurolabs.ai",
      "url": "https://pssst.fyi"
    },
    "termsOfService": "https://pssst.fyi/terms"
  },
  "servers": [
    { "url": "https://pssst.fyi/v1", "description": "Production" }
  ],
  "components": {
    "securitySchemes": {
      "BearerKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "sk_live_<48-hex>",
        "description": "Issued from /dash → API tab. Stored as SHA-256 hash; raw key shown once."
      }
    },
    "parameters": {
      "TopicId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": { "type": "string" }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "schema": { "type": "string", "format": "uuid" },
        "description": "Recommended on POSTs. 24h replay window; repeated request returns the cached response with Idempotent-Replayed: true and does not re-debit."
      }
    },
    "schemas": {
      "Account": {
        "type": "object",
        "properties": {
          "user_id":               { "type": "string" },
          "email":                 { "type": "string", "format": "email" },
          "tier":                  { "type": "string", "enum": ["free", "pssst"] },
          "credit_balance_cents":  { "type": "integer" },
          "credit_balance_eur":    { "type": "string" },
          "api_free_calls_used":   { "type": "integer" },
          "api_free_calls_total":  { "type": "integer" },
          "per_call_cost_cents":   { "type": "integer", "description": "Cost of POST /v1/topics/:id/scan in cents." }
        }
      },
      "Topic": {
        "type": "object",
        "properties": {
          "id":           { "type": "string" },
          "title":        { "type": "string" },
          "summary":      { "type": "string" },
          "status":       { "type": "string", "enum": ["active", "paused"] },
          "cadence":      { "type": "string", "enum": ["daily", "weekly", "monthly"] },
          "scan_mode":    { "type": "string", "enum": ["api", "cron"], "description": "API-created topics are scan_mode=api (agent-managed). Dashboard topics are scan_mode=cron (auto-scan on cadence)." },
          "last_scan_at": { "type": "string", "format": "date-time", "nullable": true },
          "scan_count":   { "type": "integer" },
          "created_at":   { "type": "string", "format": "date-time" }
        }
      },
      "Brief": {
        "type": "object",
        "properties": {
          "brief":    { "type": "string" },
          "sit_with": { "type": "string" },
          "sources":  { "type": "array", "items": { "$ref": "#/components/schemas/Source" } },
          "fresh":    { "type": "boolean" },
          "scan_at":  { "type": "string", "format": "date-time" },
          "billing":  { "$ref": "#/components/schemas/BillingInfo" }
        }
      },
      "Source": {
        "type": "object",
        "properties": {
          "title":        { "type": "string" },
          "outlet":       { "type": "string" },
          "url":          { "type": "string", "format": "uri" },
          "published_at": { "type": "string", "format": "date" }
        }
      },
      "BillingInfo": {
        "type": "object",
        "properties": {
          "paid":                 { "type": "boolean", "description": "true = debited credits, false = used a free call" },
          "cost_cents":           { "type": "integer" },
          "balance_cents":        { "type": "integer" },
          "free_calls_remaining": { "type": "integer" }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error":         { "type": "string" },
          "balance_cents": { "type": "integer", "nullable": true },
          "topup_url":     { "type": "string", "nullable": true },
          "retry_after_s": { "type": "integer", "nullable": true }
        }
      }
    }
  },
  "security": [{ "BearerKey": [] }],
  "paths": {
    "/account": {
      "get": {
        "summary": "Account state + credit balance",
        "operationId": "getAccount",
        "responses": {
          "200": {
            "description": "OK",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Account" } } }
          },
          "401": { "description": "Missing/invalid/revoked key" }
        }
      }
    },
    "/topics": {
      "get": {
        "summary": "List all topics owned by the key's account (web + API)",
        "operationId": "listTopics",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Topic" } } }
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create new topic(s) from a brain-dump description",
        "description": "Splits the description into 1-5 focused topics via LLM. New topics are scan_mode=api (agent-managed, no cron auto-scan). Free.",
        "operationId": "createTopics",
        "parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["description"],
                "properties": {
                  "description": { "type": "string", "maxLength": 1000, "description": "Brain-dump in any language, e.g. 'OPEC+ output decisions'." }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Topic" } } }
                }
              }
            }
          },
          "400": { "description": "missing_description / description_too_long" },
          "422": { "description": "no_topics_found" },
          "502": { "description": "split_failed (upstream LLM)" }
        }
      }
    },
    "/topics/{id}": {
      "get": {
        "summary": "Read one topic",
        "operationId": "getTopic",
        "parameters": [{ "$ref": "#/components/parameters/TopicId" }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Topic" } } } },
          "404": { "description": "topic_not_found" }
        }
      },
      "patch": {
        "summary": "Pause/resume + change cadence",
        "operationId": "patchTopic",
        "parameters": [{ "$ref": "#/components/parameters/TopicId" }],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status":  { "type": "string", "enum": ["active", "paused"] },
                  "cadence": { "type": "string", "enum": ["daily", "weekly", "monthly"] }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Topic" } } } },
          "400": { "description": "bad_status / bad_cadence" },
          "404": { "description": "topic_not_found" }
        }
      },
      "delete": {
        "summary": "Delete one topic",
        "operationId": "deleteTopic",
        "parameters": [{ "$ref": "#/components/parameters/TopicId" }],
        "responses": {
          "200": { "description": "OK" },
          "404": { "description": "topic_not_found" }
        }
      }
    },
    "/topics/{id}/briefs": {
      "get": {
        "summary": "List archived briefs for this topic",
        "operationId": "listBriefs",
        "parameters": [
          { "$ref": "#/components/parameters/TopicId" },
          { "name": "since", "in": "query", "schema": { "type": "string", "format": "date-time" } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 200, "default": 50 } }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Brief" } } }
                }
              }
            }
          }
        }
      }
    },
    "/topics/{id}/briefs/latest": {
      "get": {
        "summary": "Read the most recent brief (cached, free)",
        "operationId": "getLatestBrief",
        "parameters": [{ "$ref": "#/components/parameters/TopicId" }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Brief" } } } },
          "404": { "description": "no_briefs_yet" }
        }
      }
    },
    "/topics/{id}/scan": {
      "post": {
        "summary": "Run on-demand scan and return the brief",
        "description": "Debits 1 free call OR €0.50 from the credit balance atomically before invoking the LLM. Hard scan failures refund the debit automatically.",
        "operationId": "runScan",
        "parameters": [
          { "$ref": "#/components/parameters/TopicId" },
          { "$ref": "#/components/parameters/IdempotencyKey" }
        ],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Brief" } } } },
          "402": {
            "description": "insufficient_credits",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "404": { "description": "topic_not_found" },
          "409": { "description": "topic_paused" },
          "429": {
            "description": "rate_limited",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "502": { "description": "scan_failed (upstream LLM); debit refunded" }
        }
      }
    },
    "/credits": {
      "get": {
        "summary": "Credit balance + last 50 ledger entries",
        "operationId": "getCredits",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "balance_cents": { "type": "integer" },
                    "balance_eur":   { "type": "string" },
                    "free_calls":    { "type": "object" },
                    "ledger":        { "type": "array" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/credits/topup": {
      "post": {
        "summary": "Generate a Stripe Checkout URL for a top-up pack",
        "description": "Returns a Stripe-hosted Checkout URL for one-time payment. On success, webhook credits the account.",
        "operationId": "topupCredits",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["pack"],
                "properties": {
                  "pack": { "type": "string", "enum": ["5", "10", "25", "50"] }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "checkout_url": { "type": "string", "format": "uri" },
                    "session_id":   { "type": "string" }
                  }
                }
              }
            }
          },
          "400": { "description": "unknown_pack" }
        }
      }
    }
  }
}
