{
  "openapi": "3.1.1",
  "info": {
    "title": "StackerScan Public API",
    "version": "1.0.0",
    "summary": "Open, read-only precious-metals price, premium and share data. No authentication of any kind is required.",
    "description": "The public, read-only HTTP API behind StackerScan — live and historical precious-metals spot prices, observed dealer premiums, PCGS price-guide history, the supported-currency catalog, and read access to shared stacks and receipts.\n\n## Authentication\n\nThis API is fully open: no authentication, no API keys, and no registration are required for any endpoint in this document.\nThere is no `securitySchemes` section and the root `security` is the empty list, which says the same thing structurally: every operation below is callable by an anonymous client over plain HTTPS. Nothing here is rate-limited by account, because there are no accounts involved.\n\nThe one apparent exception is not one. The share endpoints take a `token` path segment, which is a **capability URL**: possession of the token is the whole of the authorization, exactly as it is for the browser link a user shares. It is a path parameter, not a credential — no header, no exchange, no registration — so it does not qualify the statement above.\n\n## Errors\n\nEvery `/api/*` response that is not a success is an RFC 9457 `application/problem+json` document (#1115), including from the `.md` endpoints — a markdown endpoint serves `text/markdown` on success and `problem+json` on failure. The `type` member is a dereferenceable URL under https://www.stackerscan.com/errors, drawn from a closed vocabulary of 7 codes: `invalid-parameter`, `missing-parameter`, `unauthorized`, `not-found`, `rate-limited`, `upstream-unavailable`, `internal-error`. `title` describes the code and never varies per occurrence; `detail` describes the occurrence and names the offending parameter. `detail` and `hint` are English-only by decision: they are machine-consumer text, and human-facing copy lives in the translated UI layer keyed off the code.\n\n## Parameter validation\n\nInvalid input is rejected rather than silently replaced (#1122). A parameter outside its documented enum or bounds answers `400 invalid-parameter`; an absent required parameter answers `400 missing-parameter`. Integers are strict: `days=90abc` is rejected, not read as `90`.\n\n`currency`/`base` are the one enum this document cannot enumerate. They validate against the **live** catalog served by `/api/currencies`, which is a runtime read; when that read is unavailable the endpoints **fail open** and accept any syntactically plausible code rather than turning a cache outage into an API outage. Treat `/api/currencies` as the authority for what is accepted, and expect the enum to be unenforced in the window where the catalog cannot be read.\n\n## A naming trap worth reading twice\n\n`/api/metal-prices` (no suffix) is **private** and is not in this document: it answers `401 unauthorized` to an anonymous caller. `/api/metal-prices.md` is public, is described below, and reads an entirely different data path — it is not a markdown rendering of the private endpoint. The two are unrelated endpoints that happen to share a prefix. The public JSON equivalent you probably want is `/api/premiums/metal-prices`.\n\nSimilarly, `/api/deployment-info` and `/api/credits/packages` answer without a credential but are deliberately absent from this document (#1114): they are a build diagnostic and checkout plumbing, not published promises. Anything not listed under `paths` is not part of the public contract, whatever it currently answers.",
    "contact": {
      "name": "StackerScan support",
      "url": "https://www.stackerscan.com/contact",
      "email": "support@stackerscan.com"
    }
  },
  "servers": [
    {
      "url": "https://www.stackerscan.com",
      "description": "Production. The apex 307-redirects here."
    }
  ],
  "security": [],
  "externalDocs": {
    "url": "https://www.stackerscan.com/errors",
    "description": "The error-code vocabulary every failure response references."
  },
  "tags": [
    {
      "name": "Prices",
      "description": "Spot prices, current and historical."
    },
    {
      "name": "Charts",
      "description": "Daily series for charting."
    },
    {
      "name": "Premiums",
      "description": "Observed premiums over spot, and the Goldback multiple."
    },
    {
      "name": "Markdown",
      "description": "The same data as markdown documents, for agents and crawlers."
    },
    {
      "name": "Reference",
      "description": "The currency catalog other parameters validate against."
    },
    {
      "name": "Sharing",
      "description": "Read access to a published share, by capability URL."
    },
    {
      "name": "Community",
      "description": "Aggregate activity counts."
    },
    {
      "name": "Diagnostics",
      "description": "Liveness."
    }
  ],
  "paths": {
    "/api/health": {
      "get": {
        "operationId": "getHealth",
        "summary": "Liveness check",
        "description": "Reports whether the web tier is serving. Uncached — the response carries `no-store`. Intended for monitoring rather than as a data endpoint.",
        "tags": [
          "Diagnostics"
        ],
        "responses": {
          "200": {
            "description": "The service is serving requests.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "status",
                    "timestamp",
                    "service",
                    "version"
                  ],
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "healthy"
                      ]
                    },
                    "timestamp": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "service": {
                      "type": "string"
                    },
                    "version": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "The health check itself failed; the body carries a `health` extension member.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/currencies": {
      "get": {
        "operationId": "listCurrencies",
        "summary": "List supported currencies",
        "description": "The live catalog of codes accepted by `currency`/`base` everywhere else in this document — ISO 4217 fiat plus `BTC` and the metal codes. Synced daily. This endpoint is the authority for that enum, which is why the enum is not restated in the parameter schemas.",
        "tags": [
          "Reference"
        ],
        "responses": {
          "200": {
            "description": "The catalog, ordered by code.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "required": [
                      "code",
                      "name"
                    ],
                    "properties": {
                      "code": {
                        "type": "string",
                        "examples": [
                          "USD",
                          "XAU"
                        ]
                      },
                      "name": {
                        "type": "string",
                        "examples": [
                          "United States Dollar"
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "The catalog is temporarily unreadable. Unlike the endpoints that merely validate against it, this one has nothing to fail open to — its payload *is* the catalog.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/currencies.md": {
      "get": {
        "operationId": "getCurrenciesMarkdown",
        "summary": "Supported currencies, as markdown",
        "description": "The same catalog as `/api/currencies`, rendered as a markdown document for agents and crawlers. Failures are still `problem+json`, not markdown.",
        "tags": [
          "Markdown"
        ],
        "responses": {
          "200": {
            "description": "The catalog as a markdown list.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "The catalog is temporarily unreadable.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/metal-prices.md": {
      "get": {
        "operationId": "getMetalPricesMarkdown",
        "summary": "Metal spot prices, as markdown",
        "description": "Current spot prices and recent history for gold, silver, platinum, palladium, copper and nickel, as markdown tables. **Not** a rendering of the private `/api/metal-prices`: despite the name, the two are unrelated endpoints reading different data paths, and only this one is public.",
        "tags": [
          "Markdown",
          "Prices"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Currency"
          },
          {
            "$ref": "#/components/parameters/Unit"
          }
        ],
        "responses": {
          "200": {
            "description": "Spot price tables in the requested currency and unit.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      }
    },
    "/api/metal-premiums.md": {
      "get": {
        "operationId": "getMetalPremiumsMarkdown",
        "summary": "Observed premiums over spot, as markdown",
        "description": "Rolling fine-metal-weighted median asking premiums over spot, by category and product, as markdown tables. `metal` is required: this endpoint covers two metals and defaulting to either would be a coin flip the caller never asked for (#1122).",
        "tags": [
          "Markdown",
          "Premiums"
        ],
        "parameters": [
          {
            "name": "metal",
            "in": "query",
            "description": "Which premium dataset to render. Required — omitting it answers `400 missing-parameter` rather than choosing for you. Matched case-insensitively.",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "silver",
                "gold"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The premium tables for the requested metal. When the dataset is temporarily unavailable this is still a 200 whose body says so.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      }
    },
    "/api/how-to-use.md": {
      "get": {
        "operationId": "getHowToUseMarkdown",
        "summary": "Product guide, as markdown",
        "description": "A step-by-step guide to using StackerScan — accounts, adding items, receipt scanning, photos, portfolio management, tags and credits. Prose about the product, not API data.",
        "tags": [
          "Markdown"
        ],
        "responses": {
          "200": {
            "description": "The guide as a markdown document.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/api/premiums/metal-prices": {
      "get": {
        "operationId": "getPremiumCalculatorMetalPrices",
        "summary": "Current spot prices as JSON",
        "description": "Current spot prices in the requested currency and unit — the JSON endpoint most callers reaching for the private `/api/metal-prices` actually want. Note that `metals` here is filtered by metal **symbol** (`xau`), not name (`gold`), unlike `/api/metal-prices-previous-day`; that difference is real and long-standing rather than a documentation slip.",
        "tags": [
          "Prices"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/BaseCurrency"
          },
          {
            "$ref": "#/components/parameters/Unit"
          },
          {
            "name": "metals",
            "in": "query",
            "description": "Comma-separated metal **symbols** to include. Omit for every metal. Matched case-insensitively; duplicates collapse.",
            "required": false,
            "schema": {
              "type": "string",
              "examples": [
                "xau,xag"
              ],
              "pattern": "^\\s*(xau|xag|xpt|xpd|xcu|ni)(\\s*,\\s*(xau|xag|xpt|xpd|xcu|ni))*\\s*$"
            }
          },
          {
            "name": "analysisId",
            "in": "query",
            "description": "Optional opaque correlation identifier echoed through to the pricing backend. Bounded to 128 characters of `A-Za-z0-9._~:-`.",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._~:-]{1,128}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Spot prices for the requested metals.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "description": "Per-metal current prices with the base currency and unit they are quoted in."
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/premiums/goldback-rate": {
      "get": {
        "operationId": "getGoldbackRate",
        "summary": "Latest Goldback premium multiple",
        "description": "The most recently published Goldback exchange rate, expressed as a multiple of gold spot. Goldback prices elsewhere in this API are derived from gold spot and this multiple rather than quoted directly.",
        "tags": [
          "Premiums"
        ],
        "responses": {
          "200": {
            "description": "The latest published multiple and the date it is for.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "premiumMultiple",
                    "rateDate"
                  ],
                  "properties": {
                    "premiumMultiple": {
                      "type": "number"
                    },
                    "rateDate": {
                      "type": "string",
                      "format": "date"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/metal-prices-previous-day": {
      "get": {
        "operationId": "getPreviousDayMetalPrices",
        "summary": "Previous session's closing prices",
        "description": "The most recent close before the cutoff date for each requested metal, used to compute day-over-day change. Looks back up to a week so a weekend or holiday still yields a price. Metals are named here (`gold`), not symbolled (`xau`).",
        "tags": [
          "Prices"
        ],
        "parameters": [
          {
            "name": "metals",
            "in": "query",
            "description": "Comma-separated metal **names**. Defaults to the six traded metals; `goldback` is accepted and priced from the gold series. Matched case-insensitively.",
            "required": false,
            "schema": {
              "type": "string",
              "default": "gold,silver,platinum,palladium,copper,nickel",
              "pattern": "^\\s*(gold|silver|platinum|palladium|copper|nickel|goldback)(\\s*,\\s*(gold|silver|platinum|palladium|copper|nickel|goldback))*\\s*$"
            }
          },
          {
            "$ref": "#/components/parameters/BaseCurrency"
          },
          {
            "name": "excludeDate",
            "in": "query",
            "description": "The quote date to look *before*, as `YYYY-MM-DD`. Defaults to today. Must be a real calendar date — `2025-02-31` is rejected.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date",
              "examples": [
                "2026-08-21"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Previous-close prices keyed by metal symbol.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "description": "No price data was found for any of the requested metals.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/metal-price-charts": {
      "get": {
        "operationId": "getMetalPriceChart",
        "summary": "Historical price series for one metal",
        "description": "A daily price series for a single metal over a trailing window. `metal` is required — the endpoint used to answer for silver when the parameter was missing *or* invalid, which made the two cases indistinguishable (#1122).",
        "tags": [
          "Charts"
        ],
        "parameters": [
          {
            "name": "metal",
            "in": "query",
            "description": "The series to return. `goldback` is derived from the gold series and the published premium multiple rather than being a traded metal. Matched case-insensitively.",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "gold",
                "silver",
                "platinum",
                "palladium",
                "copper",
                "nickel",
                "goldback"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/BaseCurrency"
          },
          {
            "$ref": "#/components/parameters/Days"
          },
          {
            "$ref": "#/components/parameters/ChartFormat"
          }
        ],
        "responses": {
          "200": {
            "description": "The requested series, plus market-session metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/goldback-price-charts": {
      "get": {
        "operationId": "getGoldbackPriceChart",
        "summary": "Historical Goldback price series",
        "description": "The Goldback series over a trailing window, derived from gold spot and the published premium multiple. Equivalent to `/api/metal-price-charts?metal=goldback`, kept as its own endpoint because it also carries the multiples it was derived from.",
        "tags": [
          "Charts"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/BaseCurrency"
          },
          {
            "$ref": "#/components/parameters/Days"
          },
          {
            "$ref": "#/components/parameters/ChartFormat"
          }
        ],
        "responses": {
          "200": {
            "description": "The Goldback series over the requested window.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/pcgs-price-charts": {
      "get": {
        "operationId": "getPcgsPriceChart",
        "summary": "PCGS price-guide history for one coin and grade",
        "description": "Historical PCGS price-guide values for a single PCGS number at a single grade. Values are published in USD only — there is no `base` parameter, and passing one has no effect.",
        "tags": [
          "Charts"
        ],
        "parameters": [
          {
            "name": "pcgsNumber",
            "in": "query",
            "description": "The PCGS catalog number identifying the coin. Required.",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "gradeNo",
            "in": "query",
            "description": "The numeric Sheldon-scale grade. Required.",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 70
            }
          },
          {
            "name": "plusGrade",
            "in": "query",
            "description": "Whether to read the plus-grade (`65+`) series. Exactly `true` or `false`; any other value is rejected rather than read as false.",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "$ref": "#/components/parameters/Days"
          }
        ],
        "responses": {
          "200": {
            "description": "The price-guide series in USD.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/transactions/spot-price": {
      "get": {
        "operationId": "getSpotPriceForDate",
        "summary": "Spot price for one metal on one date",
        "description": "The spot price of a single metal on a given date — the endpoint used to value a transaction at the moment it happened. `date` and `metal` are both required.",
        "tags": [
          "Prices"
        ],
        "parameters": [
          {
            "name": "date",
            "in": "query",
            "description": "The valuation date as `YYYY-MM-DD`. Required, and must be a real calendar date. Ignored when `current=true`.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date",
              "examples": [
                "2026-08-21"
              ]
            }
          },
          {
            "name": "metal",
            "in": "query",
            "description": "Which metal to price. Required. `goldback` is not accepted here — request `metal=gold` with `formOfMetal=goldback` instead.",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "gold",
                "silver",
                "platinum",
                "palladium",
                "copper",
                "nickel"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/BaseCurrency"
          },
          {
            "$ref": "#/components/parameters/Unit"
          },
          {
            "name": "formOfMetal",
            "in": "query",
            "description": "Free-form form-of-metal label carried on the transaction. The only value that changes the answer is `goldback`, which (with `metal=gold`) derives the Goldback price from gold spot and the published multiple.",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 64,
              "pattern": "^[^<>]*$"
            }
          },
          {
            "name": "current",
            "in": "query",
            "description": "Price at the latest available quote instead of on `date`. Exactly `true` or `false`.",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The resolved spot price in the requested currency and unit.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "description": "No spot price is available for that metal on that date.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/community/counts": {
      "get": {
        "operationId": "getCommunityCounts",
        "summary": "Aggregate community activity counts",
        "description": "Paginated aggregate counts of community activity over a trailing window. Aggregates only — no individual account is identifiable from this endpoint.",
        "tags": [
          "Community"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Days"
          },
          {
            "name": "page",
            "in": "query",
            "description": "1-based page number.",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "description": "Rows per page.",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          },
          {
            "name": "collections",
            "in": "query",
            "description": "Comma-separated collections to count. Defaults to all of them; duplicates collapse.",
            "required": false,
            "schema": {
              "type": "string",
              "default": "UserProfile,MetalTransaction",
              "pattern": "^\\s*(UserProfile|MetalTransaction)(\\s*,\\s*(UserProfile|MetalTransaction))*\\s*$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Counts for the requested collections and window.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "408": {
            "description": "The upstream aggregation did not answer in time.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "503": {
            "description": "The aggregation service is unreachable.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/share/{token}": {
      "get": {
        "operationId": "getSharedScan",
        "summary": "Read a shared scan",
        "description": "The scan its owner published behind this share token. No credential is involved: the token is the capability.",
        "tags": [
          "Sharing"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ShareToken"
          }
        ],
        "responses": {
          "200": {
            "description": "The shared scan.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "The shared payload as published by its owner. The shape is the share's own and is not further constrained here; response shapes are documentation, not a versioned contract (#1114).",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "description": "No share exists for that token, or it has been revoked.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/public/share/{token}/receipt": {
      "get": {
        "operationId": "getSharedScanReceipt",
        "summary": "Read a shared scan's receipt",
        "description": "The receipt attached to the shared scan, when its owner published one.",
        "tags": [
          "Sharing"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ShareToken"
          }
        ],
        "responses": {
          "200": {
            "description": "The receipt attached to the share.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "The shared payload as published by its owner. The shape is the share's own and is not further constrained here; response shapes are documentation, not a versioned contract (#1114).",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "description": "No share exists for that token, or it carries no receipt.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/public/share/{token}/image/{imageId}": {
      "get": {
        "operationId": "getSharedScanImage",
        "summary": "Read one image from a shared scan",
        "description": "Metadata and a retrieval URL for a single image in the shared scan. The response is JSON describing the image, not the image bytes.",
        "tags": [
          "Sharing"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ShareToken"
          },
          {
            "$ref": "#/components/parameters/ImageId"
          }
        ],
        "responses": {
          "200": {
            "description": "The image's metadata and retrieval URL.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "The shared payload as published by its owner. The shape is the share's own and is not further constrained here; response shapes are documentation, not a versioned contract (#1114).",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "description": "No such image in that share, or no such share.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/public/share/stack/{token}": {
      "get": {
        "operationId": "getSharedStack",
        "summary": "Read a shared stack",
        "description": "The stack its owner published behind this share token — its items and totals. A stack token is distinct from a scan token; the two subtrees do not share a namespace.",
        "tags": [
          "Sharing"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ShareToken"
          }
        ],
        "responses": {
          "200": {
            "description": "The shared stack.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "The shared payload as published by its owner. The shape is the share's own and is not further constrained here; response shapes are documentation, not a versioned contract (#1114).",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "description": "No stack share exists for that token, or it has been revoked.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/public/share/stack/{token}/gallery": {
      "get": {
        "operationId": "getSharedStackGallery",
        "summary": "List a shared stack's images",
        "description": "The images published with the shared stack, as a list to page through.",
        "tags": [
          "Sharing"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ShareToken"
          }
        ],
        "responses": {
          "200": {
            "description": "The stack's published images.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "The shared payload as published by its owner. The shape is the share's own and is not further constrained here; response shapes are documentation, not a versioned contract (#1114).",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "description": "No stack share exists for that token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/public/share/stack/{token}/image/{imageId}": {
      "get": {
        "operationId": "getSharedStackImage",
        "summary": "Read one image from a shared stack",
        "description": "Metadata and a retrieval URL for a single image in the shared stack's gallery. JSON describing the image, not the image bytes.",
        "tags": [
          "Sharing"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ShareToken"
          },
          {
            "$ref": "#/components/parameters/ImageId"
          }
        ],
        "responses": {
          "200": {
            "description": "The image's metadata and retrieval URL.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "The shared payload as published by its owner. The shape is the share's own and is not further constrained here; response shapes are documentation, not a versioned contract (#1114).",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "description": "No such image in that stack share, or no such share.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "An RFC 9457 problem detail. Extension members beyond those listed may appear (for example `parameter` on a parameter failure); a client should ignore members it does not know.",
        "required": [
          "type",
          "title",
          "status",
          "detail"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "description": "A dereferenceable URL identifying the error code, e.g. `https://www.stackerscan.com/errors/invalid-parameter`.",
            "enum": [
              "https://www.stackerscan.com/errors/invalid-parameter",
              "https://www.stackerscan.com/errors/missing-parameter",
              "https://www.stackerscan.com/errors/unauthorized",
              "https://www.stackerscan.com/errors/not-found",
              "https://www.stackerscan.com/errors/rate-limited",
              "https://www.stackerscan.com/errors/upstream-unavailable",
              "https://www.stackerscan.com/errors/internal-error"
            ]
          },
          "title": {
            "type": "string",
            "description": "A short, occurrence-independent summary of the error code."
          },
          "status": {
            "type": "integer",
            "description": "The HTTP status code, repeated in the body."
          },
          "detail": {
            "type": "string",
            "description": "What went wrong on this occurrence — names the offending parameter and the accepted values where applicable. English-only."
          },
          "hint": {
            "type": "string",
            "description": "An optional suggested next action. English-only."
          }
        },
        "additionalProperties": true
      }
    },
    "parameters": {
      "BaseCurrency": {
        "name": "base",
        "in": "query",
        "description": "Currency the prices are converted to, as an ISO 4217 code, `BTC`, or a metal code. Validated against the live catalog from `/api/currencies`, and unenforced in the window where that catalog cannot be read.",
        "required": false,
        "schema": {
          "type": "string",
          "default": "USD",
          "examples": [
            "USD",
            "EUR",
            "BTC"
          ]
        }
      },
      "Currency": {
        "name": "currency",
        "in": "query",
        "description": "Currency the prices are converted to. Same catalog-backed validation as `base`; the two names are the historical spelling difference between endpoints, not a difference in meaning.",
        "required": false,
        "schema": {
          "type": "string",
          "default": "USD",
          "examples": [
            "USD",
            "EUR"
          ]
        }
      },
      "Unit": {
        "name": "unit",
        "in": "query",
        "description": "Weight unit prices are quoted in. Exactly these two spellings — `oz`, `ounce`, `g` and `grams` are rejected (#1122).",
        "required": false,
        "schema": {
          "type": "string",
          "enum": [
            "troy_oz",
            "gram"
          ],
          "default": "troy_oz"
        }
      },
      "Days": {
        "name": "days",
        "in": "query",
        "description": "Size of the trailing window, in days. Strictly an integer: a value such as `90abc` is rejected rather than truncated.",
        "required": false,
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 3650,
          "default": 90
        }
      },
      "ChartFormat": {
        "name": "format",
        "in": "query",
        "description": "`line` returns closing prices; `ohlc` returns open/high/low/close rows.",
        "required": false,
        "schema": {
          "type": "string",
          "enum": [
            "line",
            "ohlc"
          ],
          "default": "line"
        }
      },
      "ShareToken": {
        "name": "token",
        "in": "path",
        "description": "The share token from the link its owner published. A capability URL: possession of the token is the authorization, and it is a path parameter rather than a credential — no header or registration is involved. Treat it as a secret, since anyone holding it can read the shared data.",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^[A-Za-z0-9._~:-]{1,128}$"
        }
      },
      "ImageId": {
        "name": "imageId",
        "in": "path",
        "description": "Identifier of one image within the share, as listed by the share itself.",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^[A-Za-z0-9._~:-]{1,128}$"
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "A parameter was missing or outside its accepted values. `type` is `missing-parameter` or `invalid-parameter`, and `detail` names the parameter.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "ServerError": {
        "description": "The request was well formed but could not be served. Retrying later may succeed.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      }
    }
  }
}