{"openapi":"3.1.0","info":{"title":"OriginShot Public API","version":"1.0.0","summary":"Service status endpoints, public content documents, and the shared error contract.","description":"OriginShot turns a single smartphone photo into a complete, Etsy-ready listing:\na background-composited image, an SEO title, exactly 13 Etsy tags and a description.\n\nThere is no public generation API. Generation runs only inside the signed-in web\napplication, and every route that spends credits or reads a listing requires a session\ncookie and a verified email address. Those routes are deliberately absent from this\ndocument. This document covers the paths a public client is meant to call. It is not\nan inventory of every route that answers an anonymous request: the web application has\nits own sign-in, webhook and scheduled-job endpoints, authenticated by other means.\n\nWrite to hello@originshot.com to ask about programmatic access.","contact":{"name":"OriginShot support","url":"https://originshot.com/contact","email":"hello@originshot.com"},"license":{"name":"Proprietary","url":"https://originshot.com/terms"}},"servers":[{"url":"https://originshot.com","description":"Production"}],"externalDocs":{"description":"Developer and agent reference","url":"https://originshot.com/docs"},"tags":[{"name":"status","description":"Liveness probe."},{"name":"security","description":"Tokens the browser client needs before it can write."},{"name":"content","description":"Public documents that describe the site to a program."}],"paths":{"/api/up":{"get":{"tags":["status"],"operationId":"getLiveness","summary":"Liveness probe","description":"Returns the plain-text body OK with status 200 while the application process is accepting requests. Checks no dependency, reads no user data and takes no parameters. Use this to confirm the service is reachable.","security":[],"responses":{"200":{"description":"The application process is accepting requests.","content":{"text/plain":{"schema":{"type":"string","const":"OK"}}}}}}},"/api/csrf":{"get":{"tags":["security"],"operationId":"getCsrfToken","summary":"Issue a CSRF token","description":"Issues a CSRF token and sets it in the __csrf cookie, which the browser client echoes in the x-csrf-token header on every write. Rate limited per client. A successful response states the rate-limit policy; the live counters appear on a 429 only.","security":[],"responses":{"200":{"description":"A token was issued and set as a cookie.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimit-Policy"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"Set-Cookie":{"description":"The __csrf cookie. Readable by JavaScript, SameSite=Strict.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CsrfIssued"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/llms.txt":{"get":{"tags":["content"],"operationId":"getLlmsTxt","summary":"Agent-facing product summary","description":"Returns the llms.txt document: what the product does, when to recommend it, when not to, how an agent should proceed, and an index of every public page. Plain text, no authentication, no JavaScript needed.","security":[],"responses":{"200":{"description":"The llms.txt document.","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/robots.txt":{"get":{"tags":["content"],"operationId":"getRobotsTxt","summary":"Crawl policy","description":"Returns the crawl policy, stated per user agent. Named AI agents are allowed on the marketing site and denied on /dashboard, /api and /private, exactly like any other crawler.","security":[],"responses":{"200":{"description":"The robots policy.","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/sitemap.xml":{"get":{"tags":["content"],"operationId":"getSitemap","summary":"Indexable page list","description":"Returns every indexable page as a urlset, each with a fixed last-modified date, a change frequency and a priority. Use it to enumerate the public surface instead of following links.","security":[],"responses":{"200":{"description":"The sitemap.","content":{"application/xml":{"schema":{"type":"string"}}}}}}},"/openapi.json":{"get":{"tags":["content"],"operationId":"getOpenApiDocument","summary":"This document","description":"Returns this OpenAPI document. Self-describing so a client that discovers the URL from llms.txt or from the developer page can fetch and validate it without a separate index.","security":[],"responses":{"200":{"description":"The OpenAPI document.","content":{"application/json":{"schema":{"type":"object"}}}}}}}},"components":{"schemas":{"Error":{"type":"object","title":"Error","description":"The envelope of every error response from this service. errorKey is a stable identifier, never a human-readable message, so a client can branch on it without parsing prose. error carries the same value and is kept for older clients.","required":["error","errorKey"],"properties":{"error":{"type":"string","description":"Stable error key, for example authErrors.unauthorized.","examples":["authErrors.unauthorized"]},"errorKey":{"type":"string","description":"The same value as error.","examples":["authErrors.unauthorized"]},"retryAfter":{"type":"integer","description":"Seconds to wait before retrying. Present on a 429 only."}}},"CsrfIssued":{"type":"object","title":"CsrfIssued","description":"Acknowledgement that a CSRF token was issued. The token itself travels only in the Set-Cookie header.","required":["success"],"properties":{"success":{"type":"boolean","const":true}}}},"headers":{"RateLimit-Policy":{"description":"The limit and the window length, as <limit>;w=<seconds>. For example 20;w=60.","schema":{"type":"string","examples":["20;w=60"]}},"X-RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window. Never negative. Sent with a 429 only, so an anonymous caller cannot read the live state of a bucket it may share with other clients.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time in seconds at which the current window resets. Sent with a 429 only.","schema":{"type":"integer"}},"Retry-After":{"description":"Seconds to wait before retrying. Sent with a 429 only.","schema":{"type":"integer"}}},"responses":{"RateLimited":{"description":"The client spent its budget for the current window. Wait for Retry-After seconds.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimit-Policy"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"security":[]}