{
  "openapi": "3.1.1",
  "info": {
    "title": "blackball reflection prompt API",
    "version": "1.0.0",
    "summary": "Draw a private, playful prompt for reflection.",
    "description": "Returns one curated reflection prompt without accepting or storing the end user's question. Responses are for entertainment and reflection, not advice or prediction.",
    "termsOfService": "https://blackball.app/#terms",
    "contact": {
      "name": "blackball",
      "url": "https://blackball.app/developers"
    },
    "license": {
      "name": "All rights reserved",
      "url": "https://blackball.app/#terms"
    }
  },
  "externalDocs": {
    "description": "Integration and safety guide",
    "url": "https://blackball.app/developers"
  },
  "servers": [
    {
      "url": "https://blackball.app"
    }
  ],
  "security": [],
  "tags": [
    {
      "name": "Reflection",
      "description": "Privacy-first, non-authoritative prompts for playful reflection."
    }
  ],
  "paths": {
    "/api/v1/prompts/random": {
      "get": {
        "operationId": "getDecisionPrompt",
        "summary": "Draw a reflection prompt",
        "description": "Returns one random curated prompt. Do not send the end user's question: this operation has no parameters and accepts no request body. The result is a playful reflection aid, not advice, analysis, or a prediction, and must not be used as the basis for medical, legal, financial, safety-critical, crisis, or other high-stakes decisions.",
        "tags": ["Reflection"],
        "parameters": [],
        "security": [],
        "responses": {
          "200": {
            "description": "A reflection prompt was drawn.",
            "headers": {
              "Cache-Control": {
                "description": "Responses must not be stored by caches.",
                "schema": { "type": "string", "const": "no-store, max-age=0" }
              },
              "X-RateLimit-Limit": {
                "description": "Best-effort anonymous request allowance per runtime window.",
                "schema": { "type": "integer" }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests remaining in the current runtime window.",
                "schema": { "type": "integer" }
              },
              "X-RateLimit-Reset": {
                "description": "Approximate window reset as a Unix timestamp in seconds.",
                "schema": { "type": "integer" }
              }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PromptResponse" }
              }
            }
          },
          "400": {
            "description": "Query parameters or a request body were supplied and rejected before a prompt was drawn.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          },
          "405": {
            "description": "The endpoint was called with a method that could submit content.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          },
          "429": {
            "description": "The best-effort anonymous rate limit was exceeded.",
            "headers": {
              "Retry-After": {
                "description": "Seconds until the current runtime window resets.",
                "schema": { "type": "integer", "minimum": 1 }
              }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          },
          "500": {
            "description": "The service could not draw a prompt. No question content was received or stored."
          }
        }
      },
      "options": {
        "operationId": "getDecisionPromptCorsPolicy",
        "summary": "Read the public CORS policy",
        "responses": {
          "204": { "description": "CORS policy returned with no body." }
        }
      }
    },
    "/api/v1/draws": {
      "post": {
        "operationId": "issueWebsiteDraw",
        "summary": "Issue and record a website draw",
        "description": "Selects a catalog answer, records only an internal event UUID, versioned answer ID, and server-generated timestamp, then returns it. Send an empty POST: the private question, query parameters, and request bodies are rejected. Website clients may fall back to local catalog selection when this endpoint is unavailable; fallback draws are not recorded.",
        "tags": ["Reflection"],
        "parameters": [],
        "security": [],
        "responses": {
          "200": {
            "description": "The answer was recorded and returned.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/WebsiteDrawResponse" }
              }
            }
          },
          "400": {
            "description": "Submitted input was rejected before selection.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          },
          "405": {
            "description": "A method other than POST was used.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          },
          "429": {
            "description": "The anonymous rate limit was exceeded before persistence.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          },
          "503": {
            "description": "Persistence failed, so no backend answer was returned.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      },
      "options": {
        "operationId": "getWebsiteDrawCorsPolicy",
        "summary": "Read the website draw CORS policy",
        "responses": {
          "204": { "description": "CORS policy returned with no body." }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "PromptResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": ["schema_version", "answer", "framing", "privacy", "source_url"],
        "properties": {
          "schema_version": { "type": "string", "const": "1.0" },
          "answer": {
            "type": "object",
            "additionalProperties": false,
            "required": ["id", "text", "category", "tone"],
            "properties": {
              "id": { "type": "string", "pattern": "^v1\\.[a-z0-9-]+$" },
              "text": { "type": "string", "minLength": 1 },
              "category": {
                "type": "string",
                "enum": ["move", "wait", "reconsider", "trust", "clarify", "release"]
              },
              "tone": {
                "type": "string",
                "enum": ["affirming", "cautionary", "reflective", "uncertain"]
              }
            }
          },
          "framing": {
            "type": "object",
            "additionalProperties": false,
            "required": ["kind", "not_advice", "not_a_prediction", "high_stakes_use"],
            "properties": {
              "kind": { "type": "string", "const": "playful_reflection" },
              "not_advice": { "type": "boolean", "const": true },
              "not_a_prediction": { "type": "boolean", "const": true },
              "high_stakes_use": { "type": "boolean", "const": false }
            }
          },
          "privacy": {
            "type": "object",
            "additionalProperties": false,
            "required": ["question_received", "question_stored"],
            "properties": {
              "question_received": { "type": "boolean", "const": false },
              "question_stored": { "type": "boolean", "const": false }
            }
          },
          "source_url": {
            "type": "string",
            "format": "uri",
            "const": "https://blackball.app/"
          }
        }
      },
      "WebsiteDrawResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": ["schema_version", "answer", "recording", "privacy"],
        "properties": {
          "schema_version": { "type": "string", "const": "1.0" },
          "answer": { "$ref": "#/components/schemas/PromptResponse/properties/answer" },
          "recording": {
            "type": "object",
            "additionalProperties": false,
            "required": ["event_id", "issued_at", "semantics"],
            "properties": {
              "event_id": { "type": "string", "format": "uuid" },
              "issued_at": { "type": "string", "format": "date-time" },
              "semantics": { "type": "string", "const": "backend_issue" }
            }
          },
          "privacy": { "$ref": "#/components/schemas/PromptResponse/properties/privacy" }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": ["error"],
        "properties": {
          "error": {
            "type": "object",
            "additionalProperties": false,
            "required": ["code", "message"],
            "properties": {
              "code": { "type": "string" },
              "message": { "type": "string" }
            }
          }
        }
      }
    }
  }
}
