{"openapi":"3.0.3","info":{"title":"BackendX API","version":"1.0.0","description":"BackendX is an AI-powered service that automatically generates backend infrastructure (APIs, DB schemas, Docker, source code) from user requirements.\n\n## Server URLs\n\n- **Authentication only**: `https://www.backendx.ai` (the frontend origin serving this spec).\n- **All other API calls**: `http://flask:8000` (the backend API server).\n\n## Agent Workflow (step-by-step)\n\n1. **Authenticate**: `POST https://www.backendx.ai/api/auth/agent/google` with a Google ID Token → receive a BackendX Bearer token. If the user does not exist yet, an account is automatically created (no separate signup step). Use this token in all subsequent `Authorization: Bearer {token}` headers. Note: this is the only endpoint on the frontend server.\n   - **If you don't have a Google ID Token**, obtain one via gcloud CLI:\n     1. Install gcloud CLI: https://cloud.google.com/sdk/docs/install\n     2. Log in: `gcloud auth login`\n     3. Print the token: `gcloud auth print-identity-token`\n2. **Accept EULA** (first time only): `GET /api/user/eula/` to check status. If `agreed` is false, `POST /api/user/eula/?version={v}&langcode={lc}` to accept.\n3. **Ask for notification email**: Before creating the project, ask the user for an email address to receive build completion notifications. The AI backend will ask for this email during the conversation, so collecting it upfront avoids an extra round-trip.\n4. **Create a project**: `POST /api/user/projects/` → returns `{uuid}`.\n5. **Send messages**: `POST /api/user/projects/{project_uuid}/messages` with `{\"message\": \"...\"}`. The response is **202 Accepted** with `{task_id}`. You must then poll the task status.\n6. **Poll task status**: Poll `GET /api/user/projects/{project_uuid}/status/{task_id}` every 2 seconds. See the polling protocol below.\n7. **Check for AI response**: When polling returns 200 with `fetch_messages: true`, fetch messages via `GET /api/user/projects/{project_uuid}/messages`. The AI typically asks **5-8 follow-up questions** (e.g., auth method, admin roles, business rules, sorting, concurrency, data retention, notifications, limits) — continue the conversation by sending more messages (step 5).\n8. **Wait for pipeline**: The AI drives the generation pipeline automatically. Keep polling and responding to questions until `workflow_state` reaches `7` (ReadyToRun).\n9. **Retrieve free artifacts**: Once `workflow_state` reaches `7`, API docs (`/apis`) and Docker images (`/dockerfiles`) are available for free.\n10. **Purchase paid bundles** (optional): Other artifacts require purchasing. First fetch current prices from `GET /api/metadata?key=menu_item_prices`, then `GET /api/user/projects/{project_uuid}/{artifact}/purchase` to check status, `POST` to purchase.\n11. **Configure environment** (recommended before downloading): `GET /api/user/projects/{project_uuid}/editable-keys` returns a list of configurable keys. Each item has: `filename` (config file name), `key` (variable name), `description` (human-readable hint), `current_value` (current setting), and `is_current_default` (whether the value is a placeholder). Handle each item based on its state: (a) `current_value` is empty → **MUST** fill with a real value; (b) `is_current_default: true` with non-empty `current_value` → **SHOULD** replace (the value is a working placeholder but insecure/non-functional for production); (c) `is_current_default: false` with non-empty `current_value` → **SKIP** (already set by the user, do not override). Only send items that need new values via `POST /api/user/projects/{project_uuid}/env-overrides` with body `{\"overrides\": [{\"filename\": \"...\", \"key\": \"...\", \"new_value\": \"...\"}]}`. Then proceed to download.\n12. **Download project files**: `GET /api/user/projects/{project_uuid}/files/zip` to download all files as a ZIP archive. Alternatively, download individual files via `GET /api/user/projects/{project_uuid}/files/{file_name}` using names from `ProjectData.files_for_local_run`.\n13. **Run locally**: Extract the ZIP, then run `docker compose up -d` in the extracted directory. The archive contains `docker-compose.yaml` and all necessary configuration files.\n14. **Verify**: Run `docker compose ps` to confirm all containers are healthy. Then check the API docs (`/apis`) for the health check endpoint and call it to verify the service is responding. No need to test every API endpoint individually — if containers are healthy and the health check passes, the project is considered working.\n\n## Polling Protocol\n\nPoll `GET /api/user/projects/{project_uuid}/status/{task_id}` every ~2 seconds. Interpret the HTTP status code:\n- **200 OK**: Task complete. Stop polling. Check response flags.\n- **202 Accepted**: Task still running. Continue polling. May include `interim_message`.\n- **409 Conflict**: Another client is already working on this project. Stop polling.\n- **404 Not Found**: Task no longer exists. Stop polling.\n- **Other errors (5xx, network timeout, or no response for an extended period at ANY pipeline stage)**: The pipeline stage may have failed. This recovery flow applies to all stages (0–7), not just a specific stage. Call `POST /api/user/projects/{project_uuid}/retry` to retry the failed stage, then resume polling with the new `task_id` returned in the project data. If retry returns `{\"error\": \"Pipeline is still running\", \"running_stages\": [...]}`, the pipeline is NOT stuck — it is still working. In this case, send any message (e.g. `{\"message\": \"1\"}`) via `POST /api/user/projects/{project_uuid}/messages` to nudge the pipeline forward, then re-fetch project info for a new `task_id` and resume polling. Retry thresholds: **Stage 5 (WritingCode): 15+ minutes**, **all other stages: 5+ minutes** with no `progress_message` change.\n\nResponse flags (TaskStatusResponse):\n- `fetch_messages: true` → Fetch new messages via `GET /api/user/projects/{project_uuid}/messages`.\n- `fetch_project_info: true` → Re-fetch project data via `GET /api/user/projects/{project_uuid}/`.\n- `task_id: \"<new_id>\"` → Backend redirected to a new task. Switch polling to the new task_id.\n- `interim_message: \"...\"` → Temporary status text (not persisted; will disappear on next message fetch).\n\n## Credit System\n\n- 1 credit = KRW 100.\n- Check current balance via `GET /api/user/credits/`.\n- Credit purchase requires a browser (Toss payment widget). Agents cannot purchase credits via API.\n\n### Pricing\n\nFetch current prices from `GET /api/metadata?key=menu_item_prices`. Response fields:\n- `architecture`, `db_schema`, `srs`, `dockerfile`, `repo`: credits per artifact unlock\n- `extra_project`: credits for additional project creation\n- `extra_project_free_count`: number of free projects before charges apply\n- `api_clipboard`, `deployment`: other feature costs\n\n### Free artifacts (no purchase required)\n- API documentation (`/apis`)\n- Docker images (`/dockerfiles`)\n\n## Cost & Time Expectations\n\nCode generation takes longer than a typical LLM chat response (minutes to tens of minutes depending on project complexity), but is significantly cheaper than using LLM-based coding agents such as Claude or Codex directly. BackendX batches the entire backend generation pipeline (architecture analysis, API extraction, DB schema, code writing, Dockerization) into a single credit-based transaction, making it more cost-effective for full-stack backend scaffolding.\n\n## Message Ordering\n\n`GET /api/user/projects/{project_uuid}/messages` returns messages in **reverse chronological order** (newest first). Agents must reverse the array for chronological display. Use `offset` and `limit` query parameters for pagination.\n\n## Frozen Projects\n\nWhen `ProjectData.frozen` is `true`, the project is locked and message submission is rejected. Check the `frozen` field before attempting to send messages.\n\n## Browser-Only Features (not available to agents)\n\n- **Credit purchase**: Requires Toss payment widget (browser pop-up).\n- **GitHub OAuth linking**: Requires browser redirect flow. Alternative: use `PUT /api/user/github/?access_token={token}` if you already have a GitHub access token.","contact":{"name":"BackendX Support","url":"https://backendx.com"}},"servers":[{"url":"http://flask:8000","description":"Backend API server. Use for all endpoints EXCEPT /api/auth/agent/google."}],"security":[{"BearerAuth":[]}],"x-agent-limitations":{"browser_only":[{"feature":"Credit purchase","reason":"Requires Toss payment widget, a browser-hosted pop-up/redirect flow.","workaround":"User must purchase credits via browser before agent can spend them."},{"feature":"GitHub OAuth linking","reason":"Requires browser redirect to github.com/login/oauth.","workaround":"Use PUT /api/user/github/?access_token={github_token} if agent already has a GitHub access token."}]},"paths":{"/api/auth/agent/google":{"servers":[{"url":"https://www.backendx.ai","description":"This endpoint is hosted on the Next.js frontend, NOT the backend."}],"post":{"tags":["Authentication"],"summary":"Authenticate with Google ID Token","description":"Exchange a Google OAuth2 ID Token for a BackendX Bearer token. Obtain the ID Token via `gcloud auth print-identity-token`. The returned token is used in all subsequent API calls to the backend as `Authorization: Bearer {token}`. IMPORTANT: This is the only endpoint hosted on the frontend. All other API calls go to the backend.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id_token"],"properties":{"id_token":{"type":"string","description":"Google OAuth2 ID Token (from `gcloud auth print-identity-token`)"}}}}}},"responses":{"200":{"description":"Authentication successful","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"400":{"description":"Missing or invalid id_token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/user/":{"get":{"tags":["User"],"summary":"Get current user profile","responses":{"200":{"description":"User profile","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserData"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["User"],"summary":"Delete current user account","responses":{"204":{"description":"Account deleted"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/user/eula/":{"get":{"tags":["User"],"summary":"Check EULA acceptance status","description":"Returns whether the user has accepted the EULA. Must be accepted before using other endpoints.","responses":{"200":{"description":"EULA status","content":{"application/json":{"schema":{"type":"object","properties":{"agreed":{"type":"boolean","description":"Whether the user has accepted the EULA"},"version":{"type":"string","description":"EULA version string"}}}}}}}},"post":{"tags":["User"],"summary":"Accept the EULA","parameters":[{"name":"version","in":"query","required":true,"schema":{"type":"string"},"description":"EULA version to accept (from GET response)"},{"name":"langcode","in":"query","required":true,"schema":{"type":"string","enum":["en-US","ko-KR"]},"description":"Language of the accepted EULA"}],"responses":{"200":{"description":"EULA accepted"},"400":{"description":"Missing version or langcode","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/user/credits/":{"get":{"tags":["User"],"summary":"Check credit balance","description":"Returns the current credit balance for the authenticated user. 1 credit = KRW 100. Credits can only be purchased via browser (Toss payment widget).","responses":{"200":{"description":"Credit balance","content":{"application/json":{"schema":{"type":"object","properties":{"credits":{"type":"integer","description":"Current credit balance (1 credit = KRW 100)"}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/user/github/":{"get":{"tags":["User"],"summary":"Get GitHub integration info","description":"Returns the linked GitHub account, or null if not linked.","responses":{"200":{"description":"GitHub account info or null","content":{"application/json":{"schema":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/UserAuthInfo"}]}}}}}},"put":{"tags":["User"],"summary":"Link GitHub account","description":"Link a GitHub account using a GitHub access token. This is the agent-compatible alternative to the browser OAuth flow.","parameters":[{"name":"access_token","in":"query","required":true,"schema":{"type":"string"},"description":"GitHub personal access token"}],"responses":{"200":{"description":"GitHub account linked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserAuthInfo"}}}},"400":{"description":"Missing access_token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/user/notification_flag/":{"get":{"tags":["User"],"summary":"Get notification preferences","responses":{"200":{"description":"Notification flag configuration","content":{"application/json":{"schema":{"type":"object"}}}}}},"put":{"tags":["User"],"summary":"Update notification preferences","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"Notification flag settings"}}}},"responses":{"200":{"description":"Notification preferences updated"}}}},"/api/user/projects/":{"get":{"tags":["Projects"],"summary":"List all projects","description":"Returns projects in reverse chronological order (newest first).","responses":{"200":{"description":"Project list","content":{"application/json":{"schema":{"type":"object","properties":{"projects":{"type":"array","items":{"$ref":"#/components/schemas/ProjectSummary"}}}}}}}}},"post":{"tags":["Projects"],"summary":"Create a new project","description":"Creates an empty project. Send messages via /api/user/projects/{project_uuid}/messages to start requirement gathering. Returns 402 if insufficient credits. Check extra_project cost and extra_project_free_count via GET /api/metadata?key=menu_item_prices.","responses":{"201":{"description":"Newly created project","content":{"application/json":{"schema":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid","description":"New project UUID"}}}}}},"402":{"description":"Insufficient credits. User must purchase credits via browser."}}}},"/api/user/projects/{project_uuid}/":{"get":{"tags":["Projects"],"summary":"Get project details","description":"Returns full project data including workflow_state, frozen status, and task_id for polling.","parameters":[{"$ref":"#/components/parameters/ProjectUuid"}],"responses":{"200":{"description":"Project details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectData"}}}}}},"delete":{"tags":["Projects"],"summary":"Delete a project","parameters":[{"$ref":"#/components/parameters/ProjectUuid"}],"responses":{"204":{"description":"Project deleted"}}}},"/api/user/projects/{project_uuid}/messages":{"get":{"tags":["Projects"],"summary":"List chat messages","description":"Returns messages in **reverse chronological order** (newest first). Agent must reverse the array for chronological display. Use offset and limit for pagination.","parameters":[{"$ref":"#/components/parameters/ProjectUuid"},{"name":"offset","in":"query","schema":{"type":"integer"},"description":"Number of messages to skip from newest"},{"name":"limit","in":"query","schema":{"type":"integer"},"description":"Maximum number of messages to return"}],"responses":{"200":{"description":"Messages with pagination metadata","content":{"application/json":{"schema":{"type":"object","properties":{"messages":{"type":"array","description":"Messages in reverse chronological order","items":{"$ref":"#/components/schemas/Message"}},"offset":{"type":"integer","description":"Current offset"},"uuid":{"type":"string","format":"uuid","description":"Project UUID"}}}}}}}},"post":{"tags":["Projects"],"summary":"Send a chat message","description":"Send a requirement message to the AI. This triggers an async LLM task — the response contains a `task_id` that must be polled via GET /status/{task_id}. Will be rejected if the project is frozen or another task is in progress.","parameters":[{"$ref":"#/components/parameters/ProjectUuid"},{"name":"langcode","in":"query","required":false,"schema":{"type":"string","enum":["en-US","ko-KR"]},"description":"Message language code"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["message"],"properties":{"message":{"type":"string","description":"Message text (user requirement)"}}}}}},"responses":{"202":{"description":"Message accepted. Poll the returned task_id for completion.","content":{"application/json":{"schema":{"type":"object","properties":{"task_id":{"type":"string","description":"Async task ID. Poll GET /status/{task_id} every ~2 seconds."}}}}}},"400":{"description":"Message content is required"},"409":{"description":"Project is busy with another task. Wait and retry."}}}},"/api/user/projects/{project_uuid}/status/{task_id}":{"get":{"tags":["Projects"],"summary":"Poll async task status","description":"Poll every ~2 seconds when project has a non-null task_id. HTTP 200 = complete, 202 = in progress, 409 = conflict (another client working), 404 = task not found. See info.description for full polling protocol.","parameters":[{"$ref":"#/components/parameters/ProjectUuid"},{"name":"task_id","in":"path","required":true,"schema":{"type":"string"},"description":"Async task identifier from ProjectData.task_id"}],"responses":{"200":{"description":"Task complete. Check flags to determine next actions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskStatusResponse"}}}},"202":{"description":"Task in progress. Continue polling. May include interim_message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskStatusResponse"}}}},"404":{"description":"Task not found. Stop polling."},"409":{"description":"Conflict. Another client is working on this project. Stop polling."}}}},"/api/user/projects/{project_uuid}/retry":{"post":{"tags":["Projects"],"summary":"Retry failed pipeline stage","description":"Retries the last failed pipeline stage for the project. Applicable to ANY pipeline stage (0–7). Use this when polling stops receiving responses (5xx, timeout, or prolonged silence) at any stage. After calling, re-fetch the project data to get the new task_id and resume polling. If this returns a response indicating \"Pipeline is still running\", the pipeline is NOT stuck — send any message (e.g. {\"message\": \"1\"}) via POST /messages to nudge the pipeline forward, then re-fetch project info for a new task_id and resume polling.","parameters":[{"$ref":"#/components/parameters/ProjectUuid"}],"responses":{"200":{"description":"Retry initiated. Re-fetch project data to get the new task_id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectData"}}}},"409":{"description":"Project is busy with another task. Wait and retry."}}}},"/api/user/projects/{project_uuid}/apis":{"get":{"tags":["Generated Artifacts"],"summary":"Get generated API documentation (free)","parameters":[{"$ref":"#/components/parameters/ProjectUuid"},{"name":"langcode","in":"query","schema":{"type":"string","enum":["en-US","ko-KR"]},"description":"API doc language. Defaults to \"en-US\"."},{"name":"format","in":"query","schema":{"type":"string","enum":["markdown","openapi"]},"description":"Output format. Defaults to \"markdown\"."},{"name":"revision","in":"query","schema":{"type":"integer"},"description":"Revision number (SRS ID). Omit for latest."}],"responses":{"200":{"description":"API documentation","content":{"application/json":{"schema":{"type":"object","properties":{"apis":{"type":"string","description":"API documentation in Markdown"},"revision":{"type":"integer","description":"Revision number (SRS ID)"},"uuid":{"type":"string","format":"uuid"},"deployments":{"type":"array","description":"Active deployments. Empty if not deployed.","items":{"$ref":"#/components/schemas/DeploymentInfo"}}}}}}}}}},"/api/user/projects/{project_uuid}/architecture":{"get":{"tags":["Generated Artifacts"],"summary":"Get generated architecture document","parameters":[{"$ref":"#/components/parameters/ProjectUuid"}],"responses":{"200":{"description":"Architecture diagram","content":{"application/json":{"schema":{"type":"object","properties":{"architecture":{"description":"Architecture data (JSON string or object with components and edges)"},"uuid":{"type":"string","format":"uuid"}}}}}},"402":{"description":"Architecture not purchased"}}}},"/api/user/projects/{project_uuid}/srs":{"get":{"tags":["Generated Artifacts"],"summary":"Get generated Software Requirements Specification","parameters":[{"$ref":"#/components/parameters/ProjectUuid"},{"name":"revision","in":"query","schema":{"type":"integer"},"description":"Revision number (SRS ID). Omit for latest."}],"responses":{"200":{"description":"SRS document","content":{"application/json":{"schema":{"type":"object","properties":{"srs":{"type":"string","description":"SRS content"},"revision":{"type":"integer"},"uuid":{"type":"string","format":"uuid"}}}}}},"402":{"description":"SRS not purchased"}}}},"/api/user/projects/{project_uuid}/db_schemas":{"get":{"tags":["Generated Artifacts"],"summary":"Get generated DB schemas","parameters":[{"$ref":"#/components/parameters/ProjectUuid"},{"name":"revision","in":"query","schema":{"type":"integer"},"description":"Revision number (SRS ID). Omit for latest."}],"responses":{"200":{"description":"Database schema definitions","content":{"application/json":{"schema":{"type":"object","properties":{"db_schemas":{"type":"object","additionalProperties":{"type":"string"},"description":"DB schemas keyed by database name, values are Markdown"},"revision":{"type":"integer"},"uuid":{"type":"string","format":"uuid"}}}}}},"402":{"description":"DB schema not purchased"}}}},"/api/user/projects/{project_uuid}/dockerfiles":{"get":{"tags":["Generated Artifacts"],"summary":"Get generated Dockerfiles (free)","parameters":[{"$ref":"#/components/parameters/ProjectUuid"}],"responses":{"200":{"description":"Dockerfile contents","content":{"application/json":{"schema":{"type":"object","properties":{"dockerfiles":{"type":"object","additionalProperties":{"type":"string"},"description":"Dockerfiles keyed by service name"},"uuid":{"type":"string","format":"uuid"}}}}}},"402":{"description":"Dockerfiles not purchased"}}}},"/api/user/projects/{project_uuid}/repo":{"get":{"tags":["Generated Artifacts"],"summary":"Get generated source code repository info","parameters":[{"$ref":"#/components/parameters/ProjectUuid"}],"responses":{"200":{"description":"Repository URL","content":{"application/json":{"schema":{"type":"object","properties":{"repo_http_url":{"type":"string","description":"Source repository HTTP URL"},"uuid":{"type":"string","format":"uuid"}}}}}},"402":{"description":"Source repo not purchased"}}}},"/api/user/projects/{project_uuid}/files/{file_name}":{"get":{"tags":["Generated Artifacts"],"summary":"Download a project file","description":"Returns raw binary content with Content-Disposition: attachment header.","parameters":[{"$ref":"#/components/parameters/ProjectUuid"},{"name":"file_name","in":"path","required":true,"schema":{"type":"string"},"description":"File name from ProjectData.files_for_local_run array"}],"responses":{"200":{"description":"File binary content","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}}}}},"/api/user/projects/{project_uuid}/files/zip":{"get":{"tags":["Generated Artifacts"],"summary":"Download all project files as ZIP","parameters":[{"$ref":"#/components/parameters/ProjectUuid"}],"responses":{"200":{"description":"ZIP archive of all project files","content":{"application/zip":{"schema":{"type":"string","format":"binary"}}}}}}},"/api/user/projects/{project_uuid}/editable-keys":{"get":{"tags":["Projects"],"summary":"Get editable project fields","description":"Returns a list of environment variable keys that can be customized before downloading files. Each item includes filename, key, description, current_value, and is_current_default.","parameters":[{"$ref":"#/components/parameters/ProjectUuid"}],"responses":{"200":{"description":"Array of editable key items","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/EditableKeyItem"}}}}}}}},"/api/user/projects/{project_uuid}/env-overrides":{"post":{"tags":["Projects"],"summary":"Set environment variable overrides","description":"Override environment variable values before downloading project files. Apply overrides, then download via /files/zip.","parameters":[{"$ref":"#/components/parameters/ProjectUuid"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["overrides"],"properties":{"overrides":{"type":"array","items":{"type":"object","required":["filename","key","new_value"],"properties":{"filename":{"type":"string"},"key":{"type":"string"},"new_value":{"type":"string"}}}}}}}}},"responses":{"200":{"description":"Environment overrides applied","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/api/user/projects/{project_uuid}/verify-urls":{"get":{"tags":["Projects"],"summary":"Verify project-related URLs","parameters":[{"$ref":"#/components/parameters/ProjectUuid"}],"responses":{"200":{"description":"URL verification results","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/api/user/projects/{project_uuid}/architecture/purchase":{"get":{"tags":["Purchases"],"summary":"Check architecture bundle purchase status","parameters":[{"$ref":"#/components/parameters/ProjectUuid"}],"responses":{"200":{"description":"Purchase status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PurchaseStatus"}}}}}},"post":{"tags":["Purchases"],"summary":"Purchase the architecture document bundle","description":"Fetch current cost from GET /api/metadata?key=menu_item_prices (architecture field). Returns 200 even if already purchased.","parameters":[{"$ref":"#/components/parameters/ProjectUuid"}],"responses":{"200":{"description":"Purchase successful (or already purchased)"},"402":{"description":"Insufficient credits"},"500":{"description":"Price not configured"}}}},"/api/user/projects/{project_uuid}/srs/purchase":{"get":{"tags":["Purchases"],"summary":"Check SRS bundle purchase status","parameters":[{"$ref":"#/components/parameters/ProjectUuid"}],"responses":{"200":{"description":"Purchase status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PurchaseStatus"}}}}}},"post":{"tags":["Purchases"],"summary":"Purchase the SRS document bundle","parameters":[{"$ref":"#/components/parameters/ProjectUuid"}],"responses":{"200":{"description":"Purchase successful (or already purchased)"},"402":{"description":"Insufficient credits"},"500":{"description":"Price not configured"}}}},"/api/user/projects/{project_uuid}/db-schemas/purchase":{"get":{"tags":["Purchases"],"summary":"Check DB schema bundle purchase status","parameters":[{"$ref":"#/components/parameters/ProjectUuid"}],"responses":{"200":{"description":"Purchase status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PurchaseStatus"}}}}}},"post":{"tags":["Purchases"],"summary":"Purchase the DB schema bundle","parameters":[{"$ref":"#/components/parameters/ProjectUuid"}],"responses":{"200":{"description":"Purchase successful (or already purchased)"},"402":{"description":"Insufficient credits"},"500":{"description":"Price not configured"}}}},"/api/user/projects/{project_uuid}/dockerfiles/purchase":{"get":{"tags":["Purchases"],"summary":"Check Dockerfile bundle purchase status","parameters":[{"$ref":"#/components/parameters/ProjectUuid"}],"responses":{"200":{"description":"Purchase status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PurchaseStatus"}}}}}},"post":{"tags":["Purchases"],"summary":"Purchase the Dockerfile bundle","parameters":[{"$ref":"#/components/parameters/ProjectUuid"}],"responses":{"200":{"description":"Purchase successful (or already purchased)"},"402":{"description":"Insufficient credits"},"500":{"description":"Price not configured"}}}},"/api/user/projects/{project_uuid}/repo/purchase":{"get":{"tags":["Purchases"],"summary":"Check source repo bundle purchase status","parameters":[{"$ref":"#/components/parameters/ProjectUuid"}],"responses":{"200":{"description":"Purchase status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PurchaseStatus"}}}}}},"post":{"tags":["Purchases"],"summary":"Purchase the source code repository bundle","parameters":[{"$ref":"#/components/parameters/ProjectUuid"}],"responses":{"200":{"description":"Purchase successful (or already purchased)"},"402":{"description":"Insufficient credits"},"500":{"description":"Price not configured"}}}},"/api/store/products":{"get":{"tags":["Store"],"summary":"List available credit products","description":"Returns purchasable credit packages. Use the optional `pg` query parameter to force a payment gateway (toss/paddle); when omitted, the gateway is determined by client country (GeoIP via X-Forwarded-For). Actual payment must be completed via browser (Toss payment widget for KR, Paddle overlay for non-KR).","parameters":[{"name":"pg","in":"query","required":false,"schema":{"type":"string","enum":["toss","paddle"]},"description":"Force payment gateway. Omit to let the backend decide by country."}],"responses":{"200":{"description":"Array of credit products","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}}}}},"/api/metadata":{"get":{"tags":["Misc"],"summary":"Get service metadata","description":"Returns dynamic service metadata. Use key=menu_item_prices to fetch current artifact pricing (architecture, db_schema, srs, dockerfile, repo, extra_project, extra_project_free_count, api_clipboard, deployment).","parameters":[{"name":"key","in":"query","required":true,"schema":{"type":"string","enum":["menu_item_prices"]},"description":"Metadata key to retrieve. Use 'menu_item_prices' for artifact pricing."}],"responses":{"200":{"description":"Metadata value (schema varies by key)","content":{"application/json":{"schema":{"type":"object","description":"For key=menu_item_prices: object with fields like architecture, db_schema, srs, dockerfile, repo (each an integer representing credit cost), extra_project, extra_project_free_count."}}}},"400":{"description":"Missing key parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/task-duration-average":{"get":{"tags":["Misc"],"summary":"Get average duration per pipeline stage","description":"Returns average processing time in seconds for each generation stage. Useful for estimating wait times.","responses":{"200":{"description":"Object mapping stage names to average seconds (null if no data)","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"nullable":true,"type":"number"}}}}}}}},"/api/quote/":{"get":{"tags":["Misc"],"summary":"Get a quote","parameters":[{"name":"langcode","in":"query","schema":{"type":"string","enum":["en-US","ko-KR"]}},{"name":"length","in":"query","schema":{"type":"integer"}},{"name":"quote_type","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Quote data","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/api/contact/":{"post":{"tags":["Misc"],"summary":"Submit a contact inquiry","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","email","subject","content"],"properties":{"name":{"type":"string"},"email":{"type":"string","format":"email"},"subject":{"type":"string"},"content":{"type":"string"}}}}}},"responses":{"200":{"description":"Inquiry submitted"},"400":{"description":"Missing or invalid fields","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"POST /api/auth/agent/google with a Google ID Token (from `gcloud auth print-identity-token`) to obtain a Bearer token. Token expires in ~30 days; re-authenticate when expired."}},"parameters":{"ProjectUuid":{"name":"project_uuid","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Project identifier"}},"schemas":{"TokenResponse":{"type":"object","properties":{"token":{"type":"string","description":"BackendX Bearer token (use in Authorization: Bearer {token} header)"},"token_type":{"type":"string","enum":["bearer"]},"expires_in":{"type":"number","description":"Seconds until expiration"}}},"ProjectSummary":{"type":"object","description":"Abbreviated project info returned in list endpoints","properties":{"uuid":{"type":"string","format":"uuid"},"label":{"type":"string","description":"Project display name"},"workflow_state":{"$ref":"#/components/schemas/WorkflowState"},"created_at":{"type":"string","format":"date-time"}}},"ProjectData":{"type":"object","properties":{"uuid":{"type":"string","format":"uuid"},"created_at":{"type":"string","format":"date-time"},"workflow_state":{"$ref":"#/components/schemas/WorkflowState"},"frozen":{"type":"boolean","description":"If true, the project is locked and messages cannot be sent"},"progress_value":{"type":"number","nullable":true,"description":"Current pipeline progress (0.0 - 1.0)"},"progress_message":{"type":"string","nullable":true,"description":"Human-readable progress message"},"label":{"type":"string","nullable":true,"description":"Project display name (auto-generated from chat)"},"task_id":{"type":"string","nullable":true,"description":"Active async task ID. If non-null, poll /status/{task_id}"},"docker_image_names_with_tags":{"type":"array","items":{"type":"string"},"description":"Generated Docker image names with tags"},"docker_images_created_at":{"type":"string","format":"date-time","nullable":true},"files_for_local_run":{"type":"array","items":{"type":"string"},"description":"File names available for download via /files/{file_name}"},"deployments":{"type":"array","description":"Active deployment list","items":{"$ref":"#/components/schemas/DeploymentInfo"}},"watchdog_alert_email":{"type":"string","nullable":true,"description":"Email address for watchdog alerts (null if not set)"},"has_purchased_srs":{"type":"boolean"},"has_purchased_architecture":{"type":"boolean"},"has_purchased_db_schema":{"type":"boolean"},"has_purchased_source_repo":{"type":"boolean"},"has_purchased_dockerfiles":{"type":"boolean"},"has_purchased_api_clipboard":{"type":"boolean"},"revisions":{"type":"array","items":{"type":"integer"},"description":"Available revision numbers (SRS IDs), oldest first"}}},"DeploymentInfo":{"type":"object","properties":{"cloud_provider":{"type":"string","description":"Cloud provider (e.g. aws)"},"region":{"type":"string","description":"Deployment region"},"deployed_at":{"type":"string","format":"date-time","description":"Deployment timestamp"},"ingress_endpoints":{"type":"array","items":{"type":"object","properties":{"service":{"type":"string"},"url":{"type":"string"},"is_default":{"type":"boolean"}}}}}},"WorkflowState":{"type":"integer","enum":[0,1,2,3,4,5,6,7],"description":"Project pipeline stage: 0=TakingRequirements (depends on conversation), 1=VerifyingRequirements (~1min), 2=AnalyzingArchitecture (~5min), 3=ExtractingAPIs (~5min), 4=GeneratingDBSchema (~5min), 5=WritingCode (30+ minutes, slowest stage), 6=Dockerizing (~5-8min), 7=ReadyToRun (done)"},"Message":{"type":"object","properties":{"id":{"type":"integer","description":"Message ID"},"content":{"type":"string","description":"Message text (Markdown supported)"},"role":{"type":"string","enum":["human","ai"],"description":"Message sender: human (user) or ai (system)"},"created_at":{"type":"string","format":"date-time"}}},"UserData":{"type":"object","properties":{"subscription":{"type":"string","nullable":true},"auths":{"type":"array","items":{"$ref":"#/components/schemas/UserAuthInfo"},"description":"Linked authentication providers"}}},"UserAuthInfo":{"type":"object","properties":{"provider":{"type":"string","description":"Auth provider (e.g. github, google)"},"name":{"type":"string"},"email":{"type":"string","format":"email"},"login":{"type":"string","description":"Provider username/login"}}},"PurchaseStatus":{"type":"object","properties":{"purchased":{"type":"boolean","description":"Whether the artifact has been purchased"}}},"TaskStatusResponse":{"type":"object","description":"Response from polling /status/{task_id}. Use HTTP status code (200/202/404/409) to determine task state, then check these flags for follow-up actions.","properties":{"error_code":{"type":"integer","description":"Error code if task failed"},"task_id":{"type":"string","nullable":true,"description":"If present, backend is redirecting to a different task. Switch polling to this new task_id."},"interim_message":{"type":"string","description":"Temporary status message. Not persisted; disappears on next message fetch."},"fetch_messages":{"type":"boolean","description":"If true, new messages are available. Fetch them via GET /api/user/projects/{project_uuid}/messages."},"fetch_project_info":{"type":"boolean","nullable":true,"description":"If true, project data has changed. Re-fetch via GET /api/user/projects/{project_uuid}/."}}},"EditableKeyItem":{"type":"object","properties":{"filename":{"type":"string","description":"Configuration file name (e.g. .env)"},"key":{"type":"string","description":"Environment variable key"},"description":{"type":"string","description":"Human-readable description of the key"},"current_value":{"type":"string","description":"Current value (may be empty or default)"},"is_current_default":{"type":"boolean","description":"If true, value is a placeholder default that should be changed"}}},"Error":{"type":"object","properties":{"error":{"type":"string","description":"Error message"}}}}},"tags":[{"name":"Authentication","description":"Authenticate via POST /api/auth/agent/google with a Google ID Token to receive a BackendX Bearer token"},{"name":"User","description":"User profile and preferences"},{"name":"Projects","description":"Project CRUD, chat messages, and pipeline status"},{"name":"Generated Artifacts","description":"AI-generated outputs: APIs, architecture, SRS, DB schemas, Dockerfiles, source code"},{"name":"Purchases","description":"Bundle purchases (1 credit = KRW 100). Fetch per-artifact costs from GET /api/metadata?key=menu_item_prices."},{"name":"Store","description":"Credit product catalog"},{"name":"Misc","description":"Metadata, quotes, and contact"}]}