{"openapi":"3.1.0","info":{"title":"AdPlug API","description":"MCP Ads Platform for Performance Marketers","version":"0.1.0"},"paths":{"/oauth/google-ads/authorize":{"get":{"tags":["oauth"],"summary":"Authorize","description":"Start the Google Ads OAuth flow. Returns a redirect to Google's consent screen.\n\n`return_to` is an optional same-origin path that the callback will redirect\nto on success/failure instead of the default /dashboard/connections. Used\nby onboarding Step 4 to keep the user inside the 5-step flow.","operationId":"authorize_oauth_google_ads_authorize_get","parameters":[{"name":"user_id","in":"query","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"session_token","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Session Token"}},{"name":"return_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Return To"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/oauth/google-ads/callback":{"get":{"tags":["oauth"],"summary":"Callback","description":"Handle the OAuth callback from Google.\n\nGoogle returns `?error=access_denied&state=...` (no `code`) when the user\nclicks Deny at the consent screen. We mirror the LinkedIn callback shape\n— accept `error` and a no-`code` request, redirect back to the caller's\nreturn_to (onboarding) with the error banner — so the user lands on\nStep 4 with a clear message instead of a 422 on the API host.","operationId":"callback_oauth_google_ads_callback_get","parameters":[{"name":"code","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code"}},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}},{"name":"error","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},{"name":"error_description","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Description"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/oauth/linkedin-ads/authorize":{"get":{"tags":["oauth"],"summary":"Authorize","description":"Start the LinkedIn Ads OAuth flow.\n\n`return_to` is an optional same-origin path that the callback will\nredirect to on success/failure instead of the default\n/dashboard/connections. Used by onboarding Step 4.","operationId":"authorize_oauth_linkedin_ads_authorize_get","parameters":[{"name":"user_id","in":"query","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"session_token","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Session Token"}},{"name":"return_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Return To"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/oauth/linkedin-ads/callback":{"get":{"tags":["oauth"],"summary":"Callback","description":"Handle the OAuth callback from LinkedIn.","operationId":"callback_oauth_linkedin_ads_callback_get","parameters":[{"name":"code","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code"}},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}},{"name":"error","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},{"name":"error_description","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Description"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/oauth/microsoft-ads/authorize":{"get":{"tags":["oauth"],"summary":"Authorize","description":"Start the Microsoft Ads OAuth flow. Returns a redirect to Microsoft's consent screen.\n\n`return_to` is an optional same-origin path that the callback will redirect\nto on success/failure instead of the default /dashboard/connections. Used by\nonboarding Step 4 to keep the user inside the 5-step flow.","operationId":"authorize_oauth_microsoft_ads_authorize_get","parameters":[{"name":"user_id","in":"query","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"session_token","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Session Token"}},{"name":"return_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Return To"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/oauth/microsoft-ads/callback":{"get":{"tags":["oauth"],"summary":"Callback","description":"Handle the OAuth callback from Microsoft.\n\nEntra returns `?error=access_denied&state=...` (no `code`) when the user\ndeclines consent. We recover return_to from state and bounce back with an\nerror banner so the user lands where they started (onboarding or dashboard).","operationId":"callback_oauth_microsoft_ads_callback_get","parameters":[{"name":"code","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code"}},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}},{"name":"error","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},{"name":"error_description","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Description"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/oauth/meta-ads/authorize":{"get":{"tags":["oauth"],"summary":"Authorize","description":"Start the Meta Ads OAuth flow.","operationId":"authorize_oauth_meta_ads_authorize_get","parameters":[{"name":"user_id","in":"query","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"session_token","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Session Token"}},{"name":"return_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Return To"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/oauth/meta-ads/callback":{"get":{"tags":["oauth"],"summary":"Callback","description":"Handle the OAuth callback from Meta.","operationId":"callback_oauth_meta_ads_callback_get","parameters":[{"name":"code","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code"}},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}},{"name":"error","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},{"name":"error_description","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Description"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/meta/data-deletion":{"post":{"tags":["oauth"],"summary":"Data Deletion","description":"Meta data-deletion callback.\n\nFired when a user removes AdPlug from their Facebook \"Apps and Websites\"\nsettings and requests deletion. Drops the Meta connection (and with it the\nencrypted token) for the matching app-scoped user id, then returns the\nstatus URL + confirmation code Meta requires. Configured in the App\nDashboard under Settings → Basic → Data Deletion Callback URL.","operationId":"data_deletion_meta_data_deletion_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_data_deletion_meta_data_deletion_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/oauth/hubspot/authorize":{"get":{"tags":["oauth"],"summary":"Authorize","description":"Start connecting a HubSpot portal, or reconnecting `hub_id`. Redirects to HubSpot's consent screen.","operationId":"authorize_oauth_hubspot_authorize_get","parameters":[{"name":"user_id","in":"query","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"session_token","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Session Token"}},{"name":"return_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Return To"}},{"name":"hub_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Hub Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/oauth/hubspot/callback":{"get":{"tags":["oauth"],"summary":"Callback","description":"Store the portal the user just approved AdPlug in.","operationId":"callback_oauth_hubspot_callback_get","parameters":[{"name":"code","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code"}},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}},{"name":"error","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/oauth/google-search-console/authorize":{"get":{"tags":["oauth"],"summary":"Authorize","description":"Start the Search Console OAuth flow.","operationId":"authorize_oauth_google_search_console_authorize_get","parameters":[{"name":"user_id","in":"query","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"session_token","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Session Token"}},{"name":"return_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Return To"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/oauth/google-search-console/callback":{"get":{"tags":["oauth"],"summary":"Callback","description":"Handle Google's redirect: Deny and errors bounce back with a banner, success stores the grant.","operationId":"callback_oauth_google_search_console_callback_get","parameters":[{"name":"code","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code"}},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}},{"name":"error","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},{"name":"error_description","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Description"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/oauth/reddit-ads/authorize":{"get":{"tags":["oauth"],"summary":"Authorize","description":"Start the Reddit Ads OAuth flow.","operationId":"authorize_oauth_reddit_ads_authorize_get","parameters":[{"name":"user_id","in":"query","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"session_token","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Session Token"}},{"name":"return_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Return To"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/oauth/reddit-ads/callback":{"get":{"tags":["oauth"],"summary":"Callback","description":"Handle the OAuth callback from Reddit.\n\nReddit returns `?error=access_denied&state=...` when the user declines.","operationId":"callback_oauth_reddit_ads_callback_get","parameters":[{"name":"code","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code"}},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}},{"name":"error","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/oauth/x-ads/authorize":{"get":{"tags":["oauth"],"summary":"Authorize","description":"Start the X Ads OAuth 1.0a flow.","operationId":"authorize_oauth_x_ads_authorize_get","parameters":[{"name":"user_id","in":"query","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"session_token","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Session Token"}},{"name":"return_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Return To"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/oauth/x-ads/callback":{"get":{"tags":["oauth"],"summary":"Callback","description":"Handle X's redirect back. X sends ?denied=<request token> when the user cancels.","operationId":"callback_oauth_x_ads_callback_get","parameters":[{"name":"oauth_token","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Oauth Token"}},{"name":"oauth_verifier","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Oauth Verifier"}},{"name":"denied","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Denied"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/webhooks/paddle-sandbox":{"post":{"summary":"Paddle Webhook Sandbox","operationId":"paddle_webhook_sandbox_webhooks_paddle_sandbox_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/webhooks/paddle-live":{"post":{"summary":"Paddle Webhook Live","operationId":"paddle_webhook_live_webhooks_paddle_live_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/authorize":{"get":{"tags":["oauth-legacy-compat"],"summary":"Authorize Passthrough","operationId":"authorize_passthrough_authorize_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/token":{"post":{"tags":["oauth-legacy-compat"],"summary":"Token Passthrough","operationId":"token_passthrough_token_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/register":{"post":{"tags":["oauth-legacy-compat"],"summary":"Register Passthrough","operationId":"register_passthrough_register_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/health":{"get":{"summary":"Health","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/mcp/{mcp_token}/info":{"get":{"summary":"Mcp Info","description":"Get info about an MCP endpoint (for testing).\n\nUses track=False so probing this endpoint doesn't stamp first-auth or\nbookkeep connected clients — otherwise every ping would masquerade as\na real client connection in telemetry.","operationId":"mcp_info_mcp__mcp_token__info_get","parameters":[{"name":"mcp_token","in":"path","required":true,"schema":{"type":"string","title":"Mcp Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/users/{user_id}/profile":{"get":{"summary":"Get User Profile","description":"Get user profile data.\n\nEnriches the raw profile row with `mcp_client_connected_at`: a\n{client_key: iso_timestamp} map of when each AI client first hit /mcp,\nderived from the earliest `usage_logs.client` row per client_key. Clients\nthat connected before the `client` column existed will be missing from\nthis map — the frontend falls back to the global\n`mcp_first_authenticated_at` for the user's first-ever client.\n\nAlso adds `payment_issue` while the user's subscription, or the team\nowner's that gives them their plan, is past due (app/paddle/payment_issue.py).","operationId":"get_user_profile_api_users__user_id__profile_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/users/{user_id}/connections":{"get":{"summary":"Get User Connections","description":"Get user's ad platform connections.","operationId":"get_user_connections_api_users__user_id__connections_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/users/{user_id}/checkout-opened":{"post":{"summary":"Checkout Opened","description":"The browser opened a Paddle checkout for a paid plan.\n\nQueues the founder note Resend sends a few hours later if the user is still\non Free (one per account, deduped on the contact). Free users only.","operationId":"checkout_opened_api_users__user_id__checkout_opened_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Payload"}}}},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/users/{user_id}/usage":{"get":{"summary":"Get User Usage","description":"Get user's usage stats and recent logs.\n\nIncludes the effective monthly request and edit limits — the dashboard\ncan render meters without duplicating tier logic.","operationId":"get_user_usage_api_users__user_id__usage_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/google-ads-quota":{"get":{"summary":"Get Google Ads Quota","description":"Google Ads API usage against Google's daily operations limit, for the admin dashboard.","operationId":"get_google_ads_quota_api_admin_google_ads_quota_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/users/{user_id}/connections/{platform}":{"delete":{"summary":"Delete User Connection","description":"Disconnect an ad platform.","operationId":"delete_user_connection_api_users__user_id__connections__platform__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"platform","in":"path","required":true,"schema":{"type":"string","title":"Platform"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/users/{user_id}/hubspot-portals":{"get":{"summary":"Get Hubspot Portals","description":"The user's connected HubSpot portals and how many their plan covers.","operationId":"get_hubspot_portals_api_users__user_id__hubspot_portals_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/users/{user_id}/hubspot-portals/{hub_id}":{"patch":{"summary":"Rename Hubspot Portal","description":"Give a portal the name the user knows it by (the assistant matches it too). Empty clears it.","operationId":"rename_hubspot_portal_api_users__user_id__hubspot_portals__hub_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"hub_id","in":"path","required":true,"schema":{"type":"integer","title":"Hub Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Payload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Delete Hubspot Portal","description":"Disconnect one HubSpot portal; the others stay connected.","operationId":"delete_hubspot_portal_api_users__user_id__hubspot_portals__hub_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"hub_id","in":"path","required":true,"schema":{"type":"integer","title":"Hub Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/users/{user_id}":{"delete":{"summary":"Delete User Account","description":"Permanently delete a user's account and all associated data.\n\nOrder matters:\n  1. Cancel the account's Paddle subscriptions. Nothing is deleted unless\n     Paddle confirms: without a login the customer could not stop the\n     charges themselves.\n  2. usage_logs → profiles has NO cascade, so wipe logs first or the\n     auth.users delete fails with an FK violation.\n  3. auth.users delete cascades to profiles → connections (both have\n     ON DELETE CASCADE).\n  4. Ask Paddle again. A checkout paid for during steps 1-3 was linked to\n     the account after step 1 looked. Anything paid for after this is\n     canceled by the webhook, which finds no account.\n\nIf step 3 fails after step 2 succeeded, the account is left with no\nusage history but otherwise intact. We log the partial state loudly so\nops can reconcile, and return 500 so the client surfaces the error\ninstead of signing the user out.","operationId":"delete_user_account_api_users__user_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/users/{user_id}/rotate-mcp-token":{"post":{"summary":"Rotate Mcp Token","description":"Regenerate the user's MCP URL-token.\n\nEvery MCP connection using the prior token URL stops working immediately.\nThe client is expected to refresh the user profile after a 200 response\nand prompt the user to update their installed tools.\n\nNote on the cooldown: enforced in-process via `_rotate_last_at`, so it's\nper-Cloud-Run-instance rather than global. An authenticated user with\nN instances in rotation could in theory burst through — tracked as a\nfollow-up (move to a DB-backed check with `profiles.mcp_token_rotated_at`).","operationId":"rotate_mcp_token_api_users__user_id__rotate_mcp_token_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/users/{user_id}/paddle-sync-subscription":{"post":{"summary":"Sync Paddle Subscription","description":"Browser-triggered subscription sync.\n\nThe dashboard calls this from Paddle.js's `checkout.completed` callback so\nthe user sees their new tier instantly — no need to wait for the webhook\nto round-trip through Paddle's servers. We re-fetch the canonical\nsubscription from Paddle's API (don't trust the browser payload alone),\nthen route through the same handler the webhook uses. Idempotency at the\nhandler layer means a later webhook for the same subscription is a no-op.\n\nAccepts `{ \"subscription_id\": \"sub_...\" }` or `{ \"transaction_id\": \"txn_...\" }`.","operationId":"sync_paddle_subscription_api_users__user_id__paddle_sync_subscription_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Payload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/users/{user_id}/paddle-subscription":{"get":{"summary":"Get Paddle Subscription","description":"The live state of the user's subscription: trial end, next charge, scheduled cancellation.","operationId":"get_paddle_subscription_api_users__user_id__paddle_subscription_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/users/{user_id}/paddle-portal-session":{"post":{"summary":"Create Paddle Portal Session","description":"Create a Paddle customer-portal session and return the overview URL.\n\nThe dashboard's \"Manage billing\" button calls this and then redirects the\nbrowser to the returned URL. The session token is short-lived; the\nresponse must not be cached.","operationId":"create_paddle_portal_session_api_users__user_id__paddle_portal_session_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/users/{user_id}/paddle-change-plan":{"post":{"summary":"Paddle Change Plan","description":"Change the user's existing Paddle subscription in place (prorated).\n\nUsers with a live subscription must never go through checkout again —\nPaddle happily creates a parallel second subscription (2026-07-13\ndouble-billing incident). This endpoint swaps the subscription's price\ninstead, keeping the same subscription id so the webhook ownership guards\nstay coherent. Upgrades charge the prorated difference today; downgrades\nturn the unused higher-plan time into customer balance credit that covers\nfuture renewals.","operationId":"paddle_change_plan_api_users__user_id__paddle_change_plan_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Payload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/users/{user_id}/paddle-cancel-current-sub":{"post":{"summary":"Paddle Cancel Current Sub","description":"Cancel the user's active Paddle subscription immediately.\n\nSandbox-only: gated on the API key prefix. Returns 403 on live accounts.\nBuilt for Playwright lifecycle tests to clean up the `sub_…` they create —\nwithout it, every successful test run leaves a lingering active sub that\nbreaks subsequent runs (Paddle's Checkout overlay routes \"already-subscribed\"\ncustomers through a different flow).\n\nProduction users cancel via the Paddle customer portal — this endpoint is\nNOT a public cancel UX; the response intentionally exposes the underlying\nPaddle state so test failures are diagnosable from the spec output.","operationId":"paddle_cancel_current_sub_api_users__user_id__paddle_cancel_current_sub_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"Body_data_deletion_meta_data_deletion_post":{"properties":{"signed_request":{"type":"string","title":"Signed Request"}},"type":"object","required":["signed_request"],"title":"Body_data_deletion_meta_data_deletion_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"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}},"securitySchemes":{"HTTPBearer":{"type":"http","scheme":"bearer"}}}}