{"openapi":"3.1.0","info":{"title":"KickThemBot API","version":"1.0.0","summary":"Paid Telegram access: set up and run a KickThemBot account from an AI agent.","description":"Two credentials exist:\n\n- **Owner token** (`Authorization: Bearer ktb_owner_...`): issued to an AI agent after the account owner approves a pairing in @KickThemBot. Scoped, revocable in @KickThemBot with /agents.\n  Default scopes: billing:link, projects:write, plans:write, codes:write. members:write, api_keys:write, payments:write are granted only when the pairing request lists them, and the owner sees them under \"Sensitive\".\n- **Project API key** (`x-api-key: mib_...`): one project, for website/CRM integrations.\n\nSetup flow for agents: pair → GET /api/me → POST /api/billing/checkout-link (optional) → POST /api/projects → owner pastes the BotFather token in @KickThemBot → POST chat-link (owner adds the bot, then taps Attach in @KickThemBot) → POST plans → Stripe (the owner connects it in @KickThemBot), USDT (PUT payment-methods/crypto, then the owner taps Confirm in @KickThemBot) or access codes → GET setup-status → GET payment-links for the client's website.\n\nHuman steps are returned as `humanStep` objects with a link. The client never pastes a secret into the agent chat.\n\nTokens can never connect, change or disconnect Stripe accounts (the owner connects Stripe in the bot; no OAuth URL is ever returned), save or replace a USDT wallet address without the owner tapping Confirm in the bot, delete projects, rotate a project API key, manage the team, or change billing beyond creating a checkout link.\n\nErrors are JSON `{ \"error\": string, \"code\": string }` on agent endpoints. Project API endpoints return `{ \"error\": string }`."},"servers":[{"url":"https://api.kickthembot.com"}],"tags":[{"name":"Pairing","description":"Get an owner token through a one-tap Telegram approval."},{"name":"Account","description":"Owner, plan, usage, and billing."},{"name":"Setup","description":"Create and configure Bouncers (projects)."},{"name":"Project API","description":"Plans, access codes, and memberships for one project."}],"x-human-steps":{"approvePairing":"To let me set up KickThemBot for you, please approve access in Telegram:\n\n1. Open this link: <deepLink>\n2. @KickThemBot shows \"<agentName>\" and the code <code>. Check the code matches this message.\n3. Tap Approve.\n\nThe link works for 10 minutes. You can remove my access any time by sending /agents to @KickThemBot.","verifyEmail":"KickThemBot needs a verified email address (Stripe sends receipts there):\n\n1. Open: <verifyEmailLink>\n2. Send your email address to @KickThemBot.\n3. Enter the 6-digit code from the email.\n\nTell me when it is done.","createBotInBotFather":"Your membership bot needs its own Telegram bot. This takes about a minute:\n\n1. Open @BotFather: https://t.me/BotFather\n2. Send /newbot\n3. Send a display name, for example \"My VIP Access\".\n4. Send a username that ends in \"bot\", for example MyVipAccessBot.\n5. BotFather replies with a token that looks like 123456789:AA... Do NOT send that token to me.\n6. Open this link: <botLinkDeepLink>\n7. Paste the token into @KickThemBot. It checks the token and confirms.\n\nIf @KickThemBot says the token is invalid, copy it again from BotFather (the whole line, no spaces). If it says the bot is already used, create a new bot with /newbot: every Bouncer needs its own bot.","payPlan":"Here is your secure Stripe payment page for KickThemBot <plan> (<interval>):\n\n<checkoutUrl>\n\nIt shows the plan price and a card form from Stripe. The page stays open for about 30 minutes. After paying you can close it; your plan activates automatically within a minute.\n\nIf the payment fails, try another card or open the link again. If the link expired, tell me and I will create a new one.","addBotAsChatAdmin":"Now add @KickThemBot to your paid channel as an administrator:\n\n1. Open: <deepLink>\n2. Pick your channel.\n3. Keep \"Invite users\" and \"Ban users\" switched on and confirm.\n4. @KickThemBot then asks you in a private message to attach the channel to your Bouncer. Tap Attach.\n\nUse the same Telegram account you approved me with. The link works for 30 minutes.","connectStripe":"To take card payments inside Telegram, connect your Stripe account in @KickThemBot:\n\n1. Open: <ownerLink>\n2. @KickThemBot shows a \"Connect Stripe LIVE\" button. Tap it (it only works on the Telegram account that owns the Bouncer).\n3. Log in to Stripe, or create an account (Stripe asks for business and bank details so it can pay you out).\n4. Approve the connection to KickThemBot. You land on a page saying \"Stripe LIVE connected\", and @KickThemBot confirms in Telegram.\n\nThe Stripe button works once, for 15 minutes; open my link again for a fresh one. If the page says \"Almost there\", Stripe still needs your activation details: finish them in the Stripe dashboard (https://dashboard.stripe.com/), then open @KickThemBot, choose your Bouncer, tap Stripe and connect again.\n\nPrefer not to use Stripe? Send me a USDT wallet address and its network (TRC20, ERC20, BEP20, Polygon, Solana or TON) instead.","askForUsdtAddress":"Which USDT wallet should buyers pay into? Please send:\n\n1. The wallet address.\n2. The network: TRC20, ERC20, BEP20, Polygon, Solana or TON.\n\nDouble-check the network: USDT sent on the wrong network can be lost.","confirmWalletChange":"I sent your USDT wallet to KickThemBot. To protect your money, nothing changes until you confirm it yourself:\n\n1. Open @KickThemBot and find the message \"Confirm USDT wallet change\" (or open: <link>).\n2. Check that every address matches your own wallet app exactly, character by character, and that the network is right.\n3. Tap Confirm on the wallet message. If anything looks wrong, tap Reject.\n\nThe request expires in 30 minutes. Tell me when you have tapped Confirm."},"x-scopes":{"billing:link":"Create a KickThemBot plan checkout link for you to pay","projects:write":"Create Bouncers and chat links, and send you a link to connect Stripe yourself in this bot","plans:write":"Create membership plans","codes:write":"Create and revoke access codes","members:write":"Read and update members","api_keys:write":"Create project API keys for integrations","payments:write":"Propose USDT wallets, a buyer note or turning USDT payments on (you confirm each here), and turn USDT payments off"},"components":{"securitySchemes":{"ownerToken":{"type":"http","scheme":"bearer","bearerFormat":"ktb_owner_<43 url-safe characters>","description":"Owner token from the pairing flow. Scopes: billing:link, projects:write, plans:write, codes:write, members:write, api_keys:write, payments:write (default billing:link, projects:write, plans:write, codes:write). Limits: 120 requests/minute per token, 300/minute per client IP (IPv6 per /64), setup-status 10/minute per token."},"projectApiKey":{"type":"apiKey","in":"header","name":"x-api-key","description":"Project API key (mib_<projectId>_...). Also accepted as Authorization: Bearer."},"pairingSecret":{"type":"apiKey","in":"header","name":"X-Pairing-Secret","description":"The pairingSecret returned by POST /api/agent/pairings. Required to poll."}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string","description":"Stable machine-readable reason"},"humanStep":{"$ref":"#/components/schemas/HumanStep"}}},"HumanStep":{"type":"object","description":"Something only the account owner can do. Send them the link.","properties":{"description":{"type":"string","description":"What happens, for the agent"},"link":{"type":"string","format":"uri"},"userMessage":{"type":"string","description":"Ready-to-send message for the agent to paste to its user"}}},"NextStep":{"type":"object","properties":{"id":{"type":"string"},"humanRequired":{"type":"boolean"},"optional":{"type":"boolean"},"text":{"type":"string"},"link":{"type":"string","format":"uri"},"endpoint":{"type":"string"},"cryptoEndpoint":{"type":"string"},"requestId":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"},"userMessage":{"type":"string"}}},"WalletChangeRequest":{"type":"object","description":"A USDT wallet change proposed by an agent. Nothing is saved until the primary owner taps Confirm in the bot.","properties":{"requestId":{"type":"string"},"projectId":{"type":"string"},"status":{"type":"string","enum":["pending_owner_confirmation","confirmed","rejected","expired","superseded","stale","failed"]},"expiresAt":{"type":"string","format":"date-time"},"createdAt":{"type":["string","null"],"format":"date-time"},"decidedAt":{"type":["string","null"],"format":"date-time"},"applied":{"type":"boolean","description":"true once a confirmed change has been saved"},"ownerNotified":{"type":"boolean","description":"false if the bot could not message the owner; the link re-opens the message"},"wallets":{"type":"array","items":{"type":"object","properties":{"network":{"type":"string"},"networkLabel":{"type":"string"},"address":{"type":"string"},"label":{"type":"string"},"replacesAddress":{"type":["string","null"]},"checksummed":{"type":"boolean"}}}},"instructions":{"type":"string"},"enabled":{"type":"boolean"},"failureCode":{"type":"string"}}},"Pairing":{"type":"object","properties":{"pairingId":{"type":"string"},"status":{"type":"string","enum":["pending","approved","denied","expired"]},"agentName":{"type":"string"},"scopes":{"type":"array","items":{"type":"string","enum":["billing:link","projects:write","plans:write","codes:write","members:write","api_keys:write","payments:write"]}},"expiresAt":{"type":"string","format":"date-time"},"token":{"type":"string","description":"Present exactly once, on the first poll after approval"},"tokenId":{"type":"string"},"tokenType":{"type":"string","const":"Bearer"},"tokenAlreadyIssued":{"type":"boolean"}}},"PlanSummary":{"type":"object","properties":{"code":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"active":{"type":"boolean"},"billingInterval":{"type":"string"},"currentPeriodEnd":{"type":["string","null"],"format":"date-time"},"limits":{"type":"object","additionalProperties":true}}},"AgentProject":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"type":{"type":"string","enum":["channel","group"]},"projectType":{"type":"string","enum":["PAID_CHANNEL","PAID_GROUP"]},"botLinked":{"type":"boolean"},"botUsername":{"type":["string","null"]},"connectedChats":{"type":"integer"},"stripeConnected":{"type":"object","properties":{"live":{"type":"boolean"},"test":{"type":"boolean"}}},"apiKeyConfigured":{"type":"boolean"}}},"AgentPlan":{"type":"object","properties":{"id":{"type":"string"},"projectId":{"type":"string"},"name":{"type":"string"},"priceCents":{"type":"integer"},"currency":{"type":"string"},"billingType":{"type":"string","enum":["subscription","lifetime"]},"durationDays":{"type":"integer"},"description":{"type":"string"},"allowedChatIds":{"type":"array","items":{"type":"integer"}},"isActive":{"type":"boolean"}}},"LimitError":{"allOf":[{"$ref":"#/components/schemas/Error"}],"properties":{"limit":{"type":"object","properties":{"kind":{"type":"string"},"max":{},"current":{"type":"integer"},"planCode":{"type":"string"}}},"nextStep":{"type":"string"}}},"ProjectPlan":{"type":"object","additionalProperties":true},"Membership":{"type":"object","additionalProperties":true}}},"paths":{"/api/agent/pairings":{"post":{"summary":"Start pairing an AI agent with a KickThemBot owner account","description":"Unauthenticated. Limited to 10 per hour per client IP. Returns a Telegram deep link for the owner and a pairingSecret the agent keeps to poll. Expires after 10 minutes. The owner sees agentName labelled \"not verified\". Without scopes the token gets billing:link, projects:write, plans:write, codes:write; members:write and api_keys:write and payments:write must be listed explicitly and are shown to the owner with a warning. An owner can have at most 20 active agent tokens.","tags":["Pairing"],"security":[],"x-human-step":"The owner opens deepLink in Telegram; @KickThemBot shows the agent name, code, and scopes with Approve / Deny.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"agentName":{"type":"string","maxLength":40,"description":"Shown to the owner as \"Agent name (not verified)\". ASCII letters, digits, spaces and - _ . ( ) ' , only; whitespace is collapsed and it is cut to 40 characters. Names with links, @usernames or domains, or resembling KickThemBot/KTB, official, support, security, verified, Telegram, admin, or Stripe are refused (invalid_agent_name)."},"scopes":{"type":"array","items":{"type":"string","enum":["billing:link","projects:write","plans:write","codes:write","members:write","api_keys:write","payments:write"]},"description":"Defaults to billing:link, projects:write, plans:write, codes:write. Request members:write or api_keys:write or payments:write only if needed."}}}}}},"responses":{"201":{"description":"Pairing created","content":{"application/json":{"schema":{"type":"object","properties":{"pairingId":{"type":"string"},"code":{"type":"string"},"pairingSecret":{"type":"string","description":"Keep private; send as X-Pairing-Secret when polling"},"deepLink":{"type":"string","format":"uri"},"expiresAt":{"type":"string","format":"date-time"},"scopes":{"type":"array","items":{"type":"string"}},"pollUrl":{"type":"string"},"pollIntervalSeconds":{"type":"integer"},"humanStep":{"$ref":"#/components/schemas/HumanStep"}}}}}},"400":{"description":"Invalid agentName or scopes","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (code: rate_limited)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/agent/pairings/{pairingId}":{"get":{"summary":"Poll a pairing","description":"Returns pending, denied, expired, or approved. The owner token is included exactly once, on the first poll after approval; store it immediately. A wrong secret returns 404.","tags":["Pairing"],"security":[{"pairingSecret":[]}],"parameters":[{"name":"pairingId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Pairing state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Pairing"}}}},"401":{"description":"X-Pairing-Secret header missing (code: pairing_secret_required)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown pairing or wrong secret (code: pairing_not_found)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/me":{"get":{"summary":"Owner, plan, limits, usage, and projects","description":"Start here after pairing. nextSteps lists account-level human steps such as email verification.","tags":["Account"],"security":[{"ownerToken":[]}],"parameters":[],"responses":{"200":{"description":"Account summary","content":{"application/json":{"schema":{"type":"object","properties":{"owner":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"username":{"type":["string","null"]},"emailVerified":{"type":"boolean"}}},"plan":{"$ref":"#/components/schemas/PlanSummary"},"usage":{"type":"object","additionalProperties":true},"projects":{"type":"array","items":{"$ref":"#/components/schemas/AgentProject"}},"token":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}},"last4":{"type":"string"}}},"nextSteps":{"type":"array","items":{"$ref":"#/components/schemas/NextStep"}}}}}}},"401":{"description":"Missing, invalid, or revoked credential (code: unauthorized | invalid_token)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (code: rate_limited). Honour Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected error (code: internal_error)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/billing/status":{"get":{"summary":"KickThemBot plan and billing status","description":"Current plan, whether a subscription or checkout exists, and the paid plans with prices. Read-only.","tags":["Account"],"security":[{"ownerToken":[]}],"parameters":[],"responses":{"200":{"description":"Billing status","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"description":"Missing, invalid, or revoked credential (code: unauthorized | invalid_token)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (code: rate_limited). Honour Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected error (code: internal_error)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/billing/checkout-link":{"post":{"summary":"Create a Stripe Checkout link for a KickThemBot plan","description":"The only billing action a token can take. Refused when the owner already has an active subscription (change plans in the bot), a checkout is already open, or the email is not verified.","tags":["Account"],"security":[{"ownerToken":["billing:link"]}],"x-required-scope":"billing:link","x-human-step":"The owner opens checkoutUrl and pays. The plan activates from the Stripe webhook.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, 8-200 characters. A retried request with the same key and body, from the same token, replays the original 2xx response (Idempotency-Replayed: true) instead of creating a duplicate. Keys are per token and remembered for 7 days.","schema":{"type":"string","minLength":8,"maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["plan"],"properties":{"plan":{"type":"string","enum":["bouncer","boss","empire"]},"interval":{"type":"string","enum":["monthly","yearly"],"default":"monthly"}}}}}},"responses":{"201":{"description":"Checkout link (valid about 30 minutes)","content":{"application/json":{"schema":{"type":"object","properties":{"checkoutUrl":{"type":"string","format":"uri"},"planCode":{"type":"string"},"interval":{"type":"string"},"expiresAt":{"type":["string","null"],"format":"date-time"},"humanStep":{"$ref":"#/components/schemas/HumanStep"}}}}}},"400":{"description":"invalid_plan | invalid_interval","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or revoked credential (code: unauthorized | invalid_token)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"email_verification_required | already_on_plan | already_subscribed | checkout_pending | billing_not_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (code: rate_limited). Honour Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected error (code: internal_error)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/projects":{"post":{"summary":"Create a Bouncer (project)","description":"Creates a project within the plan project limit. Without botToken the response carries botLinkDeepLink: the owner creates a bot with @BotFather and pastes its token into @KickThemBot, never into the agent chat. botToken in the body is accepted (validated with Telegram getMe) for agents that already hold one.","tags":["Setup"],"security":[{"ownerToken":["projects:write"]}],"x-required-scope":"projects:write","x-human-step":"Open botLinkDeepLink and paste the BotFather token.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, 8-200 characters. A retried request with the same key and body, from the same token, replays the original 2xx response (Idempotency-Replayed: true) instead of creating a duplicate. Keys are per token and remembered for 7 days.","schema":{"type":"string","minLength":8,"maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":60,"description":"Shown to the owner in bot messages. Invisible characters are removed and whitespace collapsed; names with links, @usernames or domains, or resembling KickThemBot/KTB, official, support, security, verified, Telegram, admin, or Stripe are refused (invalid_name)."},"type":{"type":"string","enum":["channel","group"],"default":"channel"},"botToken":{"type":"string","description":"Optional BotFather token"}}}}}},"responses":{"201":{"description":"Project created","content":{"application/json":{"schema":{"type":"object","properties":{"project":{"$ref":"#/components/schemas/AgentProject"},"botLinkDeepLink":{"type":["string","null"],"format":"uri"},"humanStep":{"oneOf":[{"$ref":"#/components/schemas/HumanStep"},{"type":"null"}]}}}}}},"400":{"description":"invalid_name | invalid_type | invalid_bot_token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or revoked credential (code: unauthorized | invalid_token)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"plan_limit_reached | insufficient_scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LimitError"}}}},"409":{"description":"email_verification_required | project_name_taken | bot_token_in_use","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (code: rate_limited). Honour Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected error (code: internal_error)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/projects/{projectId}/setup-status":{"get":{"summary":"Setup checklist for a project","description":"Bot linked, connected chats with admin-rights checks (Telegram answers cached 60 seconds per chat), Stripe connection (live/test), plans, and the remaining nextSteps. Limited to 10 requests per minute per token; poll every 10 seconds or slower.","tags":["Setup"],"security":[{"ownerToken":[]}],"parameters":[{"name":"projectId","in":"path","required":true,"description":"Project (Bouncer) ID","schema":{"type":"string","pattern":"^[a-f0-9]{24}$"}}],"responses":{"200":{"description":"Checklist","content":{"application/json":{"schema":{"type":"object","properties":{"projectId":{"type":"string"},"ready":{"type":"boolean"},"botLinked":{"type":"boolean"},"botUsername":{"type":["string","null"]},"chats":{"type":"array","items":{"type":"object","properties":{"chatId":{"type":"integer"},"title":{"type":"string"},"type":{"type":"string"},"adminRightsOk":{"type":["boolean","null"]}}}},"stripeConnected":{"type":"object","properties":{"live":{"type":"boolean"},"test":{"type":"boolean"}}},"paymentMethod":{"type":"object","properties":{"configured":{"type":"boolean"},"stripe":{"type":"object","additionalProperties":true},"crypto":{"type":"object","properties":{"configured":{"type":"boolean"}}},"accessCodes":{"type":"object","properties":{"available":{"type":"boolean"}}}}},"plansCount":{"type":"integer"},"activePlansCount":{"type":"integer"},"paymentLinks":{"type":["object","null"],"description":"Once the bot is linked and a payment method is on: where to get buyer links for the client's website.","properties":{"endpoint":{"type":"string"},"allPlansLink":{"type":["string","null"],"format":"uri"}}},"nextSteps":{"type":"array","items":{"$ref":"#/components/schemas/NextStep"},"description":"Ids: verify_email, link_bot_token, connect_chat, fix_admin_rights, create_plan, finish_stripe_activation, confirm_wallet_change (a proposed USDT wallet waits for the owner's Confirm tap), choose_payment_method."}}}}}},"401":{"description":"Missing, invalid, or revoked credential (code: unauthorized | invalid_token)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"project_not_found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (code: rate_limited). Honour Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected error (code: internal_error)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/projects/{projectId}/chat-link":{"post":{"summary":"One-tap links to add the bot to a channel or group as admin","description":"Returns t.me links (startchannel / startgroup with admin=invite_users+restrict_members) that add @KickThemBot as administrator. When the owner newly adds the bot from the Telegram account that owns the project (within 30 minutes), the bot asks that owner in private chat to confirm \"Attach <chat> to <project>?\"; the chat attaches only after they tap Attach. Chats already connected to any project, and admin-rights edits in chats where the bot was already an administrator, are never attached. The group link carries a random startgroup value per request.","tags":["Setup"],"security":[{"ownerToken":["projects:write"]}],"x-required-scope":"projects:write","x-human-step":"The owner opens the link, picks the chat, confirms the admin rights, then taps Attach in the bot.","parameters":[{"name":"projectId","in":"path","required":true,"description":"Project (Bouncer) ID","schema":{"type":"string","pattern":"^[a-f0-9]{24}$"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["channel","group","both"]}}}}}},"responses":{"201":{"description":"Links","content":{"application/json":{"schema":{"type":"object","properties":{"projectId":{"type":"string"},"links":{"type":"object","additionalProperties":{"type":"object","properties":{"deepLink":{"type":"string","format":"uri"},"expiresAt":{"type":"string","format":"date-time"}}}},"humanStep":{"$ref":"#/components/schemas/HumanStep"}}}}}},"400":{"description":"invalid_type","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or revoked credential (code: unauthorized | invalid_token)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"plan_limit_reached | insufficient_scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LimitError"}}}},"404":{"description":"project_not_found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (code: rate_limited). Honour Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected error (code: internal_error)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/projects/{projectId}/plans":{"get":{"summary":"List membership plans","description":"Active plans by default.\n\nAuthenticate with the project API key (x-api-key or Bearer), or with an owner token (any scope) for a project the owner owns.","tags":["Project API"],"security":[{"projectApiKey":[]},{"ownerToken":[]}],"parameters":[{"name":"projectId","in":"path","required":true,"description":"Project (Bouncer) ID","schema":{"type":"string","pattern":"^[a-f0-9]{24}$"}},{"name":"includeInactive","in":"query","schema":{"type":"boolean"}},{"name":"billingType","in":"query","schema":{"type":"string","enum":["subscription","lifetime"]}}],"responses":{"200":{"description":"Plans","content":{"application/json":{"schema":{"type":"object","properties":{"projectId":{"type":"string"},"plans":{"type":"array","items":{"$ref":"#/components/schemas/ProjectPlan"}}}}}}},"401":{"description":"Missing or invalid credential","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Plan does not include API access, the owner token lacks the scope, or the route is not open to owner tokens (route_not_available_to_agents)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Project not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create a membership plan","description":"Within the plan limit. Subscriptions renew every durationDays; lifetime is a one-time payment. Stripe products and prices are created on the first Stripe checkout for the connected account. Plans also work without Stripe, through access codes.","tags":["Setup"],"security":[{"ownerToken":["plans:write"]}],"x-required-scope":"plans:write","parameters":[{"name":"projectId","in":"path","required":true,"description":"Project (Bouncer) ID","schema":{"type":"string","pattern":"^[a-f0-9]{24}$"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, 8-200 characters. A retried request with the same key and body, from the same token, replays the original 2xx response (Idempotency-Replayed: true) instead of creating a duplicate. Keys are per token and remembered for 7 days.","schema":{"type":"string","minLength":8,"maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","priceCents"],"properties":{"name":{"type":"string","maxLength":100},"priceCents":{"type":"integer","minimum":0},"currency":{"type":"string","default":"usd","pattern":"^[a-z]{3}$"},"durationDays":{"type":"integer","minimum":1,"maximum":36500,"description":"Required for subscriptions"},"billingType":{"type":"string","enum":["subscription","lifetime"],"default":"subscription"},"description":{"type":"string","maxLength":1000},"allowedChatIds":{"type":"array","items":{"type":"integer"},"description":"Omit for all connected chats"}}}}}},"responses":{"201":{"description":"Plan created","content":{"application/json":{"schema":{"type":"object","properties":{"plan":{"$ref":"#/components/schemas/AgentPlan"}}}}}},"400":{"description":"invalid_name | invalid_price | invalid_currency | invalid_billing_type | invalid_duration | invalid_chats","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or revoked credential (code: unauthorized | invalid_token)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"plan_limit_reached | insufficient_scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LimitError"}}}},"404":{"description":"project_not_found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"plan_name_taken","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (code: rate_limited). Honour Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected error (code: internal_error)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/projects/{projectId}/stripe/connect-link":{"post":{"summary":"Link for the owner to connect Stripe in the bot","description":"Never returns a Stripe OAuth URL. Returns ownerLink (t.me/<bot>?start=stripe_<projectId>, or stripe_test_<projectId>). @KickThemBot checks that the Telegram user is the project's primary owner and only then shows the Stripe Connect button; the owner is notified in Telegram when an account is connected. Only while the mode is not yet connected. A token can never replace or disconnect a connected Stripe account.","tags":["Setup"],"security":[{"ownerToken":["projects:write"]}],"x-required-scope":"projects:write","x-human-step":"The owner opens ownerLink, taps Connect Stripe in @KickThemBot, signs in to Stripe, and approves.","parameters":[{"name":"projectId","in":"path","required":true,"description":"Project (Bouncer) ID","schema":{"type":"string","pattern":"^[a-f0-9]{24}$"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"mode":{"type":"string","enum":["live","test"],"default":"live"}}}}}},"responses":{"201":{"description":"Bot link for the owner","content":{"application/json":{"schema":{"type":"object","properties":{"projectId":{"type":"string"},"mode":{"type":"string"},"ownerLink":{"type":"string","format":"uri"},"humanStep":{"$ref":"#/components/schemas/HumanStep"}}}}}},"400":{"description":"invalid_mode","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or revoked credential (code: unauthorized | invalid_token)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"project_not_found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"stripe_already_connected | stripe_activation_pending","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (code: rate_limited). Honour Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected error (code: internal_error)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"stripe_not_configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/projects/{projectId}/api-keys":{"post":{"summary":"Mint a project API key","description":"For project-level integrations (website, CRM). Needs the api_keys:write scope, which is never granted by default. Returned once and not idempotent (a retry gets 409 api_key_exists; Idempotency-Key is ignored). The key is revoked together with the token that minted it. Rotation is refused for tokens (403 rotation_requires_owner): the owner rotates keys in the bot.","tags":["Setup"],"security":[{"ownerToken":["api_keys:write"]}],"x-required-scope":"api_keys:write","parameters":[{"name":"projectId","in":"path","required":true,"description":"Project (Bouncer) ID","schema":{"type":"string","pattern":"^[a-f0-9]{24}$"}}],"responses":{"201":{"description":"New key","content":{"application/json":{"schema":{"type":"object","properties":{"projectId":{"type":"string"},"apiKey":{"type":"string"},"last4":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}}}}},"401":{"description":"Missing, invalid, or revoked credential (code: unauthorized | invalid_token)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"plan_upgrade_required | insufficient_scope | rotation_requires_owner","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"project_not_found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"api_key_exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (code: rate_limited). Honour Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected error (code: internal_error)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/projects/{projectId}/access-codes":{"post":{"summary":"Create access codes","description":"Creates 1-100 single- or multi-use codes for a plan, bound to a website buyer. Requires an Idempotency-Key (or X-Correlation-ID) of 8-200 characters; a retry replays the original codes. Counts toward the monthly access-code limit.\n\nAuthenticate with the project API key (x-api-key or Bearer), or with an owner token that has the codes:write scope for a project the owner owns.","tags":["Project API"],"security":[{"projectApiKey":[]},{"ownerToken":["codes:write"]}],"x-required-scope":"codes:write","parameters":[{"name":"projectId","in":"path","required":true,"description":"Project (Bouncer) ID","schema":{"type":"string","pattern":"^[a-f0-9]{24}$"}},{"name":"Idempotency-Key","in":"header","required":true,"description":"Optional, 8-200 characters. A retried request with the same key and body, from the same token, replays the original 2xx response (Idempotency-Replayed: true) instead of creating a duplicate. Keys are per token and remembered for 7 days.","schema":{"type":"string","minLength":8,"maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","planId","webUserId","firstName","lastName"],"properties":{"email":{"type":"string","format":"email"},"planId":{"type":"string"},"webUserId":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"expiresInDays":{"type":"number","default":7},"maxRedemptions":{"type":"integer","default":1},"codeCount":{"type":"integer","minimum":1,"maximum":100,"default":1},"note":{"type":"string"}}}}}},"responses":{"201":{"description":"Codes created (or replayed)","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid credential","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Plan does not include API access, the owner token lacks the scope, or the route is not open to owner tokens (route_not_available_to_agents)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Project not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Idempotency conflict or still processing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"423":{"description":"Access-code writes are locked for maintenance","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/projects/{projectId}/access-codes/{accessCodeId}/revoke":{"patch":{"summary":"Revoke an unredeemed access code","description":"Idempotent. A redeemed code returns 409.\n\nAuthenticate with the project API key (x-api-key or Bearer), or with an owner token that has the codes:write scope for a project the owner owns.","tags":["Project API"],"security":[{"projectApiKey":[]},{"ownerToken":["codes:write"]}],"x-required-scope":"codes:write","parameters":[{"name":"projectId","in":"path","required":true,"description":"Project (Bouncer) ID","schema":{"type":"string","pattern":"^[a-f0-9]{24}$"}},{"name":"accessCodeId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Revoked","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Invalid accessCodeId","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid credential","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Plan does not include API access, the owner token lacks the scope, or the route is not open to owner tokens (route_not_available_to_agents)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Project not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Already redeemed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/projects/{projectId}/memberships":{"get":{"summary":"List memberships","description":"Filters: membershipId, stripeSubscriptionId, accessCodeId, status, state (active|inactive), planId, source, telegramUserId, webUserId, email; page/limit (max 100).\n\nAuthenticate with the project API key (x-api-key or Bearer), or with an owner token that has the members:write scope for a project the owner owns.","tags":["Project API"],"security":[{"projectApiKey":[]},{"ownerToken":["members:write"]}],"x-required-scope":"members:write","parameters":[{"name":"projectId","in":"path","required":true,"description":"Project (Bouncer) ID","schema":{"type":"string","pattern":"^[a-f0-9]{24}$"}},{"name":"membershipId","in":"query","required":false,"schema":{"type":"string"}},{"name":"stripeSubscriptionId","in":"query","required":false,"schema":{"type":"string"}},{"name":"accessCodeId","in":"query","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","required":false,"schema":{"type":"string"}},{"name":"state","in":"query","required":false,"schema":{"type":"string"}},{"name":"planId","in":"query","required":false,"schema":{"type":"string"}},{"name":"source","in":"query","required":false,"schema":{"type":"string"}},{"name":"telegramUserId","in":"query","required":false,"schema":{"type":"string"}},{"name":"webUserId","in":"query","required":false,"schema":{"type":"string"}},{"name":"email","in":"query","required":false,"schema":{"type":"string"}},{"name":"page","in":"query","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Page of memberships","content":{"application/json":{"schema":{"type":"object","properties":{"projectId":{"type":"string"},"page":{"type":"integer"},"limit":{"type":"integer"},"total":{"type":"integer"},"memberships":{"type":"array","items":{"$ref":"#/components/schemas/Membership"}}}}}}},"400":{"description":"Invalid filter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid credential","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Plan does not include API access, the owner token lacks the scope, or the route is not open to owner tokens (route_not_available_to_agents)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Project not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update a membership found by telegramUserId, webUserId, email, or membershipId","description":"Extending access (extendDays/usePlanDuration) requires websitePaymentId, which makes retries safe. Writable statuses: active, past_due, canceled, unpaid. To end access set endAt in the past.\n\nAuthenticate with the project API key (x-api-key or Bearer), or with an owner token that has the members:write scope for a project the owner owns.","tags":["Project API"],"security":[{"projectApiKey":[]},{"ownerToken":["members:write"]}],"x-required-scope":"members:write","parameters":[{"name":"projectId","in":"path","required":true,"description":"Project (Bouncer) ID","schema":{"type":"string","pattern":"^[a-f0-9]{24}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"Updated membership","content":{"application/json":{"schema":{"type":"object","properties":{"membership":{"$ref":"#/components/schemas/Membership"}}}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid credential","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Plan does not include API access, the owner token lacks the scope, or the route is not open to owner tokens (route_not_available_to_agents)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Project not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/projects/{projectId}/payment-methods/crypto":{"put":{"summary":"Propose USDT wallets (the owner confirms in the bot)","description":"Needs the payments:write scope (sensitive, never granted by default). A token never saves or replaces a wallet address itself: any new or changed address or label, turning USDT payments on, and any buyer instructions text become one pending request (202). @KickThemBot sends the primary owner a private message with the Bouncer name, every network and full address, \"Proposed by agent (not verified)\" and Confirm / Reject. Only the Telegram account that owns the Bouncer can confirm; helpers cannot. Confirming re-validates every address and saves it, and the owner gets the usual wallet-change alert. Requests expire after 30 minutes; a new proposal supersedes the pending one (one per project). Wallets not listed are kept; removing a wallet is only possible in the bot. Only two changes apply at once (200): enabled=false and instructions set to \"\". Instructions with invisible or control characters are refused. The request records the settings version; if the owner (or anyone) edits USDT settings before confirming, the request becomes stale (or superseded) and nothing is applied. Confirming applies all wallets, instructions and on/off in one guarded write. Addresses are validated per network (Tron base58check, EIP-55 for mixed-case EVM addresses, Solana, TON CRC); an all-lower-case EVM address is accepted and flagged to the owner as having no checksum. With no enabled field, a first wallet turns USDT payments on when confirmed. Proposals (202) are limited to 5 per hour per token.","tags":["Setup"],"security":[{"ownerToken":["payments:write"]}],"x-required-scope":"payments:write","x-human-step":"The owner opens @KickThemBot, checks every address against their own wallet app, and taps Confirm on the wallet message.","parameters":[{"name":"projectId","in":"path","required":true,"description":"Project (Bouncer) ID","schema":{"type":"string","pattern":"^[a-f0-9]{24}$"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, 8-200 characters. A retried request with the same key and body, from the same token, replays the original 2xx response (Idempotency-Replayed: true) instead of creating a duplicate. Keys are per token and remembered for 7 days.","schema":{"type":"string","minLength":8,"maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"wallets":{"type":"array","minItems":1,"maxItems":6,"items":{"type":"object","required":["network","address"],"properties":{"network":{"type":"string","enum":["TRC20","ERC20","BEP20","POLYGON","SOLANA","TON"]},"address":{"type":"string"},"label":{"type":"string","maxLength":40,"description":"Shown to buyers. No addresses or links."}}}},"instructions":{"type":"string","maxLength":500,"description":"Buyer note shown with the address"},"enabled":{"type":"boolean","description":"Turn USDT payments on or off"}}}}}},"responses":{"200":{"description":"Applied at once (nothing that could redirect money), or unchanged","content":{"application/json":{"schema":{"type":"object","properties":{"projectId":{"type":"string"},"status":{"type":"string","enum":["applied","unchanged"]},"applied":{"type":"array","items":{"type":"string","enum":["instructions","enabled"]}},"crypto":{"type":"object","additionalProperties":true}}}}}},"202":{"description":"Pending the owner's confirmation in the bot. Nothing is saved yet.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/WalletChangeRequest"},{"type":"object","properties":{"pollUrl":{"type":"string"},"userMessage":{"type":"string"},"humanStep":{"$ref":"#/components/schemas/HumanStep"}}}]}}}},"400":{"description":"invalid_body | invalid_wallets | invalid_address | invalid_asset | invalid_label | duplicate_network | too_many_wallets | invalid_instructions | instructions_too_long | invalid_enabled | no_wallets","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or revoked credential (code: unauthorized | invalid_token)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"project_not_found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"idempotency_conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (code: rate_limited). Honour Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected error (code: internal_error)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"audit_unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/projects/{projectId}/payment-methods/crypto/requests/{requestId}":{"get":{"summary":"Status of a proposed USDT wallet change","description":"Poll every 10 seconds or slower after PUT payment-methods/crypto returned 202. confirmed with applied=true means the wallets are saved (crypto shows the current settings); rejected, expired, superseded, stale (settings changed after the proposal: propose again) or failed mean nothing from this request was saved.","tags":["Setup"],"security":[{"ownerToken":["payments:write"]}],"x-required-scope":"payments:write","parameters":[{"name":"projectId","in":"path","required":true,"description":"Project (Bouncer) ID","schema":{"type":"string","pattern":"^[a-f0-9]{24}$"}},{"name":"requestId","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-f0-9]{24}$"}}],"responses":{"200":{"description":"Request status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WalletChangeRequest"}}}},"401":{"description":"Missing, invalid, or revoked credential (code: unauthorized | invalid_token)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"project_not_found | request_not_found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (code: rate_limited). Honour Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected error (code: internal_error)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/projects/{projectId}/payment-links":{"get":{"summary":"Shareable buyer links and website buttons","description":"Read-only, any scope. Returns t.me/<servicebot>?start=plans (all plans) and t.me/<servicebot>?start=buy_<planId> for each active plan, each with a self-contained HTML button (inline styles, no scripts) for the client's website, plus which payment methods buyers can use (card, USDT networks, access codes). available is false until the Bouncer has a linked bot.","tags":["Setup"],"security":[{"ownerToken":[]}],"parameters":[{"name":"projectId","in":"path","required":true,"description":"Project (Bouncer) ID","schema":{"type":"string","pattern":"^[a-f0-9]{24}$"}}],"responses":{"200":{"description":"Links","content":{"application/json":{"schema":{"type":"object","properties":{"projectId":{"type":"string"},"available":{"type":"boolean"},"botUsername":{"type":["string","null"]},"allPlansLink":{"type":["string","null"],"format":"uri"},"allPlansHtmlSnippet":{"type":["string","null"]},"plans":{"type":"array","items":{"type":"object","properties":{"planId":{"type":"string"},"name":{"type":"string"},"priceCents":{"type":"integer"},"currency":{"type":"string"},"priceLabel":{"type":"string"},"billingType":{"type":"string"},"link":{"type":"string","format":"uri"},"htmlSnippet":{"type":"string"}}}},"paymentMethods":{"type":"object","properties":{"configured":{"type":"boolean"},"card":{"type":"object","properties":{"available":{"type":"boolean"},"testAvailable":{"type":"boolean"}}},"crypto":{"type":"object","properties":{"available":{"type":"boolean"},"networks":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"label":{"type":"string"}}}}}},"accessCodes":{"type":"object","properties":{"available":{"type":"boolean"}}}}},"supportedCryptoNetworks":{"type":"array","items":{"type":"object","additionalProperties":true}},"notes":{"type":"array","items":{"type":"string"}}}}}}},"401":{"description":"Missing, invalid, or revoked credential (code: unauthorized | invalid_token)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"project_not_found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (code: rate_limited). Honour Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected error (code: internal_error)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/projects/{projectId}/memberships/{membershipId}":{"patch":{"summary":"Update a membership by ID","description":"Same body as PATCH /memberships.\n\nAuthenticate with the project API key (x-api-key or Bearer), or with an owner token that has the members:write scope for a project the owner owns.","tags":["Project API"],"security":[{"projectApiKey":[]},{"ownerToken":["members:write"]}],"x-required-scope":"members:write","parameters":[{"name":"projectId","in":"path","required":true,"description":"Project (Bouncer) ID","schema":{"type":"string","pattern":"^[a-f0-9]{24}$"}},{"name":"membershipId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"Updated membership","content":{"application/json":{"schema":{"type":"object","properties":{"membership":{"$ref":"#/components/schemas/Membership"}}}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid credential","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Plan does not include API access, the owner token lacks the scope, or the route is not open to owner tokens (route_not_available_to_agents)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Project not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}