{
  "openapi": "3.1.0",
  "info": {
    "title": "StellarFirm public API",
    "version": "1.1.0",
    "summary": "The routes of StellarFirm that a person or an agent can call.",
    "description": "StellarFirm gives you AI Agents, Agentic Assistants for your work that you run as the CEO. API-Version: 1 selects major version 1; omitting it selects version 1. Breaking changes require a new major version. Deprecation and Sunset headers announce retirement at least 90 days ahead; see https://stellarfirm.ai/docs/api/policy. This is the small public HTTP API of the web app. Every route answers JSON, and every failure answers the same `ApiError` shape with a stable `error.code`. Authentication is described at https://stellarfirm.ai/docs/api/authentication, the routes with examples at https://stellarfirm.ai/docs/api/endpoints, and every error code at https://stellarfirm.ai/docs/api/errors.",
    "contact": {
      "name": "StellarFirm",
      "email": "hey@stellarfirm.ai",
      "url": "https://stellarfirm.ai/support"
    },
    "termsOfService": "https://stellarfirm.ai/terms"
  },
  "externalDocs": {
    "description": "StellarFirm API docs",
    "url": "https://stellarfirm.ai/docs/api"
  },
  "servers": [
    {
      "url": "https://stellarfirm.ai",
      "description": "StellarFirm"
    }
  ],
  "tags": [
    {
      "name": "Status",
      "description": "Is the site up, and is there a notice."
    },
    {
      "name": "Account",
      "description": "The signed-in account: access and credits."
    },
    {
      "name": "Email",
      "description": "Links that arrive in an email."
    },
    {
      "name": "Free trial",
      "description": "A few free prompts for a visitor with no account, from the landing page."
    }
  ],
  "paths": {
    "/api/health": {
      "get": {
        "operationId": "getHealth",
        "tags": [
          "Status"
        ],
        "summary": "Check that the web app is up",
        "description": "Answers `ok: true` while the web app is running. It needs no sign-in and reads nothing from an account. Use it for an uptime check.",
        "parameters": [
          {
            "name": "API-Version",
            "in": "header",
            "required": false,
            "description": "The public API major version. Omit it for version 1. An unsupported value returns 400. See https://stellarfirm.ai/docs/api/policy.",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ],
              "default": "1"
            }
          }
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The web app is running.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "$ref": "#/components/schemas/Health"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "The public API major version used for this response.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "Link": {
                "description": "Link to the versioning and deprecation policy (rel=deprecation).",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "IETF draft quota remaining; t is seconds until retry on refusal.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Policy": {
                "description": "IETF draft quota policy with q requests and w seconds.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Limit": {
                "description": "Compatibility field: requests allowed per window.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              },
              "RateLimit-Remaining": {
                "description": "Compatibility field: requests left for this caller.",
                "schema": {
                  "type": "integer",
                  "minimum": 0
                }
              }
            }
          },
          "400": {
            "description": "The API-Version header is unsupported. Send 1 or omit it.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "The public API major version used for this response.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "Link": {
                "description": "Link to the versioning and deprecation policy (rel=deprecation).",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "IETF draft quota remaining; t is seconds until retry on refusal.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Policy": {
                "description": "IETF draft quota policy with q requests and w seconds.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Limit": {
                "description": "Compatibility field: requests allowed per window.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              },
              "RateLimit-Remaining": {
                "description": "Compatibility field: requests left for this caller.",
                "schema": {
                  "type": "integer",
                  "minimum": 0
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "description": "Too many health checks from this address. Wait for Retry-After seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "The public API major version used for this response.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "Link": {
                "description": "Link to the versioning and deprecation policy (rel=deprecation).",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit": {
                "description": "IETF draft quota remaining; t is seconds until retry on refusal.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Policy": {
                "description": "IETF draft quota policy with q requests and w seconds.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Limit": {
                "description": "Compatibility field: requests allowed per window.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              },
              "RateLimit-Remaining": {
                "description": "Compatibility field: requests left for this caller.",
                "schema": {
                  "type": "integer",
                  "minimum": 0
                }
              },
              "Retry-After": {
                "description": "Seconds before trying again.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              }
            }
          }
        }
      }
    },
    "/api/site-banner": {
      "get": {
        "operationId": "getSiteBanner",
        "tags": [
          "Status"
        ],
        "summary": "Read the announcement strip",
        "description": "The notice shown at the top of the site, or `banner: null` when there is none. It is the same for everyone, needs no sign-in, and may be cached for half a minute. Browsers on other origins may read it.",
        "parameters": [
          {
            "name": "API-Version",
            "in": "header",
            "required": false,
            "description": "The public API major version. Omit it for version 1. An unsupported value returns 400. See https://stellarfirm.ai/docs/api/policy.",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ],
              "default": "1"
            }
          }
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The current notice, or null.",
            "headers": {
              "API-Version": {
                "description": "The public API major version used for this response.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "Link": {
                "description": "Link to the versioning and deprecation policy (rel=deprecation).",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Shared caches may keep the answer briefly.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "$ref": "#/components/schemas/SiteBannerResponse"
                }
              }
            }
          },
          "400": {
            "description": "The API-Version header is unsupported. Send 1 or omit it.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "The public API major version used for this response.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "Link": {
                "description": "Link to the versioning and deprecation policy (rel=deprecation).",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          }
        }
      }
    },
    "/api/entitlement": {
      "get": {
        "operationId": "getEntitlement",
        "tags": [
          "Account"
        ],
        "summary": "Check whether the signed-in account may run",
        "description": "Answers whether the signed-in account may run: an active trial, a licence, or free Hobby after the trial. The desktop app asks this before it runs, and an agent acting for an account can ask it too. Only a `200` with `ok: true` means the account may run. It is never cached.",
        "parameters": [
          {
            "name": "API-Version",
            "in": "header",
            "required": false,
            "description": "The public API major version. Omit it for version 1. An unsupported value returns 400. See https://stellarfirm.ai/docs/api/policy.",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ],
              "default": "1"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "The account may run (trial, licence or Hobby). Ask again within `recheckAfterSeconds`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "$ref": "#/components/schemas/EntitlementAllowed"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "The public API major version used for this response.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "Link": {
                "description": "Link to the versioning and deprecation policy (rel=deprecation).",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "The API-Version header is unsupported. Send 1 or omit it.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "The public API major version used for this response.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "Link": {
                "description": "Link to the versioning and deprecation policy (rel=deprecation).",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "No signed-in session. `status` is `signed-out` and `error.code` is `signed_out`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "$ref": "#/components/schemas/EntitlementDenied"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "The public API major version used for this response.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "Link": {
                "description": "Link to the versioning and deprecation policy (rel=deprecation).",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "403": {
            "description": "The account is signed in but may not run: its trial record could not be read (`invalid`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "$ref": "#/components/schemas/EntitlementDenied"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "The public API major version used for this response.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "Link": {
                "description": "Link to the versioning and deprecation policy (rel=deprecation).",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "503": {
            "description": "Sign-in is not set up here (`unconfigured`) or the account could not be checked (`unavailable`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "$ref": "#/components/schemas/EntitlementDenied"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "The public API major version used for this response.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "Link": {
                "description": "Link to the versioning and deprecation policy (rel=deprecation).",
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/api/credits/balance": {
      "get": {
        "operationId": "getCreditsBalance",
        "tags": [
          "Account"
        ],
        "summary": "Read the account's credit balance",
        "description": "The signed-in account's balance as a count of StellarFirm credits. It is a number of credits and nothing else. It is never cached.",
        "parameters": [
          {
            "name": "API-Version",
            "in": "header",
            "required": false,
            "description": "The public API major version. Omit it for version 1. An unsupported value returns 400. See https://stellarfirm.ai/docs/api/policy.",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ],
              "default": "1"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "The balance, in credits.",
            "headers": {
              "API-Version": {
                "description": "The public API major version used for this response.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "Link": {
                "description": "Link to the versioning and deprecation policy (rel=deprecation).",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Private and never stored.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "$ref": "#/components/schemas/CreditsBalance"
                }
              }
            }
          },
          "400": {
            "description": "The API-Version header is unsupported. Send 1 or omit it.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "The public API major version used for this response.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "Link": {
                "description": "Link to the versioning and deprecation policy (rel=deprecation).",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "No signed-in session. The `error.code` is `signed_out`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "The public API major version used for this response.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "Link": {
                "description": "Link to the versioning and deprecation policy (rel=deprecation).",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "503": {
            "description": "The answer could not be read right now. Try again shortly.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "The public API major version used for this response.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "Link": {
                "description": "Link to the versioning and deprecation policy (rel=deprecation).",
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/api/email/unsubscribe": {
      "post": {
        "operationId": "unsubscribeFromOnboardingEmails",
        "tags": [
          "Email"
        ],
        "summary": "Stop the later onboarding emails",
        "description": "One-click unsubscribe from the onboarding emails (RFC 8058). The signed `token` in the link is the credential, so no sign-in is needed. Use the link exactly as it was sent.",
        "security": [],
        "parameters": [
          {
            "name": "API-Version",
            "in": "header",
            "required": false,
            "description": "The public API major version. Omit it for version 1. An unsupported value returns 400. See https://stellarfirm.ai/docs/api/policy.",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ],
              "default": "1"
            }
          },
          {
            "name": "token",
            "in": "query",
            "required": true,
            "description": "The signed token from the link in the email.",
            "schema": {
              "type": "string",
              "minLength": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The later emails are stopped.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                },
                "example": "You will not get the later onboarding notes."
              }
            },
            "headers": {
              "API-Version": {
                "description": "The public API major version used for this response.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "Link": {
                "description": "Link to the versioning and deprecation policy (rel=deprecation).",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "The token is missing or not valid. The `error.code` is `invalid_token`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "The public API major version used for this response.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "Link": {
                "description": "Link to the versioning and deprecation policy (rel=deprecation).",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "502": {
            "description": "The later emails are still scheduled. The `error.code` is `upstream_failed`. Try the link again.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "The public API major version used for this response.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "Link": {
                "description": "Link to the versioning and deprecation policy (rel=deprecation).",
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/api/try/status": {
      "get": {
        "operationId": "getTrialStatus",
        "tags": [
          "Free trial"
        ],
        "summary": "Read the free trial's status",
        "description": "Says whether this month's free trial is still open and how many free prompts the visitor has left. The visitor is told apart by network address, with no sign-in and no cookie. It counts nothing and is never cached. It answers `404` with the code `not_found` while the free trial is switched off.",
        "parameters": [
          {
            "name": "API-Version",
            "in": "header",
            "required": false,
            "description": "The public API major version. Omit it for version 1. An unsupported value returns 400. See https://stellarfirm.ai/docs/api/policy.",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ],
              "default": "1"
            }
          }
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The trial's status for this visitor.",
            "headers": {
              "API-Version": {
                "description": "The public API major version used for this response.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "Link": {
                "description": "Link to the versioning and deprecation policy (rel=deprecation).",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "The answer is never cached.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "$ref": "#/components/schemas/TrialStatus"
                }
              }
            }
          },
          "400": {
            "description": "The API-Version header is unsupported. Send 1 or omit it.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "The public API major version used for this response.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "Link": {
                "description": "Link to the versioning and deprecation policy (rel=deprecation).",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "The free trial is switched off. The `error.code` is `not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "The public API major version used for this response.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "Link": {
                "description": "Link to the versioning and deprecation policy (rel=deprecation).",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "503": {
            "description": "The status could not be read right now. The `error.code` is `unavailable`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "The public API major version used for this response.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "Link": {
                "description": "Link to the versioning and deprecation policy (rel=deprecation).",
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/api/try/prompt": {
      "post": {
        "operationId": "sendTrialPrompt",
        "tags": [
          "Free trial"
        ],
        "summary": "Send one free prompt",
        "description": "Sends one prompt to one of three assistants (`coder`, `marketing` or `product-owner`) and answers with its work. Each visitor gets a few prompts, one at a time, and the whole trial has an allowance for the month. Send `Accept: text/event-stream` to read the work as it happens: one JSON event per `data:` line (`started`, then `activity`, `text` and `artifact` events, then `done`, or `error`). Without it the final `done` event comes back as JSON, with the artifacts. The request must come from a person in a browser on the site; a script is refused. A prompt that fails on our side before it has done any work is not counted.",
        "parameters": [
          {
            "name": "API-Version",
            "in": "header",
            "required": false,
            "description": "The public API major version. Omit it for version 1. An unsupported value returns 400. See https://stellarfirm.ai/docs/api/policy.",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ],
              "default": "1"
            }
          }
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TrialPromptRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The assistant's work, as a stream of events or as the final JSON answer.",
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string"
                },
                "example": "data: {\"type\":\"started\",\"assistant\":\"coder\",\"promptsLeft\":2}\n\ndata: {\"type\":\"done\",\"answer\":\"Done.\",\"promptsLeft\":2}\n\n"
              },
              "application/json": {
                "schema": {
                  "type": "object",
                  "$ref": "#/components/schemas/TrialPromptDone"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "The public API major version used for this response.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "Link": {
                "description": "Link to the versioning and deprecation policy (rel=deprecation).",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "The prompt is not valid, the request names no network address, or the API-Version header is unsupported. The `error.code` is `invalid_request`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "The public API major version used for this response.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "Link": {
                "description": "Link to the versioning and deprecation policy (rel=deprecation).",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "403": {
            "description": "The request did not pass the check that a person sent it. The `error.code` is `try_refused`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "The public API major version used for this response.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "Link": {
                "description": "Link to the versioning and deprecation policy (rel=deprecation).",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "The free trial is switched off. The `error.code` is `not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "The public API major version used for this response.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "Link": {
                "description": "Link to the versioning and deprecation policy (rel=deprecation).",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "413": {
            "description": "The request is far larger than a prompt. The `error.code` is `too_large`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "The public API major version used for this response.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "Link": {
                "description": "Link to the versioning and deprecation policy (rel=deprecation).",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "The visitor used every free prompt (`try_used_up`, with `promptsLeft`), a prompt is already running, or too many run at once (`try_busy`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "The public API major version used for this response.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "Link": {
                "description": "Link to the versioning and deprecation policy (rel=deprecation).",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "502": {
            "description": "The prompt could not be answered. The `error.code` is `try_failed`, and `error.promptsLeft` says how many prompts are left.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "The public API major version used for this response.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "Link": {
                "description": "Link to the versioning and deprecation policy (rel=deprecation).",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "This month's free trial is used up (`try_ended`, with `reopensOn`), the check that a person sent it could not run (`try_refused`), or the trial could not start (`unavailable`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "The public API major version used for this response.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "Link": {
                "description": "Link to the versioning and deprecation policy (rel=deprecation).",
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "The signed-in account's session token, sent as `Authorization: Bearer <token>`. The StellarFirm desktop app and the phone app use it. See https://stellarfirm.ai/docs/api/authentication."
      },
      "sessionCookie": {
        "type": "apiKey",
        "in": "cookie",
        "name": "__session",
        "description": "The session cookie the browser holds after you sign in at /sign-in. Sent automatically by a browser."
      }
    },
    "responses": {
      "MethodNotAllowed": {
        "description": "The route does not accept that HTTP method. The `Allow` header lists the ones it does. The `error.code` is `method_not_allowed`.",
        "headers": {
          "Allow": {
            "description": "The methods this route accepts.",
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "$ref": "#/components/schemas/ApiError"
            }
          }
        }
      }
    },
    "schemas": {
      "ApiError": {
        "type": "object",
        "description": "Every failure from a public route has this shape. Branch on `error.code`; it does not change between releases.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "$ref": "#/components/schemas/ApiErrorDetail"
          }
        },
        "examples": [
          {
            "error": {
              "code": "not_found",
              "message": "There is no API route at /api/nothing-here.",
              "hint": "Read the route list at /openapi.json, or start at /docs/api.",
              "docs": "https://stellarfirm.ai/docs/api/errors"
            }
          }
        ]
      },
      "ApiErrorDetail": {
        "type": "object",
        "required": [
          "code",
          "message",
          "hint",
          "docs"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "A stable machine-readable code.",
            "enum": [
              "not_found",
              "method_not_allowed",
              "signed_out",
              "forbidden",
              "forbidden_origin",
              "rate_limited",
              "invalid_request",
              "invalid_token",
              "not_configured",
              "balance_unavailable",
              "credits_not_configured",
              "billing_not_configured",
              "paid_only",
              "checkout_failed",
              "portal_failed",
              "limit_required",
              "limit_failed",
              "cancel_failed",
              "invalid_signature",
              "fulfilment_failed",
              "unavailable",
              "upstream_failed",
              "too_large",
              "try_used_up",
              "try_ended",
              "try_busy",
              "try_refused",
              "try_failed",
              "internal_error"
            ],
            "examples": [
              "signed_out"
            ]
          },
          "message": {
            "type": "string",
            "description": "What went wrong, in one plain sentence."
          },
          "hint": {
            "type": "string",
            "description": "What to do next."
          },
          "docs": {
            "type": "string",
            "format": "uri",
            "description": "Where the error codes are explained.",
            "examples": [
              "https://stellarfirm.ai/docs/api/errors"
            ]
          },
          "reopensOn": {
            "type": "string",
            "format": "date",
            "description": "Only on `try_ended`: the day the free trial opens again (UTC)."
          },
          "promptsLeft": {
            "type": "integer",
            "minimum": 0,
            "description": "Only on `try_used_up`: the free prompts left, which is 0."
          }
        }
      },
      "Health": {
        "type": "object",
        "required": [
          "ok",
          "service",
          "surface",
          "agent"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "service": {
            "type": "string",
            "description": "A fixed identifier for the web service."
          },
          "surface": {
            "type": "string",
            "description": "Which part of the product answered."
          },
          "agent": {
            "type": "string",
            "description": "Whether assistants run in this service."
          }
        }
      },
      "SiteBanner": {
        "type": "object",
        "required": [
          "kind",
          "message"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "promo",
              "warning"
            ],
            "description": "A `promo` shows on the public site only. A `warning` shows everywhere."
          },
          "message": {
            "type": "string",
            "description": "The notice text."
          }
        }
      },
      "SiteBannerResponse": {
        "type": "object",
        "required": [
          "banner"
        ],
        "properties": {
          "banner": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/SiteBanner"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "EntitlementAllowed": {
        "type": "object",
        "required": [
          "ok",
          "status",
          "recheckAfterSeconds"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "status": {
            "type": "string",
            "enum": [
              "licensed",
              "trial",
              "hobby"
            ]
          },
          "trialEndsAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the trial ends. Present on a trial."
          },
          "assistantLimit": {
            "type": "integer",
            "minimum": 0,
            "description": "How many named assistants the account may hire."
          },
          "teamLimit": {
            "type": "integer",
            "minimum": 1,
            "description": "How many teams of assistants the account may have, the first team included. Absent means the plan puts no cap on teams."
          },
          "recheckAfterSeconds": {
            "type": "integer",
            "minimum": 1,
            "description": "Ask again within this many seconds."
          }
        }
      },
      "EntitlementDenied": {
        "type": "object",
        "description": "The account may not run. It carries the `ApiError` fields too, so a client can branch on `error.code`.",
        "required": [
          "ok",
          "status",
          "message",
          "error"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": false
          },
          "status": {
            "type": "string",
            "enum": [
              "signed-out",
              "invalid",
              "unconfigured",
              "unavailable"
            ]
          },
          "message": {
            "type": "string"
          },
          "error": {
            "$ref": "#/components/schemas/ApiErrorDetail"
          }
        }
      },
      "CreditsBalance": {
        "type": "object",
        "required": [
          "credits"
        ],
        "properties": {
          "credits": {
            "type": "number",
            "minimum": 0,
            "description": "The balance, as a count of credits."
          }
        }
      },
      "TrialStatus": {
        "type": "object",
        "required": [
          "open",
          "promptsLeft",
          "promptsPerVisitor"
        ],
        "properties": {
          "open": {
            "type": "boolean",
            "description": "False once this month's allowance for the free trial is used up."
          },
          "promptsLeft": {
            "type": "integer",
            "minimum": 0,
            "description": "Free prompts this visitor has left."
          },
          "promptsPerVisitor": {
            "type": "integer",
            "minimum": 1,
            "description": "Free prompts each visitor gets."
          },
          "reopensOn": {
            "type": "string",
            "format": "date",
            "description": "The day the trial opens again (UTC). Present only while it is closed."
          }
        }
      },
      "TrialPromptRequest": {
        "type": "object",
        "required": [
          "assistant",
          "content"
        ],
        "properties": {
          "assistant": {
            "type": "string",
            "enum": [
              "coder",
              "marketing",
              "product-owner"
            ],
            "description": "Which assistant gets the prompt."
          },
          "content": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2000,
            "description": "The prompt, at most 2000 characters once trimmed."
          }
        }
      },
      "TrialArtifact": {
        "type": "object",
        "required": [
          "id",
          "kind",
          "title"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "html",
              "svg",
              "markdown",
              "csv",
              "json",
              "other"
            ],
            "description": "A kind the page draws, or `other` for what is only listed."
          },
          "title": {
            "type": "string"
          },
          "content": {
            "type": "string",
            "description": "The file's text, for a kind the page draws and when it is small enough."
          }
        }
      },
      "TrialPromptDone": {
        "type": "object",
        "required": [
          "type",
          "answer",
          "promptsLeft"
        ],
        "properties": {
          "type": {
            "type": "string",
            "const": "done"
          },
          "answer": {
            "type": "string",
            "description": "The whole answer, Markdown."
          },
          "promptsLeft": {
            "type": "integer",
            "minimum": 0,
            "description": "Free prompts this visitor has left."
          },
          "held": {
            "type": "boolean",
            "description": "True when the answer stopped at a step that needs an Approve, which a free prompt never gets."
          },
          "artifacts": {
            "type": "array",
            "description": "What the assistant published. Present when the answer is read as JSON.",
            "items": {
              "$ref": "#/components/schemas/TrialArtifact"
            }
          }
        }
      }
    }
  }
}
