{"openapi":"3.1.0","info":{"title":"AgentForge","version":"0.1.0"},"paths":{"/lang/{locale}":{"get":{"summary":"Set Lang","description":"Language switcher endpoint. Sets the lang cookie + redirects back to\nthe page the user came from (or / if Referer is missing/cross-origin).","operationId":"set_lang_lang__locale__get","parameters":[{"name":"locale","in":"path","required":true,"schema":{"type":"string","title":"Locale"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/":{"get":{"summary":"Index","operationId":"index__get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/landing":{"get":{"summary":"Landing","description":"Marketing page with a working hero textarea. The hero form htmx-posts\nto /generate (rate-limited 3/day for anon visitors), so first-time\narrivals can experience the product without leaving the page.\n\n`?from=...` is a future-proof channel tag (just passed to the template;\nCF / access logs already capture it for analytics).","operationId":"landing_landing_get","parameters":[{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"From"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/terms":{"get":{"summary":"Terms","description":"Terms of Service. Public page, no auth. Required by Lemon Squeezy\nstorefront review and by EU/UK consumer law for paid services.","operationId":"terms_terms_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/privacy":{"get":{"summary":"Privacy","description":"Privacy Policy. Public page, no auth. Required by GDPR/CCPA and by\nLemon Squeezy storefront review.","operationId":"privacy_privacy_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/pricing":{"get":{"summary":"Pricing","operationId":"pricing_pricing_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/blog":{"get":{"summary":"Blog Index","description":"Blog index — lists all published posts, newest first.\n\nPulls from content/blog/*.md via the in-process cache in app.blog.\nNo rate-limit counting, and crucially **no new fingerprint cookie\nwrite** — bots hit these endpoints constantly and cookie-setting on\nevery crawl is noise that confuses anti-abuse heuristics. We still\nresolve the user (read-only) so the nav bar shows correct\nSign-in / Pro / etc. state for actual humans browsing.","operationId":"blog_index_blog_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/blog/{slug}":{"get":{"summary":"Blog Post","description":"Single blog post. 404 if slug doesn't exist. Resolves `related:`\nfront-matter into actual BlogPost objects so the template can render\na related-posts section with descriptions.\n\nSame crawler-friendly stance as /blog — read user state for nav,\nbut no fingerprint cookie writes on every hit.","operationId":"blog_post_blog__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/blog/{slug}/like":{"post":{"summary":"Blog Like","description":"Increment a post's like counter. Returns the new displayed totals\n(fictional base + real). 404 for unknown slugs so we don't seed rows\nfor garbage paths. Dedup is best-effort on the client (localStorage);\na vanity counter doesn't warrant server-side identity tracking.","operationId":"blog_like_blog__slug__like_post","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/blog/{slug}/share":{"post":{"summary":"Blog Share","description":"Increment a post's share counter (fired when the reader actually\nshares or copies the link). Returns new displayed totals.","operationId":"blog_share_blog__slug__share_post","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sitemap.xml":{"get":{"summary":"Sitemap Xml","description":"Auto-generated sitemap covering static pages + all blog posts.\n\nSubmitted once to Google Search Console + Bing Webmaster Tools, then\nthey re-crawl periodically and auto-discover new blog posts on every\nprocess restart that ships fresh content/blog/*.md files.\n\nPriorities are rough guidance, not hard rules — Google mostly ignores\nthem but Bing still uses them as a tiebreaker. We mark the homepage\nat 1.0, blog index at 0.8, individual posts at 0.7, marketing pages\nat 0.6, legal at 0.3.","operationId":"sitemap_xml_sitemap_xml_get","responses":{"200":{"description":"Successful Response"}}},"head":{"summary":"Sitemap Xml","description":"Auto-generated sitemap covering static pages + all blog posts.\n\nSubmitted once to Google Search Console + Bing Webmaster Tools, then\nthey re-crawl periodically and auto-discover new blog posts on every\nprocess restart that ships fresh content/blog/*.md files.\n\nPriorities are rough guidance, not hard rules — Google mostly ignores\nthem but Bing still uses them as a tiebreaker. We mark the homepage\nat 1.0, blog index at 0.8, individual posts at 0.7, marketing pages\nat 0.6, legal at 0.3.","operationId":"sitemap_xml_sitemap_xml_get","responses":{"200":{"description":"Successful Response"}}}},"/robots.txt":{"get":{"summary":"Robots Txt","description":"Permissive robots.txt — we WANT to be indexed (including by GPTBot\n/ ChatGPT-User / Claude-Web for LLM-SEO purposes; we do NOT block them).\nThe Disallow list covers:\n  - /api/*           — JSON endpoints, useless to index\n  - /generate        — htmx fragment endpoint, useless to index\n  - /login, /logout  — auth flow, never useful in search results\n  - /account/        — per-user pages, never crawlable anyway\n  - /history/        — per-user history, never crawlable anyway\n\nPoints crawlers at the sitemap so they can discover blog posts\nwithout depending on internal-link crawl depth.","operationId":"robots_txt_robots_txt_get","responses":{"200":{"description":"Successful Response","content":{"text/plain":{"schema":{"type":"string"}}}}}},"head":{"summary":"Robots Txt","description":"Permissive robots.txt — we WANT to be indexed (including by GPTBot\n/ ChatGPT-User / Claude-Web for LLM-SEO purposes; we do NOT block them).\nThe Disallow list covers:\n  - /api/*           — JSON endpoints, useless to index\n  - /generate        — htmx fragment endpoint, useless to index\n  - /login, /logout  — auth flow, never useful in search results\n  - /account/        — per-user pages, never crawlable anyway\n  - /history/        — per-user history, never crawlable anyway\n\nPoints crawlers at the sitemap so they can discover blog posts\nwithout depending on internal-link crawl depth.","operationId":"robots_txt_robots_txt_get","responses":{"200":{"description":"Successful Response","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/generate":{"post":{"summary":"Generate","operationId":"generate_generate_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_generate_generate_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/history":{"get":{"summary":"History List","operationId":"history_list_history_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/history/{entry_id}":{"get":{"summary":"History Detail","operationId":"history_detail_history__entry_id__get","parameters":[{"name":"entry_id","in":"path","required":true,"schema":{"type":"integer","title":"Entry Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/account/keys":{"get":{"summary":"Account Keys Page","description":"Self-service API-key management. Any signed-in user (Free or Pro) —\nthe API endpoint itself enforces the per-tier quota.","operationId":"account_keys_page_account_keys_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}},"post":{"summary":"Account Keys Create","operationId":"account_keys_create_account_keys_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_account_keys_create_account_keys_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/account/keys/{key_id}/revoke":{"post":{"summary":"Account Keys Revoke","operationId":"account_keys_revoke_account_keys__key_id__revoke_post","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"integer","title":"Key Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/refine":{"post":{"summary":"Api Refine","description":"Programmatic prompt refinement. Auth: `Authorization: Bearer af_...`.\n\nMetered per account (not per key): Free accounts share one daily pool\nacross gmail aliases and get 429 over the cap; Pro is unlimited. The\nfree-tier attribution line (Phase A) is part of the returned prompt, so\nthe growth loop runs through the MCP channel too.","operationId":"api_refine_api_v1_refine_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiRefineRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Api Refine Api V1 Refine Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/login":{"get":{"summary":"Login Form","operationId":"login_form_login_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}},"post":{"summary":"Login Submit","operationId":"login_submit_login_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_login_submit_login_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/logout":{"get":{"summary":"Logout","description":"Delegate to spine's /logout — it owns the .sciscale.org session cookie\n(Domain=.sciscale.org + Secure), and AgentForge can't clear that from its\nown origin because Set-Cookie clear requires matching domain/secure attrs.\nspine clears, then 302s back to `next`.","operationId":"logout_logout_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/healthz":{"get":{"summary":"Healthz","operationId":"healthz_healthz_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Healthz Healthz Get"}}}}}}}},"components":{"schemas":{"ApiRefineRequest":{"properties":{"input":{"type":"string","maxLength":4000,"minLength":1,"title":"Input"},"target_tool":{"type":"string","title":"Target Tool","default":"claude-code"},"style":{"type":"string","title":"Style","default":"plan-first"}},"type":"object","required":["input"],"title":"ApiRefineRequest"},"Body_account_keys_create_account_keys_post":{"properties":{"label":{"type":"string","title":"Label","default":""}},"type":"object","title":"Body_account_keys_create_account_keys_post"},"Body_generate_generate_post":{"properties":{"user_input":{"type":"string","maxLength":4000,"minLength":1,"title":"User Input"},"target_tool":{"type":"string","title":"Target Tool"},"style":{"type":"string","title":"Style"},"extra_constraints":{"type":"string","title":"Extra Constraints","default":""},"variant":{"type":"string","title":"Variant","default":"balanced"}},"type":"object","required":["user_input","target_tool","style"],"title":"Body_generate_generate_post"},"Body_login_submit_login_post":{"properties":{"email":{"type":"string","maxLength":254,"title":"Email"}},"type":"object","required":["email"],"title":"Body_login_submit_login_post"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}}}