{
  "openapi": "3.1.0",
  "info": {
    "title": "Save Discover Later — Agent API",
    "version": "1.0.0",
    "summary": "Programmatic bookmarking service for high-volume URL archival.",
    "description": "Save URLs on behalf of human users. Implements the Machine Payments Protocol (MPP): unauthenticated calls return 402 Payment Required with a WWW-Authenticate: Payment header per the Stripe Agentic Commerce specification.",
    "contact": { "name": "Save Discover Later", "email": "hello@savediscoverlater.com", "url": "https://savediscoverlater.com" },
    "license": { "name": "Terms of Service", "url": "https://savediscoverlater.com/terms" }
  },
  "servers": [
    { "url": "https://jnexdctcorewxreylfbi.supabase.co/functions/v1", "description": "Production" }
  ],
  "externalDocs": {
    "description": "Full agent manual",
    "url": "https://savediscoverlater.com/llms-full.txt"
  },
  "tags": [
    { "name": "Saves", "description": "Create and manage saved items on behalf of a user." }
  ],
  "paths": {
    "/api-save": {
      "post": {
        "operationId": "createSave",
        "summary": "Save a URL on behalf of a user",
        "description": "Creates one saved item. Requires either a subscription API key (Authorization: Bearer) or a Stripe Shared Payment Token (Payment header) with X-On-Behalf-Of. Returns 402 Payment Required when no valid credentials are supplied — agents must respond per the MPP / Stripe Agentic Commerce spec.",
        "tags": ["Saves"],
        "security": [
          { "agentApiKey": [] },
          { "stripeSPT": [] }
        ],
        "parameters": [
          {
            "name": "X-On-Behalf-Of",
            "in": "header",
            "required": false,
            "description": "Required when authenticating with a Stripe SPT. UUID of the human user the agent is acting for.",
            "schema": { "type": "string", "format": "uuid" }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Client-generated UUID. Repeats within 24h return the original response without re-charging.",
            "schema": { "type": "string", "format": "uuid" }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/SaveRequest" },
              "example": {
                "url": "https://example.com/great-article",
                "title": "A great article",
                "notes": "Read this for the section on agentic commerce.",
                "tags": ["ai", "commerce"],
                "space_id": "4f1c9e4a-1b2d-4c6e-9a3b-8d7e6f5a4c3b"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Item saved successfully.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/SaveResponse" } }
            }
          },
          "400": {
            "description": "Invalid request body or URL.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "402": {
            "description": "Payment Required — Machine Payments Protocol response. The agent must mint a Stripe Shared Payment Token (or attach a valid subscription key) and retry.",
            "headers": {
              "WWW-Authenticate": {
                "description": "Stripe Agentic Commerce challenge.",
                "schema": { "type": "string" },
                "example": "Payment realm=\"savediscoverlater\", scheme=\"stripe-spt\", price=\"5\", currency=\"USD\""
              }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PaymentRequired" },
                "example": {
                  "error": "payment_required",
                  "reason": "no_credentials",
                  "price_cents": 5,
                  "currency": "USD",
                  "payment_url": "https://savediscoverlater.com/pricing",
                  "docs": "https://savediscoverlater.com/llms.txt"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. Only POST and OPTIONS are accepted.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "429": {
            "description": "Rate limited. Honour Retry-After.",
            "headers": {
              "Retry-After": { "schema": { "type": "integer" }, "description": "Seconds to wait before retrying." },
              "X-RateLimit-Limit": { "schema": { "type": "integer" } },
              "X-RateLimit-Remaining": { "schema": { "type": "integer" } },
              "X-RateLimit-Reset": { "schema": { "type": "integer" }, "description": "Unix timestamp when the window resets." }
            },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RateLimited" } } }
          },
          "500": {
            "description": "Internal save failure. Retry with exponential backoff (max 3).",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "agentApiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "sdl_live_*",
        "description": "Subscription API key (Basic or Pro tier). Generated at /dashboard/settings → Agent tab. Stored server-side as SHA-256."
      },
      "stripeSPT": {
        "type": "apiKey",
        "in": "header",
        "name": "Payment",
        "description": "Stripe Shared Payment Token. Per-call billing at $0.05 USD per save. Must be paired with X-On-Behalf-Of header."
      }
    },
    "schemas": {
      "SaveRequest": {
        "type": "object",
        "required": ["url"],
        "additionalProperties": false,
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "description": "URL to save. Must be a valid RFC 3986 URI with http or https scheme.",
            "pattern": "^https?://"
          },
          "title": {
            "type": "string",
            "maxLength": 300,
            "description": "Optional human-readable title. Defaults to the URL."
          },
          "notes": {
            "type": "string",
            "maxLength": 1500,
            "description": "Optional personal note (~250 words max)."
          },
          "tags": {
            "type": "array",
            "maxItems": 10,
            "items": { "type": "string", "maxLength": 32 },
            "description": "Up to 10 tags, each ≤32 characters."
          },
          "space_id": {
            "type": "string",
            "format": "uuid",
            "description": "Optional Save Box (space) UUID to attach the item to."
          }
        }
      },
      "SaveResponse": {
        "type": "object",
        "required": ["id", "url", "title", "source", "created_at", "charged_cents"],
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "url": { "type": "string", "format": "uri" },
          "title": { "type": "string" },
          "notes": { "type": ["string", "null"] },
          "tags": { "type": ["array", "null"], "items": { "type": "string" } },
          "source": { "type": "string", "enum": ["agent"] },
          "created_at": { "type": "string", "format": "date-time" },
          "charged_cents": { "type": "integer", "minimum": 0, "description": "0 for subscription saves, 5 for metered/SPT saves." },
          "payment_method": { "type": "string", "enum": ["subscription", "metered", "spt"] }
        }
      },
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": { "type": "string" },
          "detail": { "type": "string" }
        }
      },
      "PaymentRequired": {
        "type": "object",
        "required": ["error", "price_cents", "currency"],
        "properties": {
          "error": { "type": "string", "enum": ["payment_required"] },
          "reason": {
            "type": "string",
            "enum": ["no_credentials", "invalid_or_revoked_key", "invalid_spt", "missing_x_on_behalf_of_header", "no_user_resolved"]
          },
          "price_cents": { "type": "integer", "example": 5 },
          "currency": { "type": "string", "example": "USD" },
          "payment_url": { "type": "string", "format": "uri" },
          "docs": { "type": "string", "format": "uri" }
        }
      },
      "RateLimited": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": { "type": "string", "enum": ["rate_limited"] },
          "retry_after_seconds": { "type": "integer" },
          "tier": { "type": "string", "enum": ["basic", "pro", "agentic"] }
        }
      }
    }
  }
}
