{
  "openapi": "3.1.0",
  "info": {
    "title": "Apstraco public API",
    "version": "1.0.0",
    "description": "Public, unauthenticated endpoints of the Apstraco apparel cost & tariff intelligence platform. Authenticated workspace surfaces (calculator, saved scenarios) use Supabase cookie sessions and are intentionally not part of this spec. Most pages also serve text/markdown when requested with `Accept: text/markdown`.\n\n## Versioning policy\n\nThe current stable surface is **v1**, addressed by these unversioned paths (`/api/...`). Breaking changes will NEVER be introduced on existing paths: a new major version ships under `/api/v2/...` alongside v1 for at least 90 days, and removal of a deprecated version is signalled in advance with an RFC `Sunset` header plus an entry in llms.txt and /developers. Additive changes (new optional fields) may ship without notice.\n\n## Errors\n\nEvery non-2xx response returns JSON — never HTML — using the typed Error envelope below (`code` is machine-readable, `message` human-readable, `hint` actionable). Unknown paths return 404 with code `not_found`; exceeding the per-minute limit returns 429 with code `rate_limited` and a `Retry-After` header.",
    "contact": {
      "name": "Apstraco support",
      "email": "info@apstraco.com"
    }
  },
  "servers": [
    {
      "url": "https://apstraco.com"
    }
  ],
  "tags": [
    {
      "name": "catalog",
      "description": "Catalog mapping data consumed by the calculator"
    },
    {
      "name": "market",
      "description": "Live market intelligence feeds"
    }
  ],
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Consistent error envelope for every non-2xx response (RFC 9457 problem+json fields are mirrored here).",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "description": "Machine-readable stable error code, e.g. not_found, rate_limited.",
                "examples": [
                  "not_found",
                  "rate_limited"
                ]
              },
              "message": {
                "type": "string",
                "description": "Human-readable explanation."
              },
              "hint": {
                "type": "string",
                "description": "Resolution hint — what the caller should change or when to retry."
              }
            },
            "required": [
              "code",
              "message"
            ]
          }
        },
        "required": [
          "error"
        ]
      }
    }
  },
  "x-rate-limit-policy": {
    "windowSeconds": 60,
    "limitPerIp": 60,
    "scope": "per endpoint, best-effort per serverless isolate",
    "standardHeaders": [
      "RateLimit-Limit",
      "RateLimit-Remaining",
      "RateLimit-Reset"
    ],
    "tooManyRequests": {
      "status": 429,
      "header": "Retry-After"
    }
  },
  "paths": {
    "/api/catalog-overrides": {
      "get": {
        "operationId": "listCatalogOverrides",
        "tags": [
          "catalog"
        ],
        "summary": "List catalog mapping overrides",
        "description": "Returns the operator-curated catalog mapping overrides (gender/category/garment/COO adjustments) that the calculator layers over the built-in catalog.",
        "security": [],
        "responses": {
          "200": {
            "description": "Current overrides",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "overrides": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "examples": [
                              "pakistan"
                            ]
                          },
                          "payload": {
                            "type": "object"
                          }
                        },
                        "required": [
                          "id",
                          "payload"
                        ]
                      }
                    }
                  },
                  "required": [
                    "overrides"
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Standard RateLimit-* headers are present on every response; 429 adds Retry-After.",
            "headers": {
              "RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per window."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Remaining requests in the current window."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Seconds until the window resets."
              },
              "Retry-After": {
                "schema": {
                  "type": "string"
                },
                "description": "Seconds to wait before retrying (429 only)."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Consistent error envelope for every non-2xx response (RFC 9457 problem+json fields are mirrored here).",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "description": "Machine-readable stable error code, e.g. not_found, rate_limited.",
                          "examples": [
                            "not_found",
                            "rate_limited"
                          ]
                        },
                        "message": {
                          "type": "string",
                          "description": "Human-readable explanation."
                        },
                        "hint": {
                          "type": "string",
                          "description": "Resolution hint — what the caller should change or when to retry."
                        }
                      },
                      "required": [
                        "code",
                        "message"
                      ]
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/market": {
      "get": {
        "operationId": "getMarketSnapshot",
        "tags": [
          "market"
        ],
        "summary": "Live market intel snapshot",
        "description": "Latest FX rates per sourcing country (with 90-day change and sparkline series), plus fabric/freight reference feeds where available. Served from the cached market feed; fails soft with an empty object when the feed is degraded.",
        "security": [],
        "responses": {
          "200": {
            "description": "Market snapshot keyed by feed name (fx, …)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "object"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Standard RateLimit-* headers are present on every response; 429 adds Retry-After.",
            "headers": {
              "RateLimit-Limit": {
                "schema": {
                  "type": "string"
                },
                "description": "Requests allowed per window."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Remaining requests in the current window."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Seconds until the window resets."
              },
              "Retry-After": {
                "schema": {
                  "type": "string"
                },
                "description": "Seconds to wait before retrying (429 only)."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Consistent error envelope for every non-2xx response (RFC 9457 problem+json fields are mirrored here).",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "description": "Machine-readable stable error code, e.g. not_found, rate_limited.",
                          "examples": [
                            "not_found",
                            "rate_limited"
                          ]
                        },
                        "message": {
                          "type": "string",
                          "description": "Human-readable explanation."
                        },
                        "hint": {
                          "type": "string",
                          "description": "Resolution hint — what the caller should change or when to retry."
                        }
                      },
                      "required": [
                        "code",
                        "message"
                      ]
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/warm": {
      "get": {
        "operationId": "warmCaches",
        "tags": [
          "market"
        ],
        "summary": "Feed warmer (cron-only)",
        "description": "Refreshes the HTS/market caches. Protected by a shared cron secret — public calls receive 401 by design.",
        "security": [],
        "responses": {
          "401": {
            "description": "Missing or invalid CRON_SECRET (expected for public callers)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Consistent error envelope for every non-2xx response (RFC 9457 problem+json fields are mirrored here).",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "description": "Machine-readable stable error code, e.g. not_found, rate_limited.",
                          "examples": [
                            "not_found",
                            "rate_limited"
                          ]
                        },
                        "message": {
                          "type": "string",
                          "description": "Human-readable explanation."
                        },
                        "hint": {
                          "type": "string",
                          "description": "Resolution hint — what the caller should change or when to retry."
                        }
                      },
                      "required": [
                        "code",
                        "message"
                      ]
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        }
      }
    }
  },
  "x-unmatched-paths": {
    "description": "Any undocumented /api/* path returns 404 application/json with the Error envelope (code: not_found)."
  }
}