{"version":"v1","generated_at":"2026-09-20T21:44:14+00:00","transport":{"url":"https://proginter.com/mcp","protocol":"mcp"},"auth":{"methods":[{"type":"oauth2","description":"OAuth 2.1 Bearer access tokens. Discover endpoints at the linked authorization server.","authorization_server":"https://proginter.com/.well-known/oauth-authorization-server","docs":"https://proginter.com/ai-access-setup/"},{"type":"apiKey","scheme":"apiKey+apiSecret","headers":["X-API-Key","X-API-Secret"],"docs":"https://proginter.com/ai-access-setup/"}],"scheme":"apiKey+apiSecret","headers":["X-API-Key","X-API-Secret"],"docs":"https://proginter.com/ai-access-setup/"},"tools":[{"name":"account","scope":"account","description":"Aggregate view of running and failed tasks across account and per-server contexts.","actions":[{"name":"get_aggregate","description":"Aggregate running/failed task items for the authenticated client - mirrors the panel's sidebar task badge.","method":"GET","path_template":"/tasks/aggregate","path_params":[],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array"}}]},{"name":"account_affiliate","scope":"account:affiliate","description":"Manage your affiliate program: application, statistics, commissions, payouts, settings, click tracking, and the banner and content marketing-asset generators.","actions":[{"name":"get_status","description":"Current affiliate status. active_offer - the live affiliate-sale campaign (headline, subtext, badge, cta_label, locked_rate as a decimal fraction, upfront in dollars, claim_required, and claim_state for this caller: pending/not_eligible/auto/claimed/eligible) or null when no sale is running - is returned on BOTH paths. When no affiliate record exists, has_affiliate is false and active_offer carries claim_state not_eligible so a prospect still sees the live offer. When has_affiliate is true, the response also includes the affiliate summary and, for active affiliates, aggregated stats.","method":"GET","path_template":"/affiliate/status","path_params":[],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.has_affiliate":"boolean","data.affiliate":"object|null","data.stats":"object|null","data.active_offer":"object|null"}},{"name":"create_activate","description":"Submit an affiliate application. Cash-out methods (paypal, bank_transfer) require a declared business: business_declared plus company_name and company_id (unless already on the account). Throttled to 5/min.","method":"POST","path_template":"/affiliate/activate","path_params":[],"body_params":[{"name":"payment_method","type":"string","required":true,"description":"One of: paypal, bank_transfer, account_credit"},{"name":"payment_details","type":"object","required":false,"description":"Method-specific details. PayPal: paypal_email. Bank transfer: account_name plus, for an Israeli account, bank_name, branch_number and account_number (local transfer), or, for an account outside Israel, iban and swift_code (international wire). Which set applies is detected automatically from the account country - it is never sent as a field"},{"name":"auto_credit","type":"boolean","required":false,"description":"Deprecated and ignored. Account credit always adds confirmed commissions to the account balance automatically."},{"name":"slug","type":"string","required":false,"description":"Custom referral slug (lowercase a-z, 0-9, hyphens, max 30 chars)"},{"name":"business_declared","type":"boolean","required":false,"description":"Required true for cash-out methods (paypal, bank_transfer): declares you operate a registered business that can issue tax documents"},{"name":"vat_exempt","type":"boolean","required":false,"description":"Declares the business VAT-exempt (Israeli osek patur / zair): no VAT is added to cash-outs and a receipt is uploaded instead of a tax invoice. Israeli account countries only - true is rejected for any other country"},{"name":"company_name","type":"string","required":false,"description":"Business name, required with business_declared unless already saved on the account. A value already saved on the account is KEPT: this endpoint fills it when empty, it never overwrites it. Change a saved value on the account profile"},{"name":"company_id","type":"string","required":false,"description":"Business ID (tax registration number), required with business_declared unless already saved on the account. A value already saved on the account is KEPT: this endpoint fills it when empty, it never overwrites it. Change a saved value on the account profile - the automatic document check matches the SAVED value. Letters, numbers, spaces and hyphens"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object","data.id":"integer","data.slug":"string","data.status":"string","data.referral_url":"string"}},{"name":"create_suspend","description":"Self-pause your affiliate program. Only active affiliates may suspend. Throttled to 5/min.","method":"POST","path_template":"/affiliate/suspend","path_params":[],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_reactivate","description":"Re-enable a self-suspended affiliate. Only valid for affiliates previously suspended by the client. Throttled to 5/min.","method":"POST","path_template":"/affiliate/reactivate","path_params":[],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_claim","description":"Claim the live affiliate-sale offer so your future referrals lock at the sale rate. Only an approved (active) affiliate can claim, and only while a claim-required sale is running. Idempotent - claiming again returns success. When the active sale applies automatically (claim_required false) this returns success without needing a claim. An affiliate whose own rate already beats the sale keeps the higher rate. Throttled to 5 per 10 minutes.","method":"POST","path_template":"/affiliate/claim","path_params":[],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object","data.claimed":"boolean"}},{"name":"list_statistics","description":"Aggregate statistics for the authenticated affiliate's dashboard. Earnings figures are in USD at the current value of the currencies they were earned in.","method":"GET","path_template":"/affiliate/statistics","path_params":[],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object"}},{"name":"list_earnings","description":"Earnings graph data over the requested timeframe. Values are in USD at the current value of the currencies they were earned in.","method":"GET","path_template":"/affiliate/graphs/earnings","path_params":[],"body_params":[{"name":"timeframe","type":"string","required":false,"description":"Time window, 6m or 12m (default 12m). Data is grouped by calendar month."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object"}},{"name":"list_referrals","description":"Referrals graph data over the requested timeframe.","method":"GET","path_template":"/affiliate/graphs/referrals","path_params":[],"body_params":[{"name":"timeframe","type":"string","required":false,"description":"Time window, 6m or 12m (default 12m). Data is grouped by calendar month."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object"}},{"name":"list_commissions","description":"Paginated commissions. Client names are anonymized (first name + last initial). native_amount + native_currency are the exact values the commission was earned in (the referred payment's currency for recurring commissions, USD for upfront bonuses) - payouts pay these at face value when they match the payout currency, and amount is the USD-equivalent anchor. locked_rate is the referral's per-referral sale-locked commission rate as a decimal fraction (null = follows the affiliate's live rate, 0 = sale-locked at $0).","method":"GET","path_template":"/affiliate/commissions","path_params":[],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page (max 100)"},{"name":"status","type":"string","required":false,"description":"Filter by status: pending, confirmed, rejected, paid, cancelled"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"integer","data[].client_name":"string","data[].type":"string","data[].amount":"number","data[].native_amount":"number","data[].native_currency":"string","data[].original_amount":"number|null","data[].original_currency":"string|null","data[].status":"string","data[].locked_rate":"number|null","data[].created_at":"string","data[].confirmed_at":"string|null","meta":"object"}},{"name":"list_payouts","description":"Paginated payouts. status is one of: pending, processing, completed, failed, cancelled. Cash-out rows carry ai_verification_status, document_type (tax_invoice, or receipt for a declared VAT-exempt business) and can_cancel_or_change, so an integrator can render upload and cancel affordances without probing the action endpoints.","method":"GET","path_template":"/affiliate/payouts","path_params":[],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page (max 100)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"integer","data[].amount":"number","data[].currency":"string","data[].payment_method":"string","data[].status":"string","data[].payment_reference":"string|null","data[].ai_verification_status":"string|null","data[].document_type":"string|null","data[].can_cancel_or_change":"boolean","data[].processed_at":"string|null","data[].created_at":"string","meta":"object"}},{"name":"get_summary","description":"Lifetime payout total and current pending (confirmed + unconfirmed) commission totals. All amounts are USD-normalized (the panel dashboard converts to the affiliate's display currency. This endpoint stays USD-canonical). Also returns the current in-flight cash-out (in_flight_payout, null when none), method_ready - which payout methods have complete saved details, so change-method targets can be offered without probing for 422s - and can_request_cash_out / method_details_incomplete / business_record_incomplete. can_request_cash_out is TRUE when the payout method is a cash-out method, no cash-out is in flight, the saved details for that method are complete, and the business record is complete (registered-business declaration plus a business name and business ID on the account profile) - it is the panel's button state, not a full pre-flight check: the $100 minimum and a suspended affiliate are still checked when you POST and come back as an actionable 422. The two *_incomplete flags say WHICH precondition is missing, because they are fixed on different pages: method_details_incomplete points at the affiliate payment settings, business_record_incomplete at the account profile.","method":"GET","path_template":"/affiliate/payouts/summary","path_params":[],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.total_paid":"number","data.total_paid_currency":"string","data.pending_confirmed":"number","data.pending_unconfirmed":"number","data.payment_method":"string","data.vat_exempt":"boolean","data.in_flight_payout":"object|null","data.in_flight_payout.id":"integer","data.in_flight_payout.status":"string","data.in_flight_payout.payment_method":"string","data.in_flight_payout.amount":"number","data.in_flight_payout.currency":"string","data.in_flight_payout.document_type":"string","data.in_flight_payout.has_invoice":"boolean","data.in_flight_payout.ai_verification_status":"string|null","data.in_flight_payout.can_cancel_or_change":"boolean","data.method_ready":"object","data.method_ready.paypal":"boolean","data.method_ready.bank_transfer":"boolean","data.method_ready.account_credit":"boolean","data.can_request_cash_out":"boolean","data.method_details_incomplete":"boolean","data.business_record_incomplete":"boolean","data.bill_to":"object","data.bill_to.name":"string","data.bill_to.company_id":"string"}},{"name":"create_request","description":"Request a cash-out of confirmed earnings (PayPal or bank transfer methods only - account credit is paid automatically). The amount, payout currency and VAT are computed server-side from the saved settings and confirmed commissions at a live exchange rate. A business declared VAT-exempt (vat_exempt) gets no VAT added and uploads a receipt instead of a tax invoice. Requires a declared business. One cash-out can be in flight at a time. Minimum $100 USD equivalent. Throttled to 5 per 10 minutes.","method":"POST","path_template":"/affiliate/payouts/request","path_params":[],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object","data.id":"integer","data.amount":"number","data.currency":"string","data.currency_symbol":"string","data.status":"string","data.ai_verification_status":"string","data.bill_to":"object","data.bill_to.name":"string","data.bill_to.company_id":"string"}},{"name":"create_invoice","description":"Upload (or replace) the tax document for a pending cash-out payout as multipart/form-data. A regular business uploads a tax invoice, a business declared VAT-exempt uploads a receipt with no VAT line. Replacing is possible only until the document passes verification - after that the payout is being prepared and the upload is rejected with 422 (contact support if something needs to change). The document is verified automatically (total must equal the payout amount including VAT when it applies, a valid tax document with your business ID, billed to Proginter, current date). On a verified PayPal payout the money is sent automatically. Throttled to 5 per 10 minutes. Note: this endpoint takes a multipart file upload (invoice) that cannot be sent over MCP - run the upload via the panel or a direct External API multipart call.","method":"POST","path_template":"/affiliate/payouts/{payout}/invoice","path_params":[{"name":"payout","type":"string","required":true,"description":"URL parameter"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object","data.id":"integer","data.invoice_uploaded":"boolean","data.ai_verification_status":"string","data.verification_summary":"string"}},{"name":"cancel","description":"DESTRUCTIVE - Cancel a pending cash-out before a tax document is uploaded for it. The confirmed earnings return to the available pool for a new request - unless the current payout method is account_credit, in which case they settle to the account balance automatically. Once a document is uploaded the window closes: that document was issued for this exact payout, so cancelling it would leave a tax document with no matching payment. From there the document can be replaced until it passes, and a payout that is processing or completed cannot be canceled at all. Throttled to 5 per 10 minutes.","method":"POST","path_template":"/affiliate/payouts/{payout}/cancel","path_params":[{"name":"payout","type":"string","required":true,"description":"URL parameter"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_change_method","description":"DESTRUCTIVE - Move a pending cash-out to another payout method before a tax document is uploaded for it. The old payout is cancelled, a fresh one is created under the new method (amounts and VAT recomputed at the live rate), and the affiliate's default payout method changes with it. Switching to account_credit settles the earnings into the account balance right away with no VAT and no document. The window closes on upload for the same reason cancel does: the replacement payout can carry a different total than the document already issued. The new method's payment details must already be saved in settings (see method_ready on the payouts summary). Throttled to 5 per 10 minutes.","method":"POST","path_template":"/affiliate/payouts/{payout}/change-method","path_params":[{"name":"payout","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"payment_method","type":"string","required":true,"description":"The new payout method. One of: paypal, bank_transfer, account_credit. Must differ from the payout's current method"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object|null","data.id":"integer","data.amount":"number","data.currency":"string","data.currency_symbol":"string","data.status":"string","data.ai_verification_status":"string|null"}},{"name":"list_settings","description":"Current affiliate profile and payment settings, including the business declaration used for cash-outs and bank_location (israel or international) - which bank field set PUT /affiliate/settings/payment expects, derived from the account country. account_country names that country. It is changed on the account profile, never here.","method":"GET","path_template":"/affiliate/settings","path_params":[],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.slug":"string","data.referral_url":"string","data.payment_method":"string","data.payment_details":"object","data.commission_rate":"string","data.upfront_bonus":"string","data.auto_credit":"boolean","data.status":"string","data.business_declared":"boolean","data.vat_exempt":"boolean","data.company_name":"string","data.company_id":"string","data.payout_currency":"string","data.bank_location":"string","data.account_country":"string"}},{"name":"update_payment","description":"Update payment method and related details. Cash-out methods (paypal, bank_transfer) require a declared business: business_declared plus company_name and company_id (unless already on the account). Throttled to 10/min.","method":"PUT","path_template":"/affiliate/settings/payment","path_params":[],"body_params":[{"name":"payment_method","type":"string","required":true,"description":"One of: paypal, bank_transfer, account_credit"},{"name":"payment_details","type":"object","required":false,"description":"Method-specific details. PayPal: paypal_email. Bank transfer: account_name plus, for an Israeli account, bank_name, branch_number and account_number (local transfer), or, for an account outside Israel, iban and swift_code (international wire). Which set applies is detected automatically from the account country - read it from bank_location on GET /affiliate/settings, it is never sent as a field. Detail keys you omit keep their saved values (updating one method never clears the other method's saved details). Send an empty string to clear a field"},{"name":"auto_credit","type":"boolean","required":false,"description":"Deprecated and ignored. Account credit always adds confirmed commissions to the account balance automatically."},{"name":"payout_currency","type":"string","required":false,"description":"Cash-out payout currency code (one of the panel-supported currencies). Earnings already in this currency are paid at face value. Earnings in other currencies are converted at the live market rate at cash-out time. Omit to keep the saved value"},{"name":"business_declared","type":"boolean","required":false,"description":"Required true when switching to a cash-out method (paypal, bank_transfer): declares you operate a registered business that can issue tax documents"},{"name":"vat_exempt","type":"boolean","required":false,"description":"Declares the business VAT-exempt (Israeli osek patur / zair): no VAT is added to cash-outs and a receipt is uploaded instead of a tax invoice. Israeli account countries only - true is rejected for any other country. Omit to keep the saved value"},{"name":"company_name","type":"string","required":false,"description":"Business name, required with business_declared unless already saved on the account. A value already saved on the account is KEPT: this endpoint fills it when empty, it never overwrites it. Change a saved value on the account profile"},{"name":"company_id","type":"string","required":false,"description":"Business ID (tax registration number), required with business_declared unless already saved on the account. A value already saved on the account is KEPT: this endpoint fills it when empty, it never overwrites it. Change a saved value on the account profile - the automatic document check matches the SAVED value. Letters, numbers, spaces and hyphens"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"update_slug","description":"Change the referral slug (and therefore the referral URL). Throttled to 10/min.","method":"PUT","path_template":"/affiliate/settings/slug","path_params":[],"body_params":[{"name":"slug","type":"string","required":true,"description":"Lowercase a-z, 0-9, hyphens, max 30 chars"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object","data.slug":"string","data.referral_url":"string"}},{"name":"list_clicks","description":"Paginated raw click log. IP addresses are masked for privacy (IPv4: last two octets hidden, IPv6: truncated after second colon).","method":"GET","path_template":"/affiliate/tracking/clicks","path_params":[],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page (max 100)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"integer","data[].ip_address":"string","data[].referer_url":"string|null","data[].landing_page":"string|null","data[].created_at":"string","meta":"object"}},{"name":"tracking_get_summary","description":"Total click count, unique IP count, and today's click count.","method":"GET","path_template":"/affiliate/tracking/summary","path_params":[],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.total_clicks":"integer","data.unique_ips":"integer","data.today_clicks":"integer"}},{"name":"get_info","description":"Banner generator limits and configuration: today's count, daily limit, remaining quota, total history count, and the supported languages, aspect ratios, and Proginter brand colors.","method":"GET","path_template":"/affiliate/banners/info","path_params":[],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.today_count":"integer","data.daily_limit":"integer","data.remaining":"integer","data.history_count":"integer","data.languages":"object","data.aspect_ratios":"object","data.proginter_colors":"object"}},{"name":"create_generate","description":"Generate a referral banner image from a prompt. Enforces the per-affiliate daily limit and a single-flight lock shared with the panel. Throttled to 20/min.","method":"POST","path_template":"/affiliate/banners/generate","path_params":[],"body_params":[{"name":"prompt","type":"string","required":true,"description":"What the banner should say or show (10-500 characters)"},{"name":"language","type":"string","required":true,"description":"Banner language. One of: en, he, ar, ru, es, fr, de, pt, zh, ja"},{"name":"aspect_ratio","type":"string","required":true,"description":"One of: 16:9, 1:1, 9:16, 4:3, 3:2"},{"name":"color_scheme","type":"string","required":true,"description":"proginter (brand colors) or custom"},{"name":"custom_colors","type":"object","required":false,"description":"Required when color_scheme is custom. Keys primary, secondary, background, each a #RRGGBB hex color"},{"name":"include_logo","type":"boolean","required":true,"description":"Whether to include the Proginter logo"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object","data.banner":"object","data.banner.id":"integer","data.banner.image_url":"string|null","data.banner.status":"string","data.banner.created_at":"string"}},{"name":"get_history","description":"Paginated history of generated banners, newest first.","method":"GET","path_template":"/affiliate/banners/history","path_params":[],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page (max 100)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"integer","data[].prompt":"string","data[].language":"string","data[].aspect_ratio":"string","data[].color_scheme":"string","data[].include_logo":"boolean","data[].image_url":"string|null","data[].status":"string","data[].error_message":"string|null","data[].created_at":"string","meta":"object","meta.current_page":"integer","meta.per_page":"integer","meta.total":"integer","meta.has_more":"boolean"}},{"name":"delete_banners","description":"DESTRUCTIVE - Delete one of your generated banners. Deleting does not restore the daily generation quota.","method":"DELETE","path_template":"/affiliate/banners/{bannerId}","path_params":[{"name":"bannerId","type":"integer","required":true,"description":"Banner ID (path parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"content_get_info","description":"Content generator limits and configuration: today's count, daily limit, remaining quota, total history count, and the supported content types, target audiences, and languages.","method":"GET","path_template":"/affiliate/content/info","path_params":[],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.today_count":"integer","data.daily_limit":"integer","data.remaining":"integer","data.history_count":"integer","data.content_types":"object","data.targets":"object","data.languages":"object"}},{"name":"content_create_generate","description":"Generate referral marketing content from a prompt. Enforces the per-affiliate daily limit and a single-flight lock shared with the panel. Throttled to 20/min.","method":"POST","path_template":"/affiliate/content/generate","path_params":[],"body_params":[{"name":"prompt","type":"string","required":true,"description":"What the content should cover (10-500 characters)"},{"name":"content_type","type":"string","required":true,"description":"One of: email, social, blog, forum, ad_copy, landing"},{"name":"target_audience","type":"string","required":true,"description":"One of: developers, businesses, agencies, bloggers, ecommerce, general"},{"name":"language","type":"string","required":true,"description":"Content language. One of: en, he, ar, ru, es, fr, de, pt, zh, ja"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object","data.content":"object","data.content.id":"integer","data.content.generated_content":"string","data.content.status":"string"}},{"name":"content_get_history","description":"Paginated history of generated content, newest first.","method":"GET","path_template":"/affiliate/content/history","path_params":[],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page (max 100)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"integer","data[].prompt":"string","data[].content_type":"string","data[].target_audience":"string","data[].language":"string","data[].generated_content":"string","data[].status":"string","data[].created_at":"string","meta":"object","meta.current_page":"integer","meta.per_page":"integer","meta.total":"integer","meta.has_more":"boolean"}},{"name":"delete_content","description":"DESTRUCTIVE - Delete one of your generated content pieces. Deleting does not restore the daily generation quota.","method":"DELETE","path_template":"/affiliate/content/{contentId}","path_params":[{"name":"contentId","type":"integer","required":true,"description":"Content ID (path parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}}]},{"name":"account_billing","scope":"account:billing","description":"Manage payment methods, account balance, transaction retries, invoice resends, and coupon validation.","actions":[{"name":"list_payment_methods","description":"Get a paginated list of your saved payment methods. Expired cards are deleted daily by the billing cron. Past transactions that referenced a deleted card keep their payment_method_id as null.","method":"GET","path_template":"/payment-methods","path_params":[],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number for pagination"},{"name":"per_page","type":"integer","required":false,"description":"Number of items per page (max 100)"},{"name":"order_by","type":"string","required":false,"description":"Column to sort by (e.g., created_at, expiry_date)"},{"name":"order_direction","type":"string","required":false,"description":"Sort direction: asc or desc (default desc)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"integer","data[].payment_type":"string","data[].provider":"string","data[].card_last_digits":"string|null","data[].expiry_date":"string","data[].is_default":"boolean","data[].created_at":"string","meta":"object"}},{"name":"create_handshake","description":"Step 1 of card registration. Returns a Tranzila handshake token. Use this token with Tranzila's hosted-fields JS SDK in the browser to PCI-safely tokenize the card. Card numbers must NEVER pass through your server. Refuses with HTTP 403 unless the calling connection is identified as an ordinary, non-anonymising one (anti-fraud check on card registration). A server or hosting address is not, so most integrations are refused here and should add the card in the Proginter panel instead, or contact support. Throttled to 10 requests per minute.","method":"POST","path_template":"/payment-methods/handshake","path_params":[],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.token":"string"}},{"name":"create_payment_methods","description":"Step 2 of card registration. Persists a card already tokenized client-side via Tranzila. Refuses with HTTP 403 unless the calling connection is identified as an ordinary, non-anonymising one (same anti-fraud check as the handshake step). Throttled to 10 requests per minute.","method":"POST","path_template":"/payment-methods","path_params":[],"body_params":[{"name":"card_last_digits","type":"string","required":true,"description":"The last 4 digits of the card (4 digits)"},{"name":"expiry_date","type":"string","required":true,"description":"Card expiry in m/y format (e.g., 12/27)"},{"name":"token","type":"string","required":true,"description":"Tranzila card token returned from the JS SDK after browser-side tokenization"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object","data.id":"integer","data.payment_type":"string","data.card_last_digits":"string","data.expiry_date":"string","data.is_default":"boolean"}},{"name":"delete_payment_methods","description":"DESTRUCTIVE - Delete one or more payment methods. Refuses with HTTP 400 if the batch includes the default method while other methods remain (set another method as default first). Returns a warning if removing the last method while a Proper Analysis subscription is active. Throttled to 10 requests per minute.","method":"DELETE","path_template":"/payment-methods","path_params":[],"body_params":[{"name":"ids","type":"array","required":true,"description":"Array of payment method IDs to delete"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","warning":"string|null"}},{"name":"update_default","description":"Set a payment method as the default. The default method is tried first by automatic charges. Throttled to 10 requests per minute.","method":"PUT","path_template":"/payment-methods/{methodId}/default","path_params":[{"name":"methodId","type":"string","required":true,"description":"URL parameter"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_deposit","description":"DESTRUCTIVE - Deposit funds to your account balance using a saved payment method. Throttled to 10 requests per minute, plus a stricter limit of 5 deposits per hour per client. Requires at least one active service on the account: with no active server or hosting account, this endpoint returns HTTP 403 and your first purchase must be made in the control panel, where your card is verified.","method":"POST","path_template":"/payment-methods/deposit","path_params":[],"body_params":[{"name":"amount","type":"number","required":true,"description":"Deposit amount (0.01-100000)"},{"name":"payment_method_id","type":"integer","required":false,"description":"Specific payment method to charge. Optional - backend falls back to default \u2192 oldest when omitted."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object","data.transaction_id":"integer","data.amount":"number","data.currency":"string"}},{"name":"update_use_balance_first","description":"Toggle the 'use balance first' preference. When enabled, the account balance is consumed before charging a payment method.","method":"PUT","path_template":"/payment-methods/use-balance-first","path_params":[],"body_params":[{"name":"use_balance_first","type":"boolean","required":true,"description":"Whether to consume balance before charging the card"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object","data.use_balance_first":"boolean"}},{"name":"create_retry","description":"DESTRUCTIVE - Retry a failed transaction. Only transactions in 'failed' status can be retried. Creates a new charge job. Throttled to 5 requests per minute. If the linked server or hosting account is no longer active (cancelled or removed), the retry is refused with HTTP 400 and the message 'This payment cannot be completed because the related service is no longer active. Please contact support if you need help.' so the card is never charged for a service the customer can no longer use. Also requires at least one active service on the account: with no active server or hosting account, this endpoint returns HTTP 403 and your first purchase must be made in the control panel, where your card is verified.","method":"POST","path_template":"/transactions/{transactionId}/retry","path_params":[{"name":"transactionId","type":"string","required":true,"description":"URL parameter"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object","data.transaction_id":"integer"}},{"name":"create_send","description":"Email completed invoices to one or more allowlisted addresses. Recipients must be the client's own email or a stored invoice_email - others are silently dropped to prevent spam relay. If `emails` is omitted or empty after filtering, the client's account email is used as the sole recipient. Throttled to 10 requests per minute.","method":"POST","path_template":"/invoices/send","path_params":[],"body_params":[{"name":"emails","type":"array","required":false,"description":"Allowlisted recipient emails (max 5). Omit to send to your account email."},{"name":"transaction_ids","type":"array","required":true,"description":"IDs of completed transactions to email (max 50)"},{"name":"note","type":"string","required":false,"description":"Optional note to include in the email (max 300 chars, newlines allowed)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object","data.recipients":"array","data.transactions":"array","data.count":"integer"}},{"name":"create_validate","description":"Check whether a coupon code is currently usable by the client, optionally against a planned server resource configuration. Returns valid:true with coupon details when usable, or valid:false with a reason. Throttled to 30 requests per minute.","method":"POST","path_template":"/coupons/validate","path_params":[],"body_params":[{"name":"code","type":"string","required":true,"description":"The coupon code to validate (max 30 chars)"},{"name":"cpu","type":"integer","required":false,"description":"Planned vCPU count, used to enforce coupon's max_cpu cap"},{"name":"memory","type":"integer","required":false,"description":"Planned RAM (MB), used to enforce coupon's max_memory cap"},{"name":"disk","type":"integer","required":false,"description":"Planned disk size (GB), used to enforce coupon's max_disk cap"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.valid":"boolean","data.message":"string|null","data.coupon":"object|null","data.coupon.code":"string","data.coupon.name":"string|null","data.coupon.discount_type":"string","data.coupon.discount_value":"number","data.coupon.discount_months":"integer|null","data.coupon.first_server_only":"boolean"}}]},{"name":"account_hosting","scope":"account:hosting","description":"Browse the shared hosting catalog, buy a plan, and manage your own hosting accounts and their websites. Shared hosting is an account-level product, so these endpoints use the account:hosting scope (covered by the parent account scope). Plan and account responses expose your disk, bandwidth, website-count, and Proper usage allowance, and NEVER include CPU or RAM caps.","actions":[{"name":"list_plans","description":"Browse the buyable shared hosting plans, priced in your account currency. CPU and RAM caps are never included; power_tier and power_label describe how much room a plan has instead.","method":"GET","path_template":"/hosting/plans","path_params":[],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number (default 1)."},{"name":"per_page","type":"integer","required":false,"description":"Results per page (default 50, max 200)."},{"name":"search","type":"string","required":false,"description":"Filter plans by name."},{"name":"order_by","type":"string","required":false,"description":"Sort field: name, price_monthly, disk_gb, bandwidth_gb, website_count, or sort_index (default sort_index)."},{"name":"order_direction","type":"string","required":false,"description":"asc or desc (default asc)."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of plan objects: id, name, disk_gb, bandwidth_gb, website_count (integers, 0 = unlimited), price_monthly, currency (no cpu/ram), power_tier (integer 1-4, how much room the plan has, 4 being the largest shared plan) and power_label (the same thing as the sentence the panel prints), is_free_tier (boolean, true on the one free plan), eligible (boolean, ONE check and not a purchase guarantee: it is always true on paid plans, and on the free plan it is false once the account already has a free hosting account. POST /hosting applies further checks that this flag does not reflect - a cross-account abuse screen on the free plan, and the completed-profile requirement on paid ones - so eligible true means not-already-refused-for-that-one-reason rather than this-purchase-will-succeed). NOTE: this endpoint returns only plans that are currently offered, and the free plan is not offered yet, so is_free_tier is false on every row you get back today. The field and the rules keyed on it are live and correct - read them rather than inferring anything from the price - but do not tell a customer a free plan is available until a row with is_free_tier true actually appears here","meta.current_page":"integer","meta.last_page":"integer","meta.per_page":"integer","meta.total":"integer"}},{"name":"get_free_month_check","description":"Check whether you are eligible for the shared-hosting first-month-free offer (eligible only when you have no hosting account yet and an offer is running).","method":"GET","path_template":"/hosting/free-month-check","path_params":[],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"eligible":"boolean","coupon":"object or null: code, discount_type, discount_value, discount_months, max_disk, name"}},{"name":"create_generate_price_hash","description":"Generate a single-use price calculation hash bound to your account. Required before calling the buy endpoint.","method":"POST","path_template":"/hosting/generate-price-hash","path_params":[],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"price_calculation_hash":"string","created_at":"string (ISO 8601)"}},{"name":"create_hosting","description":"Buy a shared hosting plan. Requires a valid price calculation hash from the generate-price-hash endpoint. The account is created and placed on a Proginter shared server automatically. The price is the plan's monthly price converted to your currency, verified server-side. If your account has an overdue balance on any service (a server or another hosting account), this purchase is blocked until you settle the balance. Two separate rules can refuse a purchase with HTTP 403, and they are keyed on different things. The active-service rule is keyed on PRICE: a plan priced above 0 requires at least one active service on the account, so with no active server or hosting account your first paid purchase must be made in the control panel, where your card is verified. A plan priced at 0 charges no card and is not affected by that rule. The profile rule is keyed on the PLAN, not the price: every plan except the free one requires a completed profile, so a new trial account is refused with code kyc_required until it adds its name and a payment method - and that includes a paid plan currently priced at 0 by a promotion. Only the plan whose is_free_tier is true skips both, and it carries its own rule instead: one free hosting account per customer, returning HTTP 422 when the account already has one. Each plan row on GET /hosting/plans carries is_free_tier and eligible, so read those two rather than inferring either from the price.","method":"POST","path_template":"/hosting","path_params":[],"body_params":[{"name":"hosting_plan_id","type":"integer","required":true,"description":"ID of the plan to buy (from /hosting/plans)."},{"name":"price_calculation_hash","type":"string","required":true,"description":"Hash from the generate-price-hash endpoint."},{"name":"frontend_calculated_price","type":"float","required":true,"description":"The price you expect to pay, for server-side verification. Pass back the chosen plan's price_monthly value from GET /hosting/plans unchanged (it is already in your account currency at the correct precision). The server recomputes the authoritative price and rejects a mismatch."},{"name":"coupon_code","type":"string","required":false,"description":"Coupon code to apply (max 255 characters). When omitted, an eligible shared-hosting first-month-free promo is auto-applied."},{"name":"payment_method_id","type":"integer","required":false,"description":"Payment method ID. Optional when you have a default method on file (the backend falls back to it). Required when the plan is paid and no method exists."},{"name":"use_balance","type":"boolean","required":false,"description":"Whether to apply account balance to the charge."},{"name":"label","type":"string","required":false,"description":"Optional label for the hosting account (max 255 characters). Defaults to the plan name."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data.task_id":"integer","data.price":"float","data.currency":"string","data.immediate_charge":"float"}},{"name":"get_ai_usage","description":"Your Proper usage allowance, how much you have used, and what is left, per window. This is per-CUSTOMER (your total budget across ALL your hosting accounts combined), not per-account - there is no per-account variant. Messages sent from the panel and over WhatsApp count on the same allowance. For a shared-hosting customer the budget is set by your plan. CPU and RAM caps are never included.","method":"GET","path_template":"/hosting/ai-usage","path_params":[],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"data.messages.per_minute":"object: limit, used, remaining, unlimited","data.messages.per_hour":"object: limit, used, remaining, unlimited","data.messages.per_day":"object: limit, used, remaining, unlimited","data.tokens.daily":"object: limit, used, remaining, unlimited","data.tokens.weekly":"object: limit, used, remaining, unlimited","data.tokens.monthly":"object: limit, used, remaining, unlimited"}},{"name":"list_accounts","description":"List your own shared hosting accounts with status, price, next billing date, disk and bandwidth usage, and the plan. CPU and RAM caps are never included.","method":"GET","path_template":"/hosting/accounts","path_params":[],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number (default 1)."},{"name":"per_page","type":"integer","required":false,"description":"Results per page (default 20, max 200)."},{"name":"search","type":"string","required":false,"description":"Filter accounts by label."},{"name":"order_by","type":"string","required":false,"description":"Sort field: id, label, status, next_cycle_at, or price (default id)."},{"name":"order_direction","type":"string","required":false,"description":"asc or desc (default desc)."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of account objects: id, label, status, price, currency, next_billing_date, overdue, grace_until (ISO 8601 datetime set while a payment extension holds the account online, else null), is_graced (boolean, true while active), websites_count, websites (array of the account's own websites, each with id, name, domain and status - this is the only way to go from an account to its sites, because GET /servers/websites has no hosting-account filter), usage (disk/bandwidth limit_gb/used_gb/remaining_gb/unlimited), plan (id, name, disk_gb, bandwidth_gb, website_count - integers, 0 = unlimited). No cpu/ram.","meta.current_page":"integer","meta.last_page":"integer","meta.per_page":"integer","meta.total":"integer"}},{"name":"get_accounts","description":"Get one of your hosting accounts in detail, including its websites. An account that is not yours returns 404. CPU and RAM caps are never included.","method":"GET","path_template":"/hosting/accounts/{account}","path_params":[{"name":"account","type":"integer","required":true,"description":"Hosting account ID (URL parameter)."}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"data.id":"integer","data.label":"string","data.status":"string","data.price":"float","data.currency":"string","data.next_billing_date":"string or null","data.overdue":"boolean","data.grace_until":"string or null (ISO 8601 datetime, set only while an operator payment extension holds the account online)","data.is_graced":"boolean (true while an active payment extension is protecting this account)","data.websites_count":"integer","data.usage":"object: disk and bandwidth limit_gb/used_gb/remaining_gb/unlimited, measured_at","data.plan":"object: id, name, disk_gb, bandwidth_gb, website_count (integers, 0 = unlimited, no cpu/ram)","data.websites":"array: id, domain, name, status"}},{"name":"create_websites","description":"Create a website under one of your hosting accounts, up to the plan's website limit. The account must be active and not overdue. Use a free subdomain or your own domain, and optionally install WordPress.","method":"POST","path_template":"/hosting/accounts/{account}/websites","path_params":[{"name":"account","type":"integer","required":true,"description":"Hosting account ID (URL parameter)."}],"body_params":[{"name":"name","type":"string","required":true,"description":"Website name (max 255 characters)."},{"name":"free_subdomain","type":"boolean","required":true,"description":"When true, a free Proginter subdomain is generated and the domain field is ignored."},{"name":"subdomain","type":"string","required":false,"description":"Optional label for the free subdomain (letters, numbers, hyphens, max 63 characters). The site becomes label.proginter.app. A taken or reserved label falls back to a random one. Only used when free_subdomain is true. The response carries the final domain."},{"name":"domain","type":"string","required":false,"description":"Your domain (required when free_subdomain is false, max 255 characters). Reverse DNS and protected domains are rejected."},{"name":"php_version","type":"string","required":false,"description":"PHP version for the site (max 20 characters). Defaults to the server default when omitted."},{"name":"install_wordpress","type":"boolean","required":true,"description":"When true, WordPress is installed and the wp_* fields are required."},{"name":"wp_name","type":"string","required":false,"description":"WordPress site title (required when install_wordpress is true, max 255 characters)."},{"name":"wp_email","type":"string","required":false,"description":"WordPress admin email (required when install_wordpress is true)."},{"name":"wp_language","type":"string","required":false,"description":"WordPress language code (required when install_wordpress is true) from the supported set."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"website.id":"integer","website.domain":"string","website.name":"string","task_id":"integer - the async deployment task. Poll it (or the website status on GET /hosting/accounts/{account}) for completion"}}]},{"name":"account_notifications","scope":"account:notifications","description":"Manage your account profile, contacts, transactions, and billing information.","actions":[{"name":"list_notifications","description":"Get all notifications (account, server, website).","method":"GET","path_template":"/notifications","path_params":[],"body_params":[{"name":"read_status","type":"string","required":false,"description":"Filter: all, read, unread"},{"name":"source","type":"string","required":false,"description":"Filter by source: account, server, website"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"integer|string","data[].source":"string","data[].title":"string","data[].text":"string","data[].type":"string","data[].is_read":"boolean","data[].read_at":"string|null","data[].created_at":"string|null","data[].server_name":"string|null","data[].website_domain":"string|null","meta":"object","meta.current_page":"integer","meta.last_page":"integer","meta.per_page":"integer","meta.total":"integer"}},{"name":"get_unread_count","description":"Get unread notification count.","method":"GET","path_template":"/notifications/unread-count","path_params":[],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":{"count":"integer","account":"integer","server":"integer","website":"integer"}}},{"name":"update_mark_read","description":"Mark notifications as read.","method":"PUT","path_template":"/notifications/mark-read","path_params":[],"body_params":[{"name":"ids","type":"array","required":false,"description":"Specific notification IDs (empty or omitted = mark ALL of them read). One source is handled per call, and the sources use different ID formats: account notification IDs are 36-character text IDs, while server and website notification IDs are whole numbers. GET /notifications returns both kinds in one list, so a mixed selection has to be split into one call per source. Any ID that is not the right format for the source you named is ignored, and the response lists it under ignored."},{"name":"source","type":"string","required":false,"description":"Which list the IDs come from: account, server or website. Leaving it out means account, so an ID taken from a server or website row needs this set or it will not match anything. When you send NO ids, this also decides which list is cleared: name one and only that list is touched, leave it out and all three are. Any other value is refused with 422 and nothing is changed."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","marked":"integer","ignored":"array"}},{"name":"delete_notifications","description":"DESTRUCTIVE - Delete notifications.","method":"DELETE","path_template":"/notifications","path_params":[],"body_params":[{"name":"ids","type":"array","required":false,"description":"Specific notification IDs. LEAVING THIS EMPTY OR OUT DELETES EVERY NOTIFICATION on the account, so send it explicitly. One source is handled per call, and the sources use different ID formats: account notification IDs are 36-character text IDs, while server and website notification IDs are whole numbers. GET /notifications returns both kinds in one list, so a mixed selection has to be split into one call per source. Any ID that is not the right format for the source you named is ignored, and the response lists it under ignored."},{"name":"source","type":"string","required":false,"description":"Which list the IDs come from: account, server or website. Leaving it out means account, so an ID taken from a server or website row needs this set or it will not match anything. When you send NO ids, this also decides which list is cleared: name one and only that list is touched, leave it out and all three are. Any other value is refused with 422 and nothing is changed."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","deleted":"integer","ignored":"array"}},{"name":"list_preferences","description":"Get notification preferences.","method":"GET","path_template":"/notifications/preferences","path_params":[],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":{"email":{"all_enabled":"boolean","categories":"array of {key, label, icon, enabled, types: [{key, label, description, enabled}]}"},"push":{"all_enabled":"boolean","categories":"array of {key, label, icon, enabled, types: [{key, label, description, enabled}]}"},"has_push_devices":"boolean","get_updates":"boolean"}}},{"name":"update_preferences","description":"Update notification preferences.","method":"PUT","path_template":"/notifications/preferences","path_params":[],"body_params":[{"name":"email","type":"object","required":true,"description":"Email preferences (all_enabled, categories, types)"},{"name":"push","type":"object","required":true,"description":"Push preferences (all_enabled, categories, types)"},{"name":"get_updates","type":"boolean","required":true,"description":"Receive product updates"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}}]},{"name":"account_profile","scope":"account:profile","description":"Manage your account profile, contacts, transactions, and billing information.","actions":[{"name":"get_profile","description":"Get your account profile information including name, email, company details, and contact information with readable names.","method":"GET","path_template":"/profile","path_params":[],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.id":"integer","data.name":"string","data.email":"string","data.company_name":"string|null","data.company_id":"string|null","data.phone":"string|null","data.phone_prefix":"string|null","data.phone_prefix_country":"string|null","data.country":"string|null","data.country_code":"string|null","data.profile_photo_url":"string|null","data.invoice_emails":"array|null","data.balance":"float","data.currency":"string","data.email_verified":"boolean","data.two_factor_enabled":"boolean","data.created_at":"string"}},{"name":"list_transactions","description":"Get a paginated list of your billing transactions including payment method details and line items.","method":"GET","path_template":"/transactions","path_params":[],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number for pagination"},{"name":"per_page","type":"integer","required":false,"description":"Number of items per page (max 100)"},{"name":"sort_by","type":"string","required":false,"description":"Column to sort by"},{"name":"sort_order","type":"string","required":false,"description":"Sort direction: asc or desc"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"integer","data[].transaction_id":"string|null","data[].amount":"float (ex-VAT base)","data[].total_with_vat":"float (the BILL: ex-VAT base plus VAT. Not necessarily what was collected - see total_collected)","data[].total_collected":"float|null (what the customer ACTUALLY paid). Use this for 'how much did I pay'. Client balance is held EX-VAT, so a charge funded from balance collects less than it bills: a 12.00 service billed 14.16 takes 12.00 off the balance and nothing from the card. Equals total_with_vat whenever no balance was used, which is the usual case. NULL on any row that has not settled (status pending or failed) - nothing has been taken there, so do not treat it as 0 and do not sum it into a paid total. A refund does NOT reduce this figure: on a row with status refunded, subtract total_refunded to get what the customer kept paying","data[].paid_from_balance":"float|null (the part taken from account balance, 0 when the card paid all of it). NULL on an unsettled row, same rule as total_collected","data[].paid_from_card":"float|null (the part charged to the card, VAT included). NULL on an unsettled row, same rule as total_collected","data[].total_refunded":"float|null (what actually went BACK to the customer, both channels added up). A refund has the same two legs as the charge: refunded_to_balance is credited to account balance immediately, refunded_to_card goes back to the card over several days. A partial refund returns only part of the card leg and leaves the balance leg alone. NULL when the row was never refunded, and ALSO null on a refunded row whose split was not recorded - some automatic refunds (a failed plan change, a failed autoscale) return the whole payment without writing the breakdown. Absent here means unknown, never zero, so never default it to 0 when status is refunded","data[].refunded_to_balance":"float|null (the part credited back to account balance, ex-VAT like the balance ledger itself). Same NULL rule as total_refunded. 0 when the refund did not settle the whole card leg, and 0 until the credit has actually landed","data[].refunded_to_card":"float|null (the part returned to the card, VAT included). Same NULL rule as total_refunded","data[].balance_figures_in_ledger_currency":"bool. paid_from_balance and refunded_to_balance are denominated in this transaction's currency, while the customer's balance ledger is denominated in their own currency. They agree on every ordinary row and this is then true. When it is FALSE the totals are still what moved, but the two balance figures are NOT the movement on the customer's balance - do not present them as a balance change, and do not convert them yourself","data[].vat_rate":"float (e.g. 0.18 for 18%, 0 if VAT-exempt)","data[].currency":"string","data[].status":"string","data[].payment_method":"object|null","data[].payment_method.id":"integer","data[].payment_method.type":"string","data[].payment_method.last_digits":"string|null","data[].payment_method.is_default":"boolean","data[].items":"array","data[].items[].id":"integer","data[].items[].type":"string|null","data[].items[].description":"string|null","data[].items[].quantity":"float|null","data[].items[].unit_price":"float|null","data[].items[].total_amount":"float|null","data[].created_at":"string","data[].three_ds_state":"string|null (card-verification lifecycle of a customer-present purchase, null for ordinary rows. awaiting_challenge means the purchase waits for the customer to verify the payment with their bank in the control panel)","data[].failure_reason":"string|null (failed transactions only - customer-safe decline reason from the card company, null when no attempt details are recorded)","data[].automatic_tries":"integer (failed transactions only - automatic charge attempts so far)","data[].manual_tries":"integer (failed transactions only - manual retry attempts so far)","data[].last_attempt_at":"string|null (failed transactions only - time of the latest recorded charge attempt)","meta":"object"}},{"name":"get_info","description":"Get billing summary information including next charge amount, overdue charges, balance, and upcoming charges. The forward totals (next_charge, overdue_charge, has_overdue, upcoming_charges) cover BOTH billable VPS servers AND billable shared-hosting accounts. Each upcoming-charge date lists its servers under `servers` and its shared-hosting accounts under a parallel `accounts` array. server_count counts servers only. hosting_account_count is the sibling count of billable shared-hosting accounts. When Proginter grants a payment extension on an overdue server or account, graced_charge is the protected subset of overdue_charge and has_graced is true, and each server or account row carries is_graced plus grace_until (the date the extension runs to), so a protected balance is distinguishable from one at immediate risk.","method":"GET","path_template":"/payments/info","path_params":[],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.next_charge":"float","data.overdue_charge":"float","data.graced_charge":"float","data.additional_charges":"float","data.balance":"float","data.currency":"string","data.upcoming_charges":"array","data.upcoming_charges[].date":"string","data.upcoming_charges[].total":"float","data.upcoming_charges[].servers":"array","data.upcoming_charges[].accounts":"array","data.has_overdue":"boolean","data.has_graced":"boolean","data.server_count":"integer","data.hosting_account_count":"integer"}},{"name":"get_login_history","description":"Get a paginated list of your account login history including IP addresses, user agents, and timestamps.","method":"GET","path_template":"/login-history","path_params":[],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number for pagination"},{"name":"per_page","type":"integer","required":false,"description":"Number of items per page (max 100)"},{"name":"sort_by","type":"string","required":false,"description":"Column to sort by"},{"name":"sort_order","type":"string","required":false,"description":"Sort direction: asc or desc"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"integer","data[].ip_address":"string|null","data[].user_agent":"string|null","data[].status":"string|null","data[].failure_reason":"string|null","data[].created_at":"string","meta":"object"}},{"name":"list_contacts","description":"Get a paginated list of your account contacts.","method":"GET","path_template":"/contacts","path_params":[],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number for pagination"},{"name":"per_page","type":"integer","required":false,"description":"Number of items per page (max 100)"},{"name":"sort_by","type":"string","required":false,"description":"Column to sort by"},{"name":"sort_order","type":"string","required":false,"description":"Sort direction: asc or desc"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"integer","data[].name":"string","data[].email":"string","data[].phone":"string|null","data[].phone_prefix":"string|null","data[].role":"string|null","data[].created_at":"string","meta":"object"}},{"name":"create_contacts","description":"Add a new contact to your account.","method":"POST","path_template":"/contacts","path_params":[],"body_params":[{"name":"name","type":"string","required":true,"description":"Contact name"},{"name":"email","type":"string","required":true,"description":"Contact email address"},{"name":"phone_prefix_id","type":"integer","required":false,"description":"Phone prefix ID"},{"name":"phone","type":"string","required":false,"description":"Phone number"},{"name":"role","type":"string","required":false,"description":"Contact role"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object","data.id":"integer","data.name":"string","data.email":"string","data.phone":"string|null","data.phone_prefix":"string|null","data.role":"string|null","data.created_at":"string"}},{"name":"update_contacts","description":"Update an existing contact.","method":"PUT","path_template":"/contacts/{contactId}","path_params":[{"name":"contactId","type":"integer","required":true,"description":"Contact ID (URL parameter)"}],"body_params":[{"name":"name","type":"string","required":true,"description":"Contact name"},{"name":"email","type":"string","required":true,"description":"Contact email address"},{"name":"phone_prefix_id","type":"integer","required":false,"description":"Phone prefix ID"},{"name":"phone","type":"string","required":false,"description":"Phone number"},{"name":"role","type":"string","required":false,"description":"Contact role"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object","data.id":"integer","data.name":"string","data.email":"string","data.phone":"string|null","data.phone_prefix":"string|null","data.role":"string|null","data.created_at":"string"}},{"name":"delete_contacts","description":"DESTRUCTIVE - Delete multiple contacts from your account.","method":"DELETE","path_template":"/contacts","path_params":[],"body_params":[{"name":"ids","type":"array","required":true,"description":"Array of contact IDs to delete"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"update_profile","description":"Update client profile. The account email cannot be changed via the API - contact support if you need to change it.","method":"PUT","path_template":"/profile","path_params":[],"body_params":[{"name":"name","type":"string","required":true,"description":"Full name"},{"name":"company_name","type":"string","required":false,"description":"Business name. Required when company_id is provided."},{"name":"company_id","type":"string","required":false,"description":"Business ID (tax registration number). Letters, numbers, spaces and hyphens."},{"name":"country_id","type":"integer","required":true,"description":"Country ID"},{"name":"phone_prefix_id","type":"integer","required":true,"description":"Phone prefix country ID"},{"name":"phone","type":"string","required":true,"description":"Phone number"},{"name":"profile_photo","type":"array","required":false,"description":"Profile photo. Upload an image file (JPEG or PNG, up to 5MB) as the array's first item to set a new photo (browser or multipart form only). Send a non-empty array of any short string (up to 255 characters) to keep the current photo. JSON and MCP clients cannot upload a file, so they use the keep-photo array. Omit this field entirely to keep the current photo unchanged (partial update). Send an explicit empty array to remove the current photo."},{"name":"invoice_emails","type":"array","required":false,"description":"Additional email addresses for invoices (max 5)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":{"id":"integer","name":"string","email":"string","company_name":"string|null","company_id":"string|null","phone":"string|null","phone_prefix":"string|null","phone_prefix_country":"string|null","country":"string|null","country_code":"string|null","profile_photo_url":"string|null","invoice_emails":"array|null"}}},{"name":"update_password","description":"Change client password. Throttled to 5 requests per minute. Note: panel sessions on other browsers/devices for this client are invalidated on their next request. The API credential used for this call (and any other API credentials) is NOT affected. Rotate those separately if needed.","method":"PUT","path_template":"/profile/password","path_params":[],"body_params":[{"name":"current_password","type":"string","required":true,"description":"Current password"},{"name":"password","type":"string","required":true,"description":"New password (min 8 chars, mixed case, numbers, symbols)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_qr_code","description":"Generate a new TOTP secret and otpauth:// URI for 2FA setup. Returns the raw otpauth:// string - NOT a base64 image. API clients render the QR code client-side from this URI. Throttled to 10 requests per minute.","method":"GET","path_template":"/profile/2fa/qr-code","path_params":[],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.secret":"string","data.qr_code_url":"string"}},{"name":"enable_2fa","description":"Enable 2FA by proving the client has registered the secret in their authenticator app (submits the current TOTP code). Sends an email notification on success. Throttled to 10 requests per minute.","method":"PUT","path_template":"/profile/2fa/enable","path_params":[],"body_params":[{"name":"secret","type":"string","required":true,"description":"The secret returned by /profile/2fa/qr-code"},{"name":"code","type":"string","required":true,"description":"Current 6-digit TOTP code from the authenticator"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"disable_2fa","description":"DESTRUCTIVE - Disable 2FA. Requires both the account password and a current TOTP code. Throttled to 10 requests per minute.","method":"PUT","path_template":"/profile/2fa/disable","path_params":[],"body_params":[{"name":"password","type":"string","required":true,"description":"Current account password"},{"name":"code","type":"string","required":true,"description":"Current 6-digit TOTP code"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"list_announcements","description":"Paginated list of platform-wide announcements. Announcements are global (not client-scoped). Stored attachment IDs are auto-hydrated into file details by the model's toArray() method - the `attachments` field returns full metadata, not raw IDs.","method":"GET","path_template":"/announcements","path_params":[],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page (max 100)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"integer","data[].title":"string","data[].description":"string|null","data[].content":"string|null","data[].attachments":"array","data[].created_at":"string","meta":"object"}},{"name":"create_resend_verification","description":"Resend the email-verification link to the authenticated client's email. Returns 400 if already verified. Throttled to 5 requests per minute, counted against ONE shared budget with the panel's own resend endpoints, so panel activity can use it up. Every request counts, including one the panel then rejects. A SECOND limit applies to the address itself: 10 verification messages per hour to that address, counted across the signup message, every resend and the change-address flow, and spendable by the panel too. A 429 can therefore come from either limit, and the two need very different waits, so ALWAYS read the Retry-After header rather than assuming the one-minute window - the per-address limit can return up to 3600 seconds. This address limit is separate from the shared-access invitation budget, so the two cannot use each other up.","method":"POST","path_template":"/profile/email/resend-verification","path_params":[],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_push_devices","description":"Register a mobile push device token. If the same device_token is registered to another client (device transferred), that row is removed before the upsert so only one client owns the token. Throttled to 30 requests per minute.","method":"POST","path_template":"/profile/push-devices","path_params":[],"body_params":[{"name":"device_token","type":"string","required":true,"description":"Platform-provided device token (max 255 chars)"},{"name":"platform","type":"string","required":true,"description":"One of: ios, android"},{"name":"device_name","type":"string","required":false,"description":"Human-readable device label (max 255 chars)"},{"name":"app_version","type":"string","required":false,"description":"App version string (max 50 chars)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_push_devices","description":"DESTRUCTIVE - Mark a push device inactive (logical unregister - the row is kept so re-registration later is cheap). Throttled to 30 requests per minute.","method":"DELETE","path_template":"/profile/push-devices","path_params":[],"body_params":[{"name":"device_token","type":"string","required":true,"description":"Platform-provided device token (max 255 chars)"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}}]},{"name":"account_sharing","scope":"account:sharing","description":"Grant other clients access to your servers or websites, and list resources that have been shared with you.","actions":[{"name":"list_permissions","description":"List permissions you have granted to others (owner side). If your API key is limited to specific websites or servers, this list leaves out any grant that reaches a website or server your key does not cover, and any grant naming a resource your key is not allowed to share. The page totals count only the rows you get back, so paging stays correct. A key that covers every website and every server AND is not narrowed on any individual one sees every grant, and so does a browser session. If your key is marked as covering everything but holds a narrower permission on one particular website or server, it is still narrowed here, because it cannot share that one. One more case to know about: the check that refuses a duplicate share for an address you have already invited is NOT narrowed, so this list can show you no matching row while POST /permissions still answers 409 for that address. If that happens, the existing grant is on a resource outside your key.","method":"GET","path_template":"/permissions","path_params":[],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page (max 100)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"integer","data[].invitee_email":"string","data[].invitee_name":"string|null","data[].status":"string","data[].scope":"string","data[].scope_label":"string","data[].scope_summary":"string","data[].permission_type":"string","data[].resources":"array","data[].time_remaining":"string|null","data[].approval_url":"null - always null on this endpoint. The key is still present so existing readers do not break, but the invitation link is no longer returned here: an invitation link is enough to accept a grant, and this list still reaches every pending invite the calling credential can see, which for a key covering the whole account and for a browser session is all of them. The credential narrowing on this endpoint changes WHICH rows you get back, so it does not make returning the links safe. The create response returns a real link ONLY when the new grant is pending. If you sent activate_immediately=true and the invitee already had a Proginter account, the grant is created active, there is no invitation to send, and approval_url is null there too - that is success, not a missing link, so branch on status rather than treating a null link as a failure. The UPDATE response never carries a link either, because a link is only ever emitted while a grant is pending, and update refuses anything that is not active, so approval_url on an update response is always null too. There is no endpoint that hands the link back a second time - store it from the create response, or use resend-approval, which mails the invitee a fresh one without returning it to you.","data[].token_expires_at":"string|null","data[].created_at":"string","data[].updated_at":"string","meta":"object"}},{"name":"create_permissions","description":"Create a new permission and email the invitee. When the invitee already has an account and activate_immediately=true, the permission activates without requiring approval. Throttled to 10 requests per minute, counted against ONE shared invitation-email budget together with the update and resend-approval endpoints. The budget belongs to the customer account, so the customer's own panel session spends it too. Every request counts, including one the panel then rejects. A SECOND limit applies to the invited address itself: 10 messages per hour to that address, counted across the invitation, the scope-change notice, the access-granted notice and the verification code, and spendable by any customer who invites the same address. The last 3 of those 10 are held for the invitee's own verification code, so the most YOU can send to one address is 7 an hour - without that reserve, repeated resends would use up the hour and leave the invitee unable to get the code they need to accept. Passing the first limit and failing the second still returns 201, because the permission really was created, but the response then carries an email_withheld field holding the reason and NO email was sent. Check for that field before telling anyone the invitation is on its way. If the grant is still pending the invitee needs that link, so use resend-approval once the hour has passed. If you used activate_immediately the access is already in place and only the notice was skipped, so there is nothing to retry and resend-approval will refuse, because it only works on a pending grant. An API key can only share what it can itself reach, and that is measured three ways: WHETHER it holds the side at all, WHICH resources on it, and HOW MUCH power over them. WHETHER: sharing websites needs the plain websites scope and sharing servers needs both a servers scope and a websites scope, because sharing a server also shares every website on it. SECOND, WHICH resources. For a key that was given any website or server access: a scope covering every website needs a key that covers every website, a scope covering every server needs a key that covers every server, and a named website or server has to be one the key already holds. Sharing a whole server counts as sharing every website on it, so such a key cannot share a server unless it covers every website. A key created with only account scopes, and no website or server picked, holds neither side and cannot create or update a share at all. It can still list and revoke the shares that already exist, but it cannot resend an invitation either. THIRD, HOW MUCH. Lowering the level is NOT a way past a refusal: sharing needs the plain websites or servers scope on the resource named, at EVERY level including read_only, so a grant refused at full is refused identically at read_only. The account-wide scopes do not read the level at all. A narrow scope cannot share at all, at any level: sharing needs the plain websites or servers scope, because a read_only share hands the invited person the site's files, database names, settings and backups, while a scope such as websites:logs only reads log lines. Those are both called read-only and they are not the same size. Such a key still uses every endpoint its own scope covers and still sees its own websites in the ordinary lists. The same holds per resource when you send website_permission_types or server_permission_types, because it is the level the invitee actually ends up with that is measured. This second rule applies to every credential, including OAuth apps. Anything wider than the key holds is refused with 403, and the message says which side is short. Every id you send must be a positive whole number, and one that is not is refused with 422 rather than quietly dropped, so a grant is never created holding less than you asked for. One address gets one permission on your account, whatever its scope and whatever its status, so a second one for the same address is refused with 409 and a top-level message. Note the code: it is a 409 carrying a message, not a 422 with field errors, so a client that only branches on 422 will fall through to its generic handler. Read the existing row from GET /permissions first: an active one can be changed with PUT, and a pending or failed one has to be deleted and created again.","method":"POST","path_template":"/permissions","path_params":[],"body_params":[{"name":"invitee_email","type":"string","required":true,"description":"Email of the user being granted access (cannot be your own email)"},{"name":"invitee_name","type":"string","required":false,"description":"Display name for the invitee"},{"name":"scope","type":"string","required":true,"description":"One of: anything, servers_all, websites_all, servers_specific, websites_specific, mixed_specific (combined: attach specific servers AND specific websites on the same permission)"},{"name":"permission_type","type":"string","required":true,"description":"Access level: full or read_only. read_only covers every read endpoint (GET) on the shared resource plus read-only SQL statements on the database query endpoint, over API keys and OAuth apps such as MCP clients alike. Reads that hand back private content (file content, downloads, backup contents, a git diff, remote URLs, passwords and login links) need full. Every write needs full."},{"name":"server_ids","type":"array","required":false,"description":"Required when scope=servers_specific (array of server IDs you own, maximum 500). For scope=mixed_specific at least one of server_ids/website_ids must be non-empty."},{"name":"website_ids","type":"array","required":false,"description":"Required when scope=websites_specific (array of website IDs you own, maximum 500). For scope=mixed_specific at least one of server_ids/website_ids must be non-empty."},{"name":"server_permission_types","type":"object","required":false,"description":"Per-server override map: { serverId: 'full'|'read_only' }. Maximum 500 entries. A server you do not list here takes the grant's own permission_type, so a read_only grant stays read_only on every server in it."},{"name":"website_permission_types","type":"object","required":false,"description":"Per-website override map: { websiteId: 'full'|'read_only' }. Maximum 500 entries. A website you do not list here takes the grant's own permission_type, so a read_only grant stays read_only on every website in it."},{"name":"activate_immediately","type":"boolean","required":false,"description":"When true + invitee is already a client, skip the email approval step"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object","email_withheld":"string (present ONLY when the per-address limit stopped the email. Its value is the reason. Absent on the normal path, so test for presence, not for null)"}},{"name":"update_permissions","description":"Update an active permission's scope, permission_type, or specific resources. Email cannot be changed. A change that alters what the invitee can reach emails them, so this is throttled to 10 requests per minute, counted against the same shared invitation-email budget as create and resend-approval. The budget belongs to the customer account, so the customer's own panel session spends it too. The same second, per-address limit described on the create endpoint applies here, and this response carries the same email_withheld field when the notice was not sent. The scope change itself is saved either way, so there is nothing to retry - only the notification to the invitee was skipped. Both halves of the API key reach rule described on the create call apply here, and for the same reason: an update can widen a grant just as a create can. Every id you send must be a positive whole number, and one that is not is refused with 422 rather than quietly dropped, which matters more on this call than on create, because the ids you send REPLACE the ones already on the grant. Per-resource access levels follow the same never-widen-by-accident principle: if you leave website_permission_types or server_permission_types out, a resource that was already read_only STAYS read_only even when the grant itself is full, and lowering the grant to read_only still lowers every resource with it. To raise one resource back to full, name it in the map.","method":"PUT","path_template":"/permissions/{permissionId}","path_params":[{"name":"permissionId","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"invitee_email","type":"string","required":true,"description":"Must match the permission's existing email"},{"name":"invitee_name","type":"string","required":false,"description":"Display name for the invitee"},{"name":"scope","type":"string","required":true,"description":"One of: anything, servers_all, websites_all, servers_specific, websites_specific, mixed_specific (combined: attach specific servers AND specific websites on the same permission)"},{"name":"permission_type","type":"string","required":true,"description":"Access level: full or read_only. read_only covers every read endpoint (GET) on the shared resource plus read-only SQL statements on the database query endpoint, over API keys and OAuth apps such as MCP clients alike. Reads that hand back private content (file content, downloads, backup contents, a git diff, remote URLs, passwords and login links) need full. Every write needs full."},{"name":"server_ids","type":"array","required":false,"description":"Required when scope=servers_specific (array of server IDs you own, maximum 500). For scope=mixed_specific at least one of server_ids/website_ids must be non-empty."},{"name":"website_ids","type":"array","required":false,"description":"Required when scope=websites_specific (array of website IDs you own, maximum 500). For scope=mixed_specific at least one of server_ids/website_ids must be non-empty."},{"name":"server_permission_types","type":"object","required":false,"description":"Per-server override map. Maximum 500 entries. A server you do not list here takes the grant's own permission_type."},{"name":"website_permission_types","type":"object","required":false,"description":"Per-website override map. Maximum 500 entries. A website you do not list here takes the grant's own permission_type."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object","email_withheld":"string (present ONLY when the per-address limit stopped the notice. Its value is the reason. Absent on the normal path, so test for presence, not for null)"}},{"name":"delete_permissions","description":"DESTRUCTIVE - Delete one or more permissions you own. Invitees lose access immediately. Every id must be a positive whole number, and one that is not is refused with 422 rather than acted on, so a delete can never land on a permission you did not name.","method":"DELETE","path_template":"/permissions","path_params":[],"body_params":[{"name":"ids","type":"array","required":true,"description":"Permission IDs to delete"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_resend_approval","description":"Re-email the approval link for a permission still in pending status. Throttled to 5 requests per minute, counted against the same shared invitation-email budget as create and update. The budget belongs to the customer account, so the customer's own panel session spends it too. Because the budget is shared, five create or update requests in a minute already use up this endpoint's share. The second, per-address limit described on the create endpoint applies here too, and this endpoint answers it with 429 plus a Retry-After header rather than a success, because a resend that sends nothing has no other purpose.","method":"POST","path_template":"/permissions/{permissionId}/resend-approval","path_params":[{"name":"permissionId","type":"string","required":true,"description":"URL parameter"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"list_available_servers","description":"Option-list of your own servers suitable for picking in a permission's server_ids. Paged: 25 per page by default, up to 100, with page and per_page, and the response carries total and has_more. The list shows only servers this API key can actually share, so it never offers a target the sharing call would then refuse. For a key that was given any website or server access that can mean an empty list: no server in its own list, or a key that does not cover every website, because sharing a server also shares every website on it. A key created with only account scopes gets an empty list, because it cannot share anything: use a key that holds the plain websites scope, and for servers the plain servers scope too.","method":"GET","path_template":"/permissions/available-servers","path_params":[],"body_params":[{"name":"search","type":"string","required":false,"description":"Substring match on server name, hostname, or IPv4"},{"name":"exclude_ids","type":"string","required":false,"description":"Comma-separated server IDs to omit"},{"name":"page","type":"integer","required":false,"description":"Page number, starting at 1. Defaults to 1."},{"name":"per_page","type":"integer","required":false,"description":"Rows per page. Defaults to 25, maximum 100."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"integer","data[].label":"string","data[].hostname":"string|null","data[].ipv4":"string|null","total":"integer (how many rows match in all, before paging)","page":"integer (the page this response holds)","per_page":"integer (how many rows one page holds)","has_more":"boolean (true when more servers matched than this page shows)"}},{"name":"list_available_websites","description":"Option-list of your own websites suitable for picking in a permission's website_ids. Paged: 25 per page by default, up to 100, with page and per_page, and the response carries total and has_more. The list shows only websites this API key can actually share, so it never offers a target the sharing call would then refuse. A key that was given any website or server access but covers no website gets an empty list. A key created with only account scopes gets an empty list, because it cannot share anything: use a key that holds the plain websites scope, and for servers the plain servers scope too.","method":"GET","path_template":"/permissions/available-websites","path_params":[],"body_params":[{"name":"search","type":"string","required":false,"description":"Substring match on website name or domain"},{"name":"exclude_ids","type":"string","required":false,"description":"Comma-separated website IDs to omit"},{"name":"page","type":"integer","required":false,"description":"Page number, starting at 1. Defaults to 1."},{"name":"per_page","type":"integer","required":false,"description":"Rows per page. Defaults to 25, maximum 100."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"integer","data[].label":"string","data[].name":"string|null","data[].domain":"string|null","total":"integer (how many rows match in all, before paging)","page":"integer (the page this response holds)","per_page":"integer (how many rows one page holds)","has_more":"boolean (true when more websites matched than this page shows)"}},{"name":"list_servers","description":"List servers that OTHER clients have shared with you (invitee side). Includes the owner's public profile and your permission type on each resource.","method":"GET","path_template":"/shared/servers","path_params":[],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page (max 100)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"integer","data[].hostname":"string|null","data[].name":"string|null","data[].ipv4":"string|null","data[].ipv6":"string|null","data[].cluster":"string|null","data[].status":"string|null","data[].shared_by":"object|null","data[].shared_by.id":"integer","data[].shared_by.name":"string","data[].shared_by.email":"string","data[].permission_type":"string","meta":"object"}},{"name":"list_websites","description":"List websites that OTHER clients have shared with you. Optional server_id filter limits to one owner's server.","method":"GET","path_template":"/shared/websites","path_params":[],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page (max 100)"},{"name":"server_id","type":"integer","required":false,"description":"Filter to websites on a specific server"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"integer","data[].name":"string|null","data[].domain":"string|null","data[].server_id":"integer","data[].server_name":"string|null","data[].status":"string","data[].shared_by":"object|null","data[].permission_type":"string","meta":"object"}}]},{"name":"coding_agent","scope":"coding_agent","description":"Manage your own AI coding agent on your managed websites. Read-only audit and usage of the sessions you can reach (list, a single session's activity timeline and token or cost detail, and total usage), plus read and update your AI coding agent configuration at the account-wide default and per-website levels and turn AI coding agent on or off per website. The sessions you can reach are the ones on websites you own AND on websites shared with you. On a website shared with you as read-only, command lines and text the agent wrote come back redacted: a shell step may return only the program name, and anything that cannot be reduced safely returns a fixed sentence saying it is hidden. API keys and subscription tokens are managed only in the panel and are never returned or accepted here. Mirrors the panel-side AI coding agent surface.","actions":[{"name":"list_sessions","description":"Paginated list of the managed AI coding agent sessions you can reach, most recent first. That is the sessions on websites you own AND on websites shared with you, not only your own. Supports page or per_page, optional substring search by website domain, and an optional status filter.","method":"GET","path_template":"/coding-agent/sessions","path_params":[],"body_params":[{"name":"page","type":"integer","required":false,"description":"1-based page number (default 1)."},{"name":"per_page","type":"integer","required":false,"description":"Page size (default 20, max 100)."},{"name":"search","type":"string","required":false,"description":"Case-insensitive substring filter on the website domain (LIKE %search%). Empty or missing means no filter."},{"name":"status","type":"string","required":false,"description":"Filter to one session status. One of: running, waiting_for_input, completed, failed, killed. Omit for all statuses."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.items":"array","data.items[].id":"integer","data.items[].domain":"string|null (the website domain the session ran on)","data.items[].model":"string|null","data.items[].status":"string (running|waiting_for_input|completed|failed|killed). waiting_for_input is a live, non-terminal state: the agent is paused waiting for an answer","data.items[].engine":"string (which coding agent ran: claude|codex|gemini)","data.items[].title":"string|null (a name the customer gave the session)","data.items[].pinned":"boolean","data.items[].is_panel_launched":"boolean (true when Proginter started this session, which is what makes it stoppable from the panel. A session started outside the panel, over SSH, cannot be stopped here)","data.items[].is_launching":"boolean (true for the short window between the launch request and the agent reporting in)","data.items[].total_cost_usd":"number (the provider cost on the key that ran the session, not a Proginter charge. On a website shared with you this is the WEBSITE OWNER's spend, not yours)","data.items[].total_tokens":"integer","data.items[].started_at":"string|null","data.items[].last_event_at":"string|null","data.items[].completed_at":"string|null","data.pagination.page":"integer","data.pagination.per_page":"integer","data.pagination.total":"integer","data.pagination.has_more":"boolean"}},{"name":"get_sessions","description":"One session's metadata, token and cost breakdown, and the most-recent activity-timeline events (sub-agents, tool use, workflow runs, shell commands, notifications). A session you cannot reach returns 404 - you can reach the sessions on websites you own and on websites shared with you. HOW MUCH OF EACH EVENT YOU GET DEPENDS ON YOUR ACCESS TO THAT WEBSITE: with full access you read the timeline as recorded, and with read-only access a shell step returns only the program name, and only when the server that ran the agent reported the command in full AND the command starts with a plain program name (a leading SETTING=value is stepped over, anything else is not) AND the whole command is under 1024 characters, while sub-agent names, workflow descriptions and anything else the agent wrote return a fixed sentence saying it is hidden. A server that does not report the command in full yields the hidden sentence for every shell step. Read label_redacted rather than guessing which happened. The example below is a FULL-ACCESS response, so every label is shown as recorded and label_redacted is false throughout. With read-only access the same session returns label_redacted true on the free-text rows, a program name or the fixed hidden sentence in place of each label, request_id and tool_input null, can_answer false, answered_at null, answer_delivered false, and pending_input empty.","method":"GET","path_template":"/coding-agent/sessions/{sessionId}","path_params":[{"name":"sessionId","type":"integer","required":true,"description":"Path parameter. The session id from GET /coding-agent/sessions."}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.session.id":"integer","data.session.domain":"string|null","data.session.model":"string|null","data.session.status":"string (running|waiting_for_input|completed|failed|killed). waiting_for_input is a live, non-terminal state: the agent is paused waiting for an answer","data.session.engine":"string (which coding agent ran: claude|codex|gemini)","data.session.title":"string|null (a name the customer gave the session)","data.session.pinned":"boolean","data.session.is_panel_launched":"boolean (true when Proginter started this session, which is what makes it stoppable from the panel. A session started outside the panel, over SSH, cannot be stopped here)","data.session.is_launching":"boolean (true for the short window between the launch request and the agent reporting in)","data.session.total_cost_usd":"number","data.session.total_input_tokens":"integer","data.session.total_output_tokens":"integer","data.session.total_cache_read_tokens":"integer","data.session.total_cache_create_tokens":"integer","data.session.total_tokens":"integer","data.session.started_at":"string|null","data.session.last_event_at":"string|null","data.session.completed_at":"string|null","data.session.failure_reason":"string|null (null when the session did not fail). VIEWER-DEPENDENT: a reason Proginter wrote is returned to everyone, and a reason the server that ran the agent wrote is returned only with full access to the website. Without it you get a fixed sentence telling you to ask someone who has it - never null, so 'failed with no reason' always means no reason exists","data.session.pending_input":"object (what you have sent that the agent has not collected yet). VIEWER-DEPENDENT: only a caller with full access to the website can send input, so with read-only access this always reads as empty rather than reporting someone else's queue","data.session.pending_input.queued":"integer (0 when nothing is waiting). Counts only what can still arrive, so a finished session always reads 0 here","data.session.pending_input.oldest_at":"string|null (when the oldest uncollected item was sent). Null on a finished session, because nothing is waiting any more","data.session.pending_input.never_delivered":"integer (how much of what you sent the agent never received). Always 0 while the session is running. On a finished session, anything above 0 means the session ended before the server collected those items and they can no longer be delivered","data.events":"array (newest first, capped at the most-recent 200)","data.events[].seq":"integer","data.events[].category":"string (subagent_start|subagent_stop|tool_use|workflow|shell|notification)","data.events[].label":"string (short, truncated activity label). Its CONTENT depends on your access to the website - see this endpoint's description - and an empty string means the event carried no text at all","data.events[].label_redacted":"boolean (true when this label is a reduced or withheld version of what was recorded, rather than the whole thing). Use this instead of pattern-matching the label text","data.events[].at":"string|null","data.events[].approval":"object, present ONLY on an event where the agent paused for permission to use a tool","data.events[].approval.answerable":"boolean (whether this approval CARRIES what an answer needs: the agent supplied a usable correlation id, and the approval belongs to the session itself rather than to one of its sub-agents). It is a property of the approval alone and does NOT mean the session can still take a decision - a finished session, a session Proginter did not start, an approval the agent has already moved past, and one that was already answered all still read true here. For whether a decision would be accepted right now, read can_answer together with session.status and session.is_panel_launched","data.events[].approval.can_answer":"boolean (whether YOU would be allowed to answer it: you hold full access to the website, the approval is answerable, and it is the session's current one). Read it as WHO the agent is waiting for. Answering is NOT available over this API - these coding-agent endpoints are read-only and there is no input endpoint yet - so treat this as a status flag, and if you build a control on it, gate it on this rather than on answerable or on request_id being non-null","data.events[].approval.request_id":"string|null (the correlation id to echo back when answering). Present when you hold full access to the website AND the approval's shape is answerable. A non-null value does NOT mean a decision would be accepted right now - it is historical correlation metadata, and it is still returned for an approval that is already answered or that the agent has moved past. Gate any action on can_answer, never on this being non-null","data.events[].approval.tool":"string|null (the tool being requested, for example Bash). Null when the recorded name is not a plain identifier","data.events[].approval.tool_input":"object|null (what the tool was asked to do). Returned only with full access to the website","data.events[].approval.input_complete":"boolean (false when the request was too large to carry intact, so tool_input is a preview and the original cannot be echoed back)","data.events[].approval.answered":"boolean (true once a decision is queued on the panel)","data.events[].approval.answered_at":"string|null (ISO 8601, when the decision was recorded). Null unless you hold full access to the website - on a website shared with you as read-only you get the answered flag but not its timing, because that is the website owner's activity","data.events[].approval.answer_delivered":"boolean (true once the server that runs the agent has actually collected the decision. answered means we hold it, this means it is on its way). Always false without full access to the website, for the same reason answered_at is null there","data.events_total":"integer (total events on the session)","data.events_shown":"integer (events returned in this response)"}},{"name":"get_usage","description":"Total AI coding agent usage aggregated over every session you can reach: cost, tokens, session count, a per-status count, and a per-website breakdown (top 20 by cost). NOT necessarily your own spend. The sessions you can reach include those on websites shared with you, and a session there ran on the WEBSITE OWNER's key - so on any shared website the total includes their spend, not yours. Read the per-website breakdown rather than presenting the single total as a personal figure. Spend is the provider cost on whichever key ran the session (bring-your-own key), never a Proginter charge.","method":"GET","path_template":"/coding-agent/usage","path_params":[],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.total_sessions":"integer","data.total_cost_usd":"number","data.total_tokens":"integer","data.tokens.input":"integer","data.tokens.output":"integer","data.tokens.cache_read":"integer","data.tokens.cache_create":"integer","data.by_status.running":"integer","data.by_status.waiting_for_input":"integer","data.by_status.completed":"integer","data.by_status.failed":"integer","data.by_status.killed":"integer","data.by_website":"array (top 20 websites by cost)","data.by_website[].domain":"string|null","data.by_website[].sessions":"integer","data.by_website[].cost_usd":"number","data.by_website[].tokens":"integer","data.by_website_total":"integer (how many websites the totals above actually cover)","data.by_website_has_more":"boolean (true when by_website is capped and its rows do not add up to the total)"}}]},{"name":"coding_agent_config","scope":"coding_agent_config","description":"Manage your own AI coding agent on your managed websites. Read-only audit and usage of the sessions you can reach (list, a single session's activity timeline and token or cost detail, and total usage), plus read and update your AI coding agent configuration at the account-wide default and per-website levels and turn AI coding agent on or off per website. The sessions you can reach are the ones on websites you own AND on websites shared with you. On a website shared with you as read-only, command lines and text the agent wrote come back redacted: a shell step may return only the program name, and anything that cannot be reduced safely returns a fixed sentence saying it is hidden. API keys and subscription tokens are managed only in the panel and are never returned or accepted here. Mirrors the panel-side AI coding agent surface.","actions":[{"name":"get_config","description":"Read your account-wide default AI coding agent configuration and its effective values. API keys and subscription tokens are managed in the panel and are never returned here.","method":"GET","path_template":"/coding-agent/config","path_params":[],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.scope":"string (account_default)","data.config.model":"string|null","data.config.instructions_md":"string|null","data.config.settings":"object|null","data.config.skills":"object|null","data.config.agents":"object|null","data.config.commands":"object|null","data.config.hooks":"object|null","data.config.plugins":"object|null","data.config.mcp_servers":"object|null","data.effective":"object (the in-force value per section, excludes the automatic Proginter MCP)"}},{"name":"update_config","description":"DESTRUCTIVE - Update your account-wide default AI coding agent configuration. Use set to override sections and clear to empty them. A section sent as null inherits the default. The change is deployed to every website where AI coding agent is enabled.","method":"PUT","path_template":"/coding-agent/config","path_params":[],"body_params":[{"name":"set","type":"object","required":false,"description":"A map of section name to value. Sections: model (string), instructions_md (string), settings, skills, agents, commands, hooks, plugins, mcp_servers (objects). A null value inherits the account default. settings, hooks, and mcp_servers must be objects, not lists. Each section and the total payload are capped at 120 KB."},{"name":"clear","type":"array","required":false,"description":"A list of section names to empty: model, instructions_md, settings, skills, agents, commands, hooks, plugins, mcp_servers. A section cannot appear in both set and clear."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","data.scope":"string (account_default)","data.config":"object (the 9 stored sections after the update, null = inherit)","data.effective":"object (the in-force value per section, excludes the automatic Proginter MCP)"}},{"name":"websites_get_config","description":"Read one website's AI coding agent override configuration, its enable state, and the effective values merged over your account default. Requires full access to the website.","method":"GET","path_template":"/coding-agent/websites/{website}/config","path_params":[{"name":"website","type":"integer","required":true,"description":"Path parameter. The website id."}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.scope":"string (website)","data.website_id":"integer","data.config.model":"string|null (null inherits the account default)","data.config.instructions_md":"string|null","data.config.settings":"object|null","data.config.skills":"object|null","data.config.agents":"object|null","data.config.commands":"object|null","data.config.hooks":"object|null","data.config.plugins":"object|null","data.config.mcp_servers":"object|null","data.config.enabled":"boolean","data.config.update_channel":"string|null (stable|latest)","data.config.version_pin":"string|null","data.config.can_enable":"boolean (false when this website's server cannot run AI work at all - read this before calling enable)","data.config.cannot_enable_reason":"string|null (the plain-words explanation when can_enable is false, safe to show a user)","data.config.coding_agent_minimums":"object (what a server must have before the AI coding agent can be switched on for a website, returned whether or not this server meets it)","data.config.coding_agent_minimums.cores":"integer (minimum CPU cores)","data.config.coding_agent_minimums.memory_gb":"integer (minimum memory in GB)","data.is_owner":"boolean (false for an invitee managing this website on the owner's behalf)","data.effective":"object (the merged effective value per section, excludes the automatic Proginter MCP)"}},{"name":"websites_update_config","description":"DESTRUCTIVE - Update one website's AI coding agent override configuration. Use set to override sections and clear to empty them. A section sent as null inherits the account default. Requires full access to the website. The change is deployed when AI coding agent is enabled on the website.","method":"PUT","path_template":"/coding-agent/websites/{website}/config","path_params":[{"name":"website","type":"integer","required":true,"description":"Path parameter. The website id."}],"body_params":[{"name":"set","type":"object","required":false,"description":"A map of section name to value. Sections: model (string), instructions_md (string), settings, skills, agents, commands, hooks, plugins, mcp_servers (objects). A null value inherits the account default. settings, hooks, and mcp_servers must be objects, not lists. Each section and the total payload are capped at 120 KB."},{"name":"clear","type":"array","required":false,"description":"A list of section names to empty: model, instructions_md, settings, skills, agents, commands, hooks, plugins, mcp_servers. A section cannot appear in both set and clear."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","data.scope":"string (website)","data.website_id":"integer","data.config":"object (the stored override sections plus enabled, update_channel, version_pin, can_enable, cannot_enable_reason, coding_agent_minimums)","data.config.can_enable":"boolean (false when this website's server cannot run AI work at all - read this before calling enable)","data.config.cannot_enable_reason":"string|null (the plain-words explanation when can_enable is false, safe to show a user)","data.config.coding_agent_minimums":"object (what a server must have before the AI coding agent can be switched on for a website, returned whether or not this server meets it)","data.config.coding_agent_minimums.cores":"integer (minimum CPU cores)","data.config.coding_agent_minimums.memory_gb":"integer (minimum memory in GB)","data.effective":"object (the merged effective value per section, excludes the automatic Proginter MCP)"}},{"name":"create_enable","description":"CONDITIONALLY DESTRUCTIVE - Turn AI coding agent on or off for a website and choose its update channel or a pinned version. Turning it off removes the installed AI coding agent but keeps the saved configuration, which is used again only if it can be turned back on. Requires full access to the website. Turning it ON also requires the website to sit on a server dedicated to one customer with at least 4 processor cores and 8 GB of memory: a shared or undersized server is refused with HTTP 400 and a plain-words message you can show a user. Read data.config.can_enable on the website config endpoint to find this out before calling. Turning it OFF is never refused for this reason, but on a server that does not meet the requirement it cannot be turned back on afterwards, so tell the user before you call it.","method":"POST","path_template":"/coding-agent/websites/{website}/enable","path_params":[{"name":"website","type":"integer","required":true,"description":"Path parameter. The website id."}],"body_params":[{"name":"enabled","type":"boolean","required":true,"description":"True installs and turns on AI coding agent for the website, false removes it. True is refused with HTTP 400 when the website's server is shared with other customers or is below 4 cores / 8 GB. False is never refused, but on such a server it cannot be set back to true afterwards."},{"name":"update_channel","type":"string","required":false,"description":"The auto-update channel: stable or latest. Omit to leave it unchanged."},{"name":"version_pin","type":"string","required":false,"description":"Pin an exact version like 2.1.195. Send null to remove a pin so the channel drives the version."}],"requires_confirmation":false,"prerequisites":[],"requires_confirmation_when":{"enabled":false},"response_structure":{"success":"boolean","data.website_id":"integer","data.config":"object (the website config with its enable and install state, plus can_enable, cannot_enable_reason and coding_agent_minimums)","data.config.can_enable":"boolean (false when this website's server cannot run AI work at all - read this before calling enable)","data.config.cannot_enable_reason":"string|null (the plain-words explanation when can_enable is false, safe to show a user)","data.config.coding_agent_minimums":"object (what a server must have before the AI coding agent can be switched on for a website, returned whether or not this server meets it)","data.config.coding_agent_minimums.cores":"integer (minimum CPU cores)","data.config.coding_agent_minimums.memory_gb":"integer (minimum memory in GB)"}}]},{"name":"security_scans","scope":"security_scans","description":"Trigger security scans, apply AI-driven fixes, view findings and scan history, and roll back applied fixes. Mirrors the panel-side Security Scans surface.","actions":[{"name":"list_security_scans","description":"Paginated scan history for the authenticated client, most recent first. Supports page/per_page and substring search by domain.","method":"GET","path_template":"/security-scans","path_params":[],"body_params":[{"name":"page","type":"integer","required":false,"description":"1-based page number (default 1)."},{"name":"per_page","type":"integer","required":false,"description":"Page size (default 20, max 100)."},{"name":"search","type":"string","required":false,"description":"Case-insensitive substring filter on domain (LIKE %search%). Empty/missing = no filter."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.items":"array","data.items[].id":"integer","data.items[].domain":"string","data.items[].mode":"string (cold|agent|internal)","data.items[].status":"string (queued|running|awaiting-credentials|completed|failed|cancelled)","data.items[].findings_count":"integer","data.items[].created_at":"string","data.items[].completed_at":"string|null","data.items[].pdf_status":"string|null","data.items[].three_ds_state":"string|null (card-verification lifecycle of a card-funded scan, null when no card verification applies. awaiting_challenge means the scan waits for the customer to verify the payment with their bank in the control panel)","data.pagination.page":"integer","data.pagination.per_page":"integer","data.pagination.total":"integer","data.pagination.has_more":"boolean"}},{"name":"create_security_scans","description":"Start a new security scan. Cold scans are free. Agent and Internal scans are paid - funded from balance when sufficient, otherwise from a saved credit card via Tranzila preauth. Paid modes require at least one active service on the account: with no active server or hosting account, they return HTTP 403 and your first purchase must be made in the control panel, where your card is verified. Free cold scans are unaffected. Throttled to 6 requests per minute.","method":"POST","path_template":"/security-scans","path_params":[],"body_params":[{"name":"mode","type":"string","required":true,"description":"Scan mode: 'cold' (free, public surface), 'agent' (paid, walks logged-in surfaces on external domains), or 'internal' (paid, hosted-only, AI-applied fixes)."},{"name":"target_type","type":"string","required":true,"description":"Target type: 'website' for a hosted website (target_id = Website id) or 'external' for a verified external domain (target_id = SecurityScanVerification id)."},{"name":"target_id","type":"integer","required":true,"description":"The id matching target_type (positive integer)."},{"name":"auto_apply_fixes","type":"boolean","required":false,"description":"If true, tier-1 fixes are auto-applied on Internal scan completion. Defaults to false. Only valid when mode=internal - Cold/Agent return 422 if this flag is true."},{"name":"use_balance","type":"boolean","required":false,"description":"If true (default), the scan is funded from the client's panel balance when sufficient. When false OR balance is insufficient, the tranzila path is taken using the explicit payment_method_id or the client's default saved card."},{"name":"payment_method_id","type":"integer","required":false,"description":"Optional id of a saved credit-card payment method (from /payment-methods) to charge if the tranzila path is taken. When omitted and the tranzila path is needed, the client's default payment method is used. Returns 422 if the id does not belong to the authenticated client OR balance is insufficient and no usable card is on file."},{"name":"auth_creds","type":"object","required":false,"description":"Optional upfront login credentials for an agent-mode scan that must get past a login wall. Only supported when mode is agent. An object with: login_url (required, the login form URL), username (required), password (required), cookies (optional, a raw cookie string), headers (optional, raw header lines). When omitted, the scan does not authenticate."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.id":"integer","data.domain":"string","data.mode":"string","data.status":"string","data.findings_count":"integer","data.findings_by_severity":"object","data.findings_by_severity.critical":"integer","data.findings_by_severity.high":"integer","data.findings_by_severity.medium":"integer","data.findings_by_severity.low":"integer","data.findings_by_severity.info":"integer","data.started_at":"string|null","data.completed_at":"string|null","data.cancelled_at":"string|null","data.created_at":"string|null","data.failed_reason":"string|null","data.pdf_status":"string|null","data.auto_apply_fixes":"boolean","data.three_ds_state":"string|null (card-verification progress for a paid scan funded by card: awaiting_challenge means the bank asked the customer to verify in the panel, challenge_returned means the verification is being finished, completed or failed are terminal. null when no card verification applies. Manual: verification itself always happens in the control panel, never over the API)"}},{"name":"get_security_scans","description":"Get the current status snapshot of a single scan, including severity-bucketed finding counts.","method":"GET","path_template":"/security-scans/{scanId}","path_params":[{"name":"scanId","type":"integer","required":true,"description":"Panel scan id (URL parameter)."}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.id":"integer","data.domain":"string","data.mode":"string","data.status":"string","data.findings_count":"integer","data.findings_by_severity":"object","data.findings_by_severity.critical":"integer","data.findings_by_severity.high":"integer","data.findings_by_severity.medium":"integer","data.findings_by_severity.low":"integer","data.findings_by_severity.info":"integer","data.started_at":"string|null","data.completed_at":"string|null","data.cancelled_at":"string|null","data.created_at":"string","data.failed_reason":"string|null","data.pdf_status":"string|null","data.auto_apply_fixes":"boolean","data.three_ds_state":"string|null (card-verification progress for a paid scan funded by card: awaiting_challenge means the bank asked the customer to verify in the panel, challenge_returned means the verification is being finished, completed or failed are terminal. null when no card verification applies. Manual: verification itself always happens in the control panel, never over the API)"}},{"name":"list_external_domains","description":"Paginated list of the client's external domain verifications, with three-state verification_status enum and last-scan metadata. Supports page/per_page and substring search by domain.","method":"GET","path_template":"/security-scans/external-domains","path_params":[],"body_params":[{"name":"page","type":"integer","required":false,"description":"1-based page number (default 1)."},{"name":"per_page","type":"integer","required":false,"description":"Page size (default 20, max 100)."},{"name":"search","type":"string","required":false,"description":"Case-insensitive substring filter on domain (LIKE %search%). Empty/missing = no filter."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.items":"array","data.items[].verification_id":"integer","data.items[].domain":"string","data.items[].verified_at":"string|null","data.items[].invalidated_at":"string|null","data.items[].verification_status":"string (never_verified|verified|invalidated)","data.items[].last_check_status":"string|null","data.items[].last_scan_at":"string|null","data.items[].last_scan_id":"integer|null","data.items[].last_findings_count":"integer|null","data.pagination.page":"integer","data.pagination.per_page":"integer","data.pagination.total":"integer","data.pagination.has_more":"boolean"}},{"name":"list_findings","description":"Paginated findings of a completed scan plus the most-recent fix session. For non-terminal scans, findings is an empty array. Supports page/per_page and substring search on title/category/severity/type.","method":"GET","path_template":"/security-scans/{scanId}/findings","path_params":[{"name":"scanId","type":"integer","required":true,"description":"Panel scan id (URL parameter)."}],"body_params":[{"name":"page","type":"integer","required":false,"description":"1-based page number (default 1)."},{"name":"per_page","type":"integer","required":false,"description":"Page size (default 20, max 100)."},{"name":"search","type":"string","required":false,"description":"Case-insensitive substring filter on title/category/severity/type. Empty/missing = no filter."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.scan":"object","data.findings":"array","data.findings[].finding_id":"string|null","data.findings[].type":"string","data.findings[].severity":"string (critical|high|medium|low|info)","data.findings[].category":"string|null","data.findings[].title":"string","data.findings[].plain_explanation":"string|null","data.findings[].remediation":"string|null","data.findings[].evidence":"object|null","data.findings[].tier":"integer (1 = auto-fixable, 2 = static remediation text only)","data.findings[].side_effect_warning":"string|null","data.findings[].fix_id":"integer|null","data.findings[].fix_status":"string|null","data.pagination.page":"integer","data.pagination.per_page":"integer","data.pagination.total":"integer","data.pagination.has_more":"boolean","data.session":"object|null (latest fix session for the scan, same fields as the apply-fixes response data object)","data.session.three_ds_state":"string|null (card-verification lifecycle of a card-funded session, null when no card verification applies. awaiting_challenge means the session waits for the customer to verify the payment with their bank in the control panel)","data.session.failure_reason":"string|null (customer-safe reason, present only when the card verification failed)"}},{"name":"create_apply_fixes","description":"DESTRUCTIVE - Apply one or more fixes from a completed Internal scan. Each fix is billed individually only when actually applied. Requires at least one active service on the account: with no active server or hosting account, this endpoint returns HTTP 403 and your first purchase must be made in the control panel, where your card is verified. Throttled to 6 requests per minute.","method":"POST","path_template":"/security-scans/{scanId}/apply-fixes","path_params":[{"name":"scanId","type":"integer","required":true,"description":"Panel scan id (URL parameter)."}],"body_params":[{"name":"fix_ids","type":"array","required":true,"description":"Array of panel fix ids (1-200 unique elements, duplicate ids return 422). Get the available fix ids from /security-scans/{scanId}/findings."},{"name":"payment_method_id","type":"integer","required":false,"description":"Optional id of a saved credit-card payment method to charge for the applied fixes. When omitted, the client's default saved card is used."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","data.session_id":"integer","data.scan_id":"integer","data.status":"string","data.preauth_amount":"number|null","data.captured_amount":"number|null","data.started_at":"string|null","data.completed_at":"string|null","data.three_ds_state":"string|null (card-verification lifecycle of a card-funded session, null when no card verification applies. awaiting_challenge means the session waits for the customer to verify the payment with their bank in the control panel)","data.failure_reason":"string|null (customer-safe reason, present only when the card verification failed)"}},{"name":"create_rollback","description":"DESTRUCTIVE - Roll back an applied fix. Requires explicit acknowledged_no_refund=true. The original fix charge is NOT refunded. Throttled to 6 requests per minute.","method":"POST","path_template":"/security-scans/fixes/{fixId}/rollback","path_params":[{"name":"fixId","type":"integer","required":true,"description":"Panel fix id (URL parameter)."}],"body_params":[{"name":"acknowledged_no_refund","type":"boolean","required":true,"description":"Must be true. Client acknowledges that rolling back this fix does NOT refund the original fix charge."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","data.fix_id":"integer","data.status":"string"}}]},{"name":"servers","scope":"servers","description":"Manage your servers including power controls, monitoring, blocked IPs, IP lists, and MySQL details.","actions":[{"name":"list_servers","description":"Get a paginated list of your servers with cluster, IP, ISP, and location information. If your API key was limited to particular resources, the list contains only the servers that key covers, and the paging totals count only those. A key limited to websites but given no servers therefore sees an empty list, which matches the per-server calls it would be refused. A key its owner never limited to any resource sees everything, as before.","method":"GET","path_template":"/servers","path_params":[],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number for pagination"},{"name":"per_page","type":"integer","required":false,"description":"Number of items per page (max 100)"},{"name":"order_by","type":"string","required":false,"description":"Column to order by. One of: name, server.cpu, price, next_cycle_at, status. Default: name. When the list includes a server shared with you by another account, an order by price or next_cycle_at ranks your own servers only and the shared ones follow as one block."},{"name":"order_direction","type":"string","required":false,"description":"Order direction: asc or desc. Default: asc."},{"name":"search","type":"string","required":false,"description":"Search servers by name, hostname, or IP address"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"integer","data[].name":"string","data[].status":"string","data[].price":"float|null (owner billing: null for servers shared with you)","data[].currency":"string|null (owner billing: null for servers shared with you)","data[].vat_rate":"float","data[].next_cycle_at":"string|null (owner billing: null for servers shared with you)","data[].overdue":"boolean|null (owner billing: null for servers shared with you)","data[].grace_until":"string|null (owner billing: null for servers shared with you. ISO 8601 datetime, set only while an operator payment extension is holding this server online, otherwise null)","data[].is_graced":"boolean|null (owner billing: null for servers shared with you. True while an active payment extension is protecting this server)","data[].server":"object|null","data[].server.id":"integer","data[].server.ipv4":"object|null","data[].server.ipv6":"object|null","data[].server.cluster":"object|null","data[].server.isp":"object|null","data[].server.city":"object|null","meta":"object"}},{"name":"get_manage","description":"Get detailed information about a specific server.","method":"GET","path_template":"/servers/{server}/manage","path_params":[{"name":"server","type":"integer","required":true,"description":"Server ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.id":"integer","data.hostname":"string|null","data.vm_status":"string","data.is_available":"boolean","data.is_owner":"boolean","data.permission_type":"string|null","data.unread_notifications":"integer","data.has_issues":"boolean (narrow legacy signal - true while an open support issue exists for this server. Kept for compatibility and NOT the overall health voice - read data.health_verdict for that)","data.has_critical_issues":"boolean (narrow legacy signal - true while an open critical support issue exists. Kept for compatibility - read data.health_verdict for the overall health voice)","data.health_verdict":"object (the unified server health verdict, judged from recorded incidents and open issues. Read it together with is_available: the verdict does not measure live reachability, and while a server is unreachable it reflects only the open-issue state. health_verdict.status: one of healthy, needs_attention, critical. health_verdict.reason: string|null - one of critical_issues, recurring_capacity, recurring_traffic_cpu, recurring_incidents, open_issues, null when healthy. health_verdict.recommendation: one of none, ram, cpu, both - both means recurring load patterns point at memory and processing power together. health_verdict.window_days: integer, the window in days the recurring patterns are judged over)","data.overdue":"boolean|null (owner billing: null for a server shared with you. True while this server has an unpaid overdue balance)","data.grace_until":"string|null (owner billing: null for a server shared with you. ISO 8601 datetime, set only while an operator payment extension is holding this server online)","data.is_graced":"boolean|null (owner billing: null for a server shared with you. True while an active payment extension is protecting this server)","data.ipv4":"object|null","data.ipv6":"object|null","data.cluster":"object|null"}},{"name":"create_start","description":"Start a stopped server.","method":"POST","path_template":"/servers/start/{server}","path_params":[{"name":"server","type":"integer","required":true,"description":"Server ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_reboot","description":"Reboot a running server.","method":"POST","path_template":"/servers/reboot/{server}","path_params":[{"name":"server","type":"integer","required":true,"description":"Server ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_shutdown","description":"Shutdown a running server.","method":"POST","path_template":"/servers/shutdown/{server}","path_params":[{"name":"server","type":"integer","required":true,"description":"Server ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_status","description":"Get the current status of a server (running, stopped, etc.).","method":"GET","path_template":"/servers/status/{server}","path_params":[{"name":"server","type":"integer","required":true,"description":"Server ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.status":"string"}},{"name":"get_usage","description":"Get current resource usage of a server (CPU, memory, disk).","method":"GET","path_template":"/servers/usage/{server}","path_params":[{"name":"server","type":"integer","required":true,"description":"Server ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.current":"object","data.current.cpu":"float|null","data.current.memory":"integer|null","data.current.disk":"integer|null","data.limits":"object","data.limits.cpu":"integer|null","data.limits.memory":"integer|null","data.limits.disk":"integer|null"}},{"name":"list_resources","description":"Get server resource allocation and usage details.","method":"GET","path_template":"/servers/{server}/resources","path_params":[{"name":"server","type":"integer","required":true,"description":"Server ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.websites":"array","data.websites[].id":"integer|string","data.websites[].domain":"string","data.websites[].username":"string","data.websites[].cpu":"float","data.websites[].memory":"float","data.websites[].disk":"float","data.websites[].cpu_limit":"float","data.websites[].memory_limit":"float","data.websites[].disk_limit":"float"}},{"name":"list_largest_files","description":"List the largest files (over 1 MB) across your websites on this server, sorted from biggest to smallest (top 50). Scoped to your own websites on the server. Only the owning domain is returned, never the system username.","method":"GET","path_template":"/servers/{server}/largest-files","path_params":[{"name":"server","type":"integer","required":true,"description":"Server ID (URL parameter)"}],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number (default 1)"},{"name":"per_page","type":"integer","required":false,"description":"Results per page, 1 to 100 (default 50)"},{"name":"search","type":"string","required":false,"description":"Filter rows whose file path or domain contains this text"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].path":"string","data[].size_mb":"integer","data[].domain":"string","total":"integer","page":"integer","per_page":"integer","has_more":"boolean"}},{"name":"get_history","description":"Get historical resource usage data for graphing.","method":"GET","path_template":"/servers/{server}/resources/history","path_params":[{"name":"server","type":"integer","required":true,"description":"Server ID (URL parameter)"}],"body_params":[{"name":"period","type":"string","required":false,"description":"Time period for history"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.timeframe":"string","data.type":"string","data.history":"array","data.history[].time":"integer|null","data.history[].cpu":"float|null","data.history[].cpu_cores":"integer|null","data.history[].memory":"integer|null","data.history[].memory_max":"integer|null","data.history[].disk":"integer|null","data.history[].disk_max":"integer|null","data.history[].net_in":"float|null","data.history[].net_out":"float|null","data.history[].disk_read":"float|null","data.history[].disk_write":"float|null"}},{"name":"get_resources_history","description":"Get server resources history.","method":"GET","path_template":"/servers/resources-history/{server}","path_params":[{"name":"server","type":"integer","required":true,"description":"Server ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.timeframe":"string","data.type":"string","data.history":"array","data.history[].time":"integer|null","data.history[].cpu":"float|null","data.history[].cpu_cores":"integer|null","data.history[].memory":"integer|null","data.history[].memory_max":"integer|null","data.history[].disk":"integer|null","data.history[].disk_max":"integer|null","data.history[].net_in":"float|null","data.history[].net_out":"float|null","data.history[].disk_read":"float|null","data.history[].disk_write":"float|null"}},{"name":"enable_safe_mode","description":"Get server safe mode status and settings.","method":"GET","path_template":"/servers/{server}/safe-mode","path_params":[{"name":"server","type":"integer","required":true,"description":"Server ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.enabled":"boolean","data.whitelist":"array"}},{"name":"servers_enable_safe_mode","description":"DESTRUCTIVE - Update server safe mode settings.","method":"POST","path_template":"/servers/{server}/safe-mode","path_params":[{"name":"server","type":"integer","required":true,"description":"Server ID (URL parameter)"}],"body_params":[{"name":"enabled","type":"boolean","required":true,"description":"Enable or disable safe mode"},{"name":"whitelist","type":"array","required":false,"description":"Array of whitelisted IPs/CIDRs"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_blocked_ips","description":"Get a list of blocked IP addresses for a server.","method":"GET","path_template":"/servers/blocked-ips/{server}","path_params":[{"name":"server","type":"integer","required":true,"description":"Server ID (URL parameter)"}],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number for pagination"},{"name":"per_page","type":"integer","required":false,"description":"Number of items per page"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"string","data[].ip":"string","data[].blocked_at":"integer|null","data[].type":"string","data[].reason":"string","data[].expires_at":"integer|null","meta":"object"}},{"name":"create_blocked_ips","description":"Block an IP address on the server firewall.","method":"POST","path_template":"/servers/blocked-ips/{server}","path_params":[{"name":"server","type":"integer","required":true,"description":"Server ID (URL parameter)"}],"body_params":[{"name":"ip","type":"string","required":true,"description":"IP address to block"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_blocked_ips","description":"DESTRUCTIVE - Unblock IP addresses on the server firewall. An entry that is a single value but not a valid IP address or CIDR range is IGNORED rather than rejected, and the call still answers 200 with success true, so read the message rather than the status: it states how many unblocks were REQUESTED, not how many addresses were actually unblocked, and it names any entries it ignored. The node call returns nothing, so an address that was never blocked is counted the same as one that was. Read the blocked-IP list back to confirm. A count of 0 means nothing was acted on. An entry sent as a list rather than a single value is refused with 422 naming the field.","method":"DELETE","path_template":"/servers/blocked-ips/{server}","path_params":[{"name":"server","type":"integer","required":true,"description":"Server ID (URL parameter)"}],"body_params":[{"name":"ids","type":"array","required":true,"description":"Array of IP addresses to unblock. Maximum 500 per call, because each one is applied on the server individually."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_ips_lists","description":"Get IP lists configured on the server.","method":"GET","path_template":"/servers/ips-lists/{server}","path_params":[{"name":"server","type":"integer","required":true,"description":"Server ID (URL parameter)"}],"body_params":[{"name":"default","type":"boolean","required":false,"description":"Include default lists"},{"name":"page","type":"integer","required":false,"description":"Page number for pagination"},{"name":"per_page","type":"integer","required":false,"description":"Number of items per page"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"string","data[].name":"string","meta":"object"}},{"name":"create_ips_lists","description":"Create a new IP list on the server.","method":"POST","path_template":"/servers/ips-lists/{server}","path_params":[{"name":"server","type":"integer","required":true,"description":"Server ID (URL parameter)"}],"body_params":[{"name":"name","type":"string","required":true,"description":"Name for the IP list"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object","data.id":"string","data.name":"string"}},{"name":"update_ips_lists","description":"Rename an IP list.","method":"PUT","path_template":"/servers/ips-lists/{server}/{name}","path_params":[{"name":"server","type":"integer","required":true,"description":"Server ID (URL parameter)"},{"name":"name","type":"string","required":true,"description":"New name for the list (body)"}],"body_params":[{"name":"currentName","type":"string","required":true,"description":"Current list name (URL parameter)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object","data.id":"string","data.name":"string"}},{"name":"delete_ips_lists","description":"DESTRUCTIVE - Delete IP lists from the server.","method":"DELETE","path_template":"/servers/ips-lists/{server}","path_params":[{"name":"server","type":"integer","required":true,"description":"Server ID (URL parameter)"}],"body_params":[{"name":"ids","type":"array","required":true,"description":"Array of list names to delete. Maximum 500 per call, because each one is applied on the server individually."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_ips","description":"Get IP addresses in a specific IP list.","method":"GET","path_template":"/servers/ips-lists/ips/{server}/{ipsList}","path_params":[{"name":"server","type":"integer","required":true,"description":"Server ID (URL parameter)"},{"name":"ipsList","type":"string","required":true,"description":"IP list name (URL parameter)"}],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number for pagination"},{"name":"per_page","type":"integer","required":false,"description":"Number of items per page"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"string","data[].ip":"string","meta":"object"}},{"name":"create_ips","description":"Add an IP address to an IP list.","method":"POST","path_template":"/servers/ips-lists/ips/{server}/{ipsList}","path_params":[{"name":"server","type":"integer","required":true,"description":"Server ID (URL parameter)"},{"name":"ipsList","type":"string","required":true,"description":"IP list name (URL parameter)"}],"body_params":[{"name":"ip","type":"string","required":true,"description":"IP address to add"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object","data.id":"string","data.ip":"string"}},{"name":"update_ips","description":"Update an IP address in an IP list.","method":"PUT","path_template":"/servers/ips-lists/ips/{server}/{ipsList}/{ip}","path_params":[{"name":"server","type":"integer","required":true,"description":"Server ID (URL parameter)"},{"name":"ipsList","type":"string","required":true,"description":"IP list name (URL parameter)"},{"name":"ip","type":"string","required":true,"description":"New IP address (body)"}],"body_params":[{"name":"currentIp","type":"string","required":true,"description":"Current IP (URL parameter)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object","data.id":"string","data.ip":"string"}},{"name":"delete_ips","description":"DESTRUCTIVE - Remove IPs from an IP list. A text entry that is not a valid IP address or CIDR range is IGNORED rather than rejected, and the call still answers 200 with success true, so read the message rather than the status: it states how many removals were REQUESTED, not how many addresses were actually removed, and it names any entries it ignored. The node call returns nothing, so an address that was not in the list is counted the same as one that was. Read the list back to confirm. A count of 0 means nothing was acted on. An entry that is not text is refused with 422 naming the field, so a mixed batch is rejected whole rather than partly ignored.","method":"DELETE","path_template":"/servers/ips-lists/ips/{server}/{ipsList}","path_params":[{"name":"server","type":"integer","required":true,"description":"Server ID (URL parameter)"},{"name":"ipsList","type":"string","required":true,"description":"IP list name (URL parameter)"}],"body_params":[{"name":"ids","type":"array","required":true,"description":"Array of IPs to remove. Maximum 500 per call, because each one is applied on the server individually."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_monitor","description":"Get server monitoring settings.","method":"GET","path_template":"/servers/monitor/{server}","path_params":[{"name":"server","type":"integer","required":true,"description":"Server ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.registered":"boolean","data.enabled":"boolean","data.emails":"array","data.auto_reboot":"boolean","data.auto_reboot_immediate":"boolean","data.auto_reboot_tries":"integer|null"}},{"name":"create_monitor","description":"Update server monitoring settings.","method":"POST","path_template":"/servers/monitor/{server}","path_params":[{"name":"server","type":"integer","required":true,"description":"Server ID (URL parameter)"}],"body_params":[{"name":"enable","type":"boolean","required":false,"description":"Enable or disable monitoring. REQUIRED, but satisfied by EITHER this name or `enabled` - which is why it is not marked required here, and the distinction matters for generated clients. The matching GET returns this value under the name `enabled`, so a caller round-tripping the GET response sends `enabled` and no `enable` at all. Marking this one required made that object fail a generated request validator before it ever reached the endpoint, so the documented round-trip was impossible for exactly the callers who follow the documentation. Send either name. If you send both, `enable` wins. Sending neither is still rejected."},{"name":"enabled","type":"boolean","required":false,"description":"Accepted spelling of `enable`, and the name the matching GET uses in its response, so a GET result can be sent back unchanged. Exactly one of `enable` or `enabled` must be present. If both are sent, `enable` wins."},{"name":"emails","type":"array","required":true,"description":"Required. Array of email addresses for alerts. At least one is required, and at most twenty. Each must be a valid address, and duplicates are rejected."},{"name":"auto_reboot","type":"boolean","required":true,"description":"Required. Automatically reboot the server when monitoring detects it is down."},{"name":"auto_reboot_immediate","type":"boolean","required":false,"description":"Required when auto_reboot is true. Reboot immediately on the first detected failure (true) instead of after auto_reboot_tries consecutive failures (false)."},{"name":"auto_reboot_tries","type":"integer|null","required":false,"description":"Optional. Number of consecutive down-checks before an auto-reboot is triggered (2-10). NULL IS ACCEPTED, and typing it integer-only was a documentation bug rather than a rule: the matching GET returns null whenever it has no in-range value to hand back, and its notes tell you to send the value back as you read it. A generated client built from an integer-only type would refuse to send that object back before it ever reached this endpoint, which would make the documented round-trip impossible for exactly the callers who follow the documentation. Sending null means the same as omitting the field: with auto_reboot off it is ignored, and otherwise the stored value falls back to 3, so read auto_reboot and auto_reboot_immediate before assuming a null round-trips as a no-op."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_mysql_details","description":"Get MySQL server details including connection information. WHO CAN CALL THIS: your credential must hold SERVER-LEVEL access to this server. On a shared-hosting plan the server belongs to Proginter and you do not hold that, so this endpoint answers 403 no matter which scopes your key carries. That is not a credential problem and retrying will not help. USE GET /servers/websites/mysql-details/{website} INSTEAD - it returns the same fields and needs only access to the website. The create-database and create-database-user responses also carry host, port, database and user for the database they create.","method":"GET","path_template":"/servers/mysql-details/{server}","path_params":[{"name":"server","type":"integer","required":true,"description":"Server ID (URL parameter). Needs SERVER-LEVEL access to that server. On a shared-hosting plan the server belongs to Proginter, so this call answers 403 whatever scopes your key holds - take host, port, database and user from the create-database or create-database-user response instead."}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.host":"string|null","data.remote_host":"string|null","data.port":"integer","data.socket_path":"string|null"}},{"name":"get_postgres_details","description":"Get the PostgreSQL connection details for this server, so you can build a connection string for a database on it. WHO CAN CALL THIS: your credential must hold SERVER-LEVEL access to this server. On a shared-hosting plan the server belongs to Proginter and you do not hold that, so this endpoint answers 403 no matter which scopes your key carries. That is not a credential problem and retrying will not help. USE GET /servers/websites/postgres-details/{website} INSTEAD - it returns the same fields and needs only access to the website. The create-database and create-database-user responses also carry host, port, database and user for the database they create. Note also what these details are usable FOR: PostgreSQL normally listens on loopback only, so they let an application RUNNING ON THAT SERVER connect, not a client on your own machine. SIX ANSWERS, so branch on the status. 200 with the details. 403 means your credential has no server-level access to this server, as above, and it is permanent for that credential. 404 means this server cannot tell you - either it has no PostgreSQL, or its server software predates this feature - and it is permanent, so do not retry. 502 means the server answered with nothing usable, which is worth retrying. 400 means the server is busy with another operation, so wait a moment and retry. 500 is anything else. On \"host\": this endpoint reports the numeric loopback address 127.0.0.1, while the create-database and create-database-user responses report the name localhost for the same server. Both are correct and both reach the same PostgreSQL - they come from different tools - so do not treat the difference as a signal about which server you are on.","method":"GET","path_template":"/servers/postgres-details/{server}","path_params":[{"name":"server","type":"integer","required":true,"description":"Server ID (URL parameter). Needs SERVER-LEVEL access to that server. On a shared-hosting plan the server belongs to Proginter, so this call answers 403 whatever scopes your key holds - take host, port, database and user from the create-database or create-database-user response instead."}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.host":"string|null - the address to connect to from the server itself","data.port":"integer|null","data.socket_path":"string|null - the Unix socket directory. Use this one, not /tmp","data.remote_enabled":"boolean - whether PostgreSQL listens on more than loopback. This describes the listener only. It is not a promise that the port is reachable from outside the server, because the firewall is a separate layer"}},{"name":"create_servers","description":"Create a new server. Requires a valid price calculation hash obtained from the generate-price-hash endpoint. If your account has an overdue balance on any service (a server or another hosting account), this purchase is blocked until you settle the balance. Requires at least one active service on the account: with no active server or hosting account, this endpoint returns HTTP 403 and your first purchase must be made in the control panel, where your card is verified.","method":"POST","path_template":"/servers","path_params":[],"body_params":[{"name":"name","type":"string","required":true,"description":"Server name (max 255 characters)"},{"name":"cpu","type":"integer","required":true,"description":"Number of CPU cores. Even values only, from 2 to 128. If you plan to run the AI coding agent on websites here, read data.coding_agent_minimums from GET /servers/{server}/plan/config on any of your servers and pick at least that many cores - the agent cannot be switched on below the minimum, and a server bought too small has to be resized"},{"name":"memory","type":"integer","required":true,"description":"Memory in GB. Even values only, from 4 to 256. The same AI coding agent minimum applies to memory - see the cpu parameter"},{"name":"disk","type":"integer","required":true,"description":"Disk space in GB. Multiples of 20, from 40 to 2560"},{"name":"country_id","type":"integer","required":false,"description":"Country ID for server location (must have active clusters)"},{"name":"frontend_calculated_price","type":"float","required":true,"description":"Price calculated on the client side for verification"},{"name":"price_calculation_hash","type":"string","required":true,"description":"Hash from generate-price-hash endpoint"},{"name":"use_balance","type":"boolean","required":false,"description":"Whether to apply account balance to the charge"},{"name":"payment_method_id","type":"integer","required":false,"description":"Payment method ID. Optional if the client has a default payment method on file - backend auto-falls back to it as a safety net. Required only when no method exists and immediate charge > 0."},{"name":"coupon_code","type":"string","required":false,"description":"Coupon code to apply (max 30 characters)"},{"name":"vps_plan_id","type":"integer","required":false,"description":"ID of a pre-built VPS plan from the active vps_plans catalog. When supplied, the customer is buying that plan instead of configuring resources freely - the controller still verifies cpu/memory/disk match the plan row server-side. Omit for custom-build orders."},{"name":"mode","type":"string","required":false,"description":"Informational tag - either \"custom\" (free configuration) or \"plans\" (buying a pre-built plan). The controller branches on vps_plan_id presence. Mode is recorded for analytics/audit but is not load-bearing. Defaults to \"custom\"."},{"name":"currency","type":"string","required":false,"description":"ISO currency code from the supported set (e.g. USD, ILS, EUR). Used for display/charge currency. Defaults to the account's billing currency when omitted."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data.name":"string","data.status":"string","data.task_id":"integer","data.price":"float","data.currency":"string","data.immediate_charge":"float","data.balance_applied":"float - best-effort estimate of balance that will be applied to this charge. The worker re-reads live balance under a row lock and the final amount may differ if a concurrent order drained balance between this call and the job running"}},{"name":"cancel","description":"DESTRUCTIVE - Cancel a server. Can cancel immediately or schedule cancellation at end of billing cycle. Answers 400 when the server was already cancelled or removed, so nothing is changed on a server that is already gone. A key limited to specific servers is refused (403) on a server that was removed, because the removed server can no longer be matched to the key. Only a key that covers every server reaches the 400 answer there. Answers 409 when the server was moved to another account while the request was running (a move that lands before the ownership check answers 403 instead), so fetch the server list again before retrying.","method":"POST","path_template":"/servers/{clientServerId}/cancel","path_params":[{"name":"clientServerId","type":"integer","required":true,"description":"Client server ID (URL parameter)"}],"body_params":[{"name":"cancel_immediately","type":"boolean","required":true,"description":"If true, cancels immediately. If false, schedules cancellation at end of billing cycle."},{"name":"reason_code","type":"string","required":false,"description":"Why the server is being cancelled. One of: platform (moved to an all-in-one platform or AI builder), price, closed_business, no_website, other_host, other. Stored with the cancellation and cleared by cancel-cancellation. The stored reason is not returned by any read endpoint, because it is an internal churn record."},{"name":"reason_text","type":"string","required":false,"description":"A note in your own words, up to 500 characters. Needs a reason_code. Like the reason code, the note is not returned by any read endpoint."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"reactivate","description":"Reverse a scheduled server cancellation. Only applicable to servers scheduled for end-of-cycle cancellation. Answers 400 when the server is not scheduled to cancel, and 400 when it was already cancelled outright (the scheduled cancellation was carried out, or the server was removed), so nothing is changed on a server that is already gone. A key limited to specific servers is refused (403) on a server that was removed, because the removed server can no longer be matched to the key. Only a key that covers every server reaches the 400 answer there. Answers 409 when the server was moved to another account while the request was running (a move that lands before the ownership check answers 403 instead), so fetch the server list again before retrying.","method":"POST","path_template":"/servers/{clientServerId}/cancel-cancellation","path_params":[{"name":"clientServerId","type":"integer","required":true,"description":"Client server ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_change_ip","description":"Change the server's IP address. Limited to one change per day per server.","method":"POST","path_template":"/servers/{server}/change-ip","path_params":[{"name":"server","type":"integer","required":true,"description":"Server ID (URL parameter)"}],"body_params":[{"name":"type","type":"integer","required":true,"description":"IP version to change: 4 for IPv4, 6 for IPv6"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"update_servers","description":"CONDITIONALLY DESTRUCTIVE - Update server settings including name, timezone, SEO block, htaccess, MySQL hosts, Redis remote, and safe mode. Renaming a server or changing its timezone needs no confirmation. Turning on remote MySQL access or remote Redis access does, because each one opens a database port on this server to the internet. Turning safe mode ON does too, because with a whitelist that leaves out the person making the call it takes every site on the server offline for everyone. Two changes on this endpoint are NOT confirmed today and you should treat them as one-way: turning safe mode OFF, and sending safe_mode_whitelist, which REPLACES the whole list rather than adding to it. Read the current values first and send the full list you want to end up with.","method":"PUT","path_template":"/servers/{server}","path_params":[{"name":"server","type":"integer","required":true,"description":"Server ID (URL parameter)"}],"body_params":[{"name":"name","type":"string","required":true,"description":"Server name (max 255 characters). Required on every update - to leave the name unchanged, send the current server name."},{"name":"timezone","type":"string","required":false,"description":"Server timezone (valid PHP timezone identifier)"},{"name":"seo_block","type":"boolean","required":false,"description":"Enable SEO blocking. Omit to leave unchanged."},{"name":"htaccess","type":"boolean","required":false,"description":"Enable .htaccess support. Omit to leave unchanged."},{"name":"allow_mysql_hosts","type":"boolean","required":false,"description":"Allow database users to be tied to specific remote IPs (or `%`) as allowed hosts. When enabled, the firewall opens port 3306 only to IPs configured on user grants - if any user has a wildcard host (`%`, hostname, or pattern like `192.168.%.%`), port 3306 stays open to all. If no user has a remote host, port 3306 stays closed even when this is true. Toggling restarts MariaDB (brief connection interruption). Omit to leave unchanged."},{"name":"allow_redis_remote","type":"boolean","required":false,"description":"Allow remote Redis access. Omit to leave unchanged."},{"name":"safe_mode_enabled","type":"boolean","required":false,"description":"Enable safe mode, which BLOCKS ALL HTTP TRAFFIC to every website on this server except from the whitelisted addresses below. Omit this field to leave it unchanged. Read that both ways before you send it: turning it ON with a whitelist that does not include you takes every site on the server offline for everyone, and turning it OFF removes that protection. Over MCP, turning it ON now asks you to confirm first. Turning it OFF does NOT, and neither does sending safe_mode_whitelist, so treat those two as one-way changes you make deliberately rather than ones you pass along with other edits."},{"name":"safe_mode_whitelist","type":"array","required":false,"description":"Array of IP addresses or CIDR ranges allowed through while safe mode is on. Omit this field to leave it unchanged. Sending it REPLACES the whole list rather than adding to it, so send every address you want to keep, not just the new one. An empty array whitelists nobody."}],"requires_confirmation":false,"prerequisites":[],"requires_confirmation_when":{"allow_mysql_hosts":true,"allow_redis_remote":true,"safe_mode_enabled":true},"response_structure":{"success":"boolean","message":"string"}},{"name":"list_settings","description":"Get the current server settings including timezone, SEO block, htaccess, allow_mysql_hosts (when enabled, the firewall opens port 3306 only to IPs configured on user host grants. Falls back to wide-open if any user has a `%`/hostname host. Stays closed if no user has a remote host), Redis remote, and safe mode.","method":"GET","path_template":"/servers/{server}/settings","path_params":[{"name":"server","type":"integer","required":true,"description":"Server ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.timezone":"string|null","data.seo_block":"boolean","data.htaccess":"boolean","data.allow_mysql_hosts":"boolean","data.allow_redis_remote":"boolean","data.safe_mode":"object"}},{"name":"create_recheck_disk_space","description":"Trigger a disk space recheck on the server. Returns the current free disk space and low-disk status.","method":"POST","path_template":"/servers/{server}/recheck-disk-space","path_params":[{"name":"server","type":"integer","required":true,"description":"Server ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.low_disk_space":"boolean","data.free_disk_mb":"integer"}},{"name":"list_suspend_settings","description":"Get server auto-suspend and notification settings for quota and bandwidth.","method":"GET","path_template":"/servers/{server}/suspend-settings","path_params":[{"name":"server","type":"integer","required":true,"description":"Server ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.auto_suspend_quota_enabled":"boolean","data.auto_suspend_bandwidth_enabled":"boolean","data.quota_notifications_enabled":"boolean","data.quota_alert_percentage":"integer|null","data.bandwidth_notifications_enabled":"boolean","data.bandwidth_alert_percentage":"integer|null"}},{"name":"create_suspend_settings","description":"Update server auto-suspend and notification settings for quota and bandwidth.","method":"POST","path_template":"/servers/{server}/suspend-settings","path_params":[{"name":"server","type":"integer","required":true,"description":"Server ID (URL parameter)"}],"body_params":[{"name":"auto_suspend_quota_enabled","type":"boolean","required":true,"description":"Enable auto-suspend when quota is exceeded"},{"name":"auto_suspend_bandwidth_enabled","type":"boolean","required":true,"description":"Enable auto-suspend when bandwidth is exceeded"},{"name":"quota_notifications_enabled","type":"boolean","required":true,"description":"Enable quota usage notifications"},{"name":"quota_alert_percentage","type":"integer","required":false,"description":"Quota alert threshold percentage (50-100, required when quota_notifications_enabled is true)"},{"name":"bandwidth_notifications_enabled","type":"boolean","required":true,"description":"Enable bandwidth usage notifications"},{"name":"bandwidth_alert_percentage","type":"integer","required":false,"description":"Bandwidth alert threshold percentage (50-100, required when bandwidth_notifications_enabled is true)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_free_month_check","description":"Check if the current client is eligible for a free month coupon on their first server.","method":"GET","path_template":"/servers/free-month-check","path_params":[],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.eligible":"boolean","data.coupon":"object|null"}},{"name":"create_generate_price_hash","description":"Generate a short-lived price calculation hash. Required before creating a server or calculating prices. The hash marks when your quote was calculated and expires after 30 minutes. The final price is always recalculated on the server from the resources you send.","method":"POST","path_template":"/servers/generate-price-hash","path_params":[],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.price_calculation_hash":"string","data.created_at":"string"}},{"name":"create_calculate_price","description":"Calculate server price based on CPU, memory, and disk specifications. Requires a valid price calculation hash.","method":"POST","path_template":"/servers/calculate-price","path_params":[],"body_params":[{"name":"cpu","type":"integer","required":true,"description":"Number of CPU cores. Even values only, from 2 to 128"},{"name":"memory","type":"integer","required":true,"description":"Memory in GB. Even values only, from 4 to 256"},{"name":"disk","type":"integer","required":true,"description":"Disk space in GB. Multiples of 20, from 40 to 2560"},{"name":"price_calculation_hash","type":"string","required":true,"description":"Hash from generate-price-hash endpoint"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.calculated_price":"float","data.base_price":"float","data.vat_rate":"float","data.balance":"float","data.use_balance_first":"boolean"}},{"name":"get_activity","description":"Get paginated server activity logs.","method":"GET","path_template":"/servers/{server}/activity","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"search","type":"string","required":false,"description":"Search activity descriptions"},{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page (max 100)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"integer","data[].actor":"object|null - actor info (type, id, name, email, profile_photo for client. Type only for manager. Null if no actor)","data[].description":"string - activity description","data[].action":"string|null - machine-readable action key. Terminal-failure values today: 'purchase.failed', 'plan.update_failed', 'autoscale.charge_failed'. Rows written by an HTTP request carry the controller method name instead. Key on this rather than display_method to tell a failed action from a successful one","data[].display_method":"string - 'set' or 'delete'","data[].created_at":"string - ISO 8601 timestamp","meta":"object","meta.current_page":"integer","meta.last_page":"integer","meta.per_page":"integer","meta.total":"integer"}},{"name":"list_monitor_events","description":"Get paginated server monitoring events (uptime/downtime events).","method":"GET","path_template":"/servers/{server}/monitor-events","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page (max 100)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"integer - sequential event index","data[].event":"string - event type (e.g., 'up', 'down')","data[].message":"string - event description","data[].date":"string - event timestamp","meta":"object","meta.current_page":"integer","meta.last_page":"integer","meta.per_page":"integer","meta.total":"integer","code":"string (CONDITIONAL, 404 only) - server_not_exists. The server is not set up for monitoring and has no uptime history"}},{"name":"list_ssl_algos","description":"Get the key types this server can create a certificate request with, and the key sizes each one accepts. Use it to fill the two fields POST /servers/websites/{website}/ssl/csr takes: a key from data is a valid algo, and a value from that key's array is a valid bits for it. The pairing is enforced, so a bits value belonging to a different key type is refused.","method":"GET","path_template":"/servers/{server}/ssl-algos","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - a map of key type to the key sizes it accepts, for example {\"RSA\": [\"2048\", \"4096\"], \"ECDSA\": [\"ec-256\"]}. Key types come back in their canonical spelling, which is the spelling the CSR endpoint expects. This field was previously documented as an array of strings, which no version of this endpoint has returned."}},{"name":"get_overview","description":"Get zone overview stats for all websites on the server. Returns status, cache, DDoS, WAF, PHP, antivirus, maintenance, image optimizer, Redis, and accessibility settings per website.","method":"GET","path_template":"/servers/{server}/websites-zone/overview","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"integer","data[].username":"string","data[].domain":"string","data[].status":"string","data[].cache_enable":"boolean","data[].cache_time":"string","data[].ddos_enable":"boolean","data[].ddos_level":"integer","data[].ddos_automode":"boolean","data[].waf_enable":"boolean","data[].php_version":"string","data[].antivirus_realtime":"boolean","data[].antivirus_infected":"integer","data[].maintenance_enable":"boolean","data[].image_optimizer_auto":"boolean","data[].redis_active":"boolean","data[].accessibility_enable":"boolean","meta":"object","meta.total":"integer"}},{"name":"list_all_ids","description":"Get all website IDs for the server.","method":"GET","path_template":"/servers/{server}/websites-zone/all-ids","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.website_ids":"array"}},{"name":"list_defaults","description":"Get default settings for the server. Optionally filter by type. type=php returns the stored default in the shape the PUT accepts: version, slow_request_time and ini_settings (an object of php.ini keys), so a GET followed by a PUT of the same object round-trips. Before 2026-09-06, type=php returned the server's PHP settings catalogue (one entry per php.ini key with its allowed values) instead of the stored default.","method":"GET","path_template":"/servers/{server}/websites-zone/defaults","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"type","type":"string","required":false,"description":"Filter by config type: cache, ddos-protection, waf, php, anti-virus, global, accessibility, redis, requests, wp, image-optimizer, ai-crawlers"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object"}},{"name":"update_defaults","description":"Update default settings for the server. The former WAF keys whitelist_ip_ranges, exclude_ip_ranges and whitelist_all_ip_ranges were removed on 2026-09-06 because no server code ever read them. Sending them has no effect.","method":"PUT","path_template":"/servers/{server}/websites-zone/defaults","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"type","type":"string","required":true,"description":"Config type: cache, ddos-protection, waf, php, anti-virus, global, accessibility, redis, requests, wp, image-optimizer, ai-crawlers"},{"name":"settings","type":"object","required":true,"description":"Settings object, keyed by type. type=cache: enable (boolean), cache_time (one of 1m, 5m, 10m, 20m, 30m, 1h, 4h, 8h, 12h, 1d, 2d, 3d, 4d, 5d, 6d, 7d, 14d, 21d, 30d), bypass_known_uri, bypass_known_query_strings, bypass_known_cookies, bypass_known_headers, ignore_known_query_strings, ignore_all_query_strings, preload_enable (booleans), preload_pages_per_minute (1 to 10). type=waf: enable, bypass_known_uri, bypass_known_query_strings, log_only (booleans), xmlrpc (all, block or jetpack), blocked_proxies_level (off, low, medium or high - stored as the matching proxy list), blocked_proxies (the stored list the GET returns - it may travel with blocked_proxies_level only while the two agree, a different level is rejected, so send the level alone to pick a preset or the list alone to store it as it is). type=requests is a closed key set in the stored form (unprefixed names, unknown keys are rejected): allow_purge_from_local_only, header_x_content_type_options (booleans), allowed_methods (array that keeps GET, POST and HEAD), header_referrer_policy (false or one policy name), content_security_policy, permissions_policy (false or the policy string), allow_frame_from (false, none or a list), allow_cors_from (false, * or a list of origins), allow_cors_methods (false, * or a list of methods), rewrites (list of objects with pattern, replacement and flags). type=php is a closed key set too: version (major.minor, the default PHP version for new websites), slow_request_time (0 to 3600 seconds) and ini_settings (an object of php.ini keys from the server PHP settings spec plus error_reporting - a sent map is merged over the stored one). type=redis is a closed key set: active, appendonly, require_username (booleans), port (must be null: each website gets its own port when Redis is turned on) and maxmemory_policy (a redis eviction policy name). A top-level settings key never contains a dot (the dotted php.ini names such as date.timezone and session.gc_maxlifetime belong inside ini_settings). Other types take the same keys as the matching per-site settings endpoint. Every key is optional."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"list_default_pages","description":"Get default page configuration for a specific page type.","method":"GET","path_template":"/servers/{server}/websites-zone/default-pages","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"page_type","type":"string","required":true,"description":"Page type: maintenance, suspended, waf, nojs, challenge_fallback, error_400, error_401, error_402, error_403, error_404, error_405, error_408, error_409, error_413, error_419, error_429, error_500, error_501, error_502, error_503, error_504, error_505"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object"}},{"name":"update_default_pages","description":"Update a default page.","method":"PUT","path_template":"/servers/{server}/websites-zone/default-pages","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"page_type","type":"string","required":true,"description":"Page type: maintenance, suspended, waf, nojs, challenge_fallback, error_400-error_505"},{"name":"code","type":"string","required":true,"description":"HTML code for the page"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_default_pages","description":"DESTRUCTIVE - Restore a default page to the system default.","method":"DELETE","path_template":"/servers/{server}/websites-zone/default-pages","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"page_type","type":"string","required":true,"description":"Page type to restore"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"purge_cache","description":"DESTRUCTIVE - Bulk purge cache for selected websites.","method":"POST","path_template":"/servers/{server}/websites-zone/cache-purge","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"website_ids","type":"array","required":true,"description":"Array of website IDs, up to 500 per call, or \"all\" to apply to every active website on the server (no limit)"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.success":"array","data.failed":"array","data.failed[].domain":"string","data.failed[].error":"string"}},{"name":"create_cache_settings","description":"Bulk update cache settings for selected websites. Sending category_overrides with any mode other than off turns caching on for each selected website and regenerates its protection rules, with or without other settings fields. Sending mode off always wins and turns caching off. With a preset mode (basic, balanced, strict) and no category_overrides the preset wins: every field the preset sets (cache_time, the bypass and ignore flags, preload_enable, and on balanced and strict preload_pages_per_minute) is overwritten by the preset's own value, and a field the preset does not set (preload_pages_per_minute on basic) keeps the value you sent. Send category_overrides to apply your own values.","method":"POST","path_template":"/servers/{server}/websites-zone/cache-settings","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"website_ids","type":"array","required":true,"description":"Array of website IDs, up to 500 per call, or \"all\" to apply to every active website on the server (no limit)"},{"name":"mode","type":"string","required":true,"description":"Cache mode: off, basic, balanced, strict"},{"name":"category_overrides","type":"object","required":false,"description":"Per-category on/off overrides (boolean values). Any category you omit keeps its current value for each selected website."},{"name":"cache_time","type":"string","required":false,"description":"Cache duration: 1m, 5m, 10m, 20m, 30m, 1h, 4h, 8h, 12h, 1d, 2d, 3d, 4d, 5d, 6d, 7d, 14d, 21d, 30d"},{"name":"bypass_known_uri","type":"boolean","required":false,"description":"Skip the cache for known admin paths. Omit it to keep each selected website's current value."},{"name":"bypass_known_query_strings","type":"boolean","required":false,"description":"Skip the cache for known dynamic URL parameters. Omit it to keep each selected website's current value."},{"name":"bypass_known_cookies","type":"boolean","required":false,"description":"Skip the cache for requests carrying known session or login cookies. Omit it to keep each selected website's current value."},{"name":"bypass_known_headers","type":"boolean","required":false,"description":"Skip the cache for requests carrying known no-cache headers. Omit it to keep each selected website's current value."},{"name":"ignore_known_query_strings","type":"boolean","required":false,"description":"Strip known tracking parameters (such as utm_source or gclid) from the cache key for every visitor. Omit it to keep each selected website's current value."},{"name":"ignore_all_query_strings","type":"boolean","required":false,"description":"Strip every URL parameter from the cache key, only for requests from known bot IP ranges. Omit it to keep each selected website's current value."},{"name":"preload_enable","type":"boolean","required":false,"description":"Warm the cache in the background. Omit it to keep each selected website's current value."},{"name":"preload_pages_per_minute","type":"integer","required":false,"description":"Pages warmed per minute, 1 to 10. Omit it to keep each selected website's current value."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.success":"array","data.failed":"array","data.failed[].domain":"string","data.failed[].error":"string"}},{"name":"create_ddos_settings","description":"Bulk update DDoS protection settings for selected websites. Send mode alone to apply a preset. Sending any of the advanced fields applies them as custom settings to the selected sites instead, with mode then used only to sync the built-in rules (the panel sends mode=balanced in that case).","method":"POST","path_template":"/servers/{server}/websites-zone/ddos-settings","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"website_ids","type":"array","required":true,"description":"Array of website IDs, up to 500 per call, or \"all\" to apply to every active website on the server (no limit)"},{"name":"mode","type":"string","required":true,"description":"DDoS mode: off, basic, balanced, strict"},{"name":"level","type":"integer","required":false,"description":"Advanced field: protection level (1=invisible, 2=max - the level previously named captcha)"},{"name":"automode","type":"boolean","required":false,"description":"Advanced field: turn Automatic mode on (protection adjusts to traffic on its own)"},{"name":"enable","type":"boolean","required":false,"description":"Advanced field: turn fixed-level protection on (send true together with level when not using Automatic mode)"},{"name":"internal_flood_protection","type":"boolean","required":false,"description":"Advanced field: enable flood rate limiting (the flood fields below apply only when this is true)"},{"name":"internal_flood_protection_mode","type":"string","required":false,"description":"Advanced field: flood cap mode, auto (a learned or standard limit) or manual (your own numbers)"},{"name":"internal_flood_protection_session_cap","type":"integer","required":false,"description":"Advanced field (manual mode): requests per second from one visitor session, 0-1000 (0 means no per-session rate cap)"},{"name":"internal_flood_protection_concurrency_cap","type":"integer","required":false,"description":"Advanced field (manual mode): simultaneous requests from one address, 200-10000"},{"name":"bypass_known_bots","type":"boolean","required":false,"description":"Advanced field: let known search engine and service bots bypass challenges"},{"name":"whitelist_timeout_minutes","type":"integer","required":false,"description":"Advanced field: trust duration in minutes after a solved challenge (1-1440)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.success":"array","data.failed":"array","data.failed[].domain":"string","data.failed[].error":"string"}},{"name":"create_waf_settings","description":"Bulk update WAF settings for selected websites. Sending category_overrides with any mode other than off turns the WAF on for each selected website and regenerates its protection rules, with or without other settings fields. Sending mode off always wins and turns the WAF off. The former whitelist_ip_ranges, exclude_ip_ranges and whitelist_all_ip_ranges fields were removed on 2026-09-06 because no server code ever read them. Sending them has no effect.","method":"POST","path_template":"/servers/{server}/websites-zone/waf-settings","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"website_ids","type":"array","required":true,"description":"Array of website IDs, up to 500 per call, or \"all\" to apply to every active website on the server (no limit)"},{"name":"mode","type":"string","required":true,"description":"WAF mode: off, balanced, strict"},{"name":"category_overrides","type":"object","required":false,"description":"Per-category on/off overrides (boolean values). Any category you omit keeps its current value for each selected website."},{"name":"xmlrpc","type":"string","required":false,"description":"XML-RPC handling: all (allow), block, or jetpack (allow only Jetpack)."},{"name":"bypass_known_uri","type":"boolean","required":false,"description":"Bypass the WAF for known-good URIs"},{"name":"bypass_known_query_strings","type":"boolean","required":false,"description":"Bypass the WAF for known-good query strings"},{"name":"blocked_proxies_level","type":"string","required":false,"description":"Anonymous-proxy blocking level: off, low, medium, high."},{"name":"log_only","type":"boolean","required":false,"description":"Enable log-only mode"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.success":"array","data.failed":"array","data.failed[].domain":"string","data.failed[].error":"string"}},{"name":"create_php_version","description":"Bulk update PHP version for selected websites.","method":"POST","path_template":"/servers/{server}/websites-zone/php-version","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"website_ids","type":"array","required":true,"description":"Array of website IDs, up to 500 per call, or \"all\" to apply to every active website on the server (no limit)"},{"name":"version","type":"string","required":true,"description":"PHP version (e.g., \"8.2\")"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.success":"array","data.failed":"array","data.failed[].domain":"string","data.failed[].error":"string"}},{"name":"create_antivirus_scan","description":"Bulk start antivirus scan for selected websites.","method":"POST","path_template":"/servers/{server}/websites-zone/antivirus-scan","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"website_ids","type":"array","required":true,"description":"Array of website IDs, up to 500 per call, or \"all\" to apply to every active website on the server (no limit)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.success":"array","data.failed":"array","data.failed[].domain":"string","data.failed[].error":"string"}},{"name":"create_antivirus_settings","description":"Bulk update antivirus settings for selected websites. Applies the automatic scan toggle and period to each website. Does not start a scan.","method":"POST","path_template":"/servers/{server}/websites-zone/antivirus-settings","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"website_ids","type":"array","required":true,"description":"Array of website IDs, up to 500 per call, or \"all\" to apply to every active website on the server (no limit)"},{"name":"automatic","type":"boolean","required":true,"description":"Enable or disable automatic scanning"},{"name":"period","type":"string","required":true,"description":"Scan interval in seconds: 86400 (daily), 604800 (weekly), 2592000 (monthly)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.success":"array","data.failed":"array","data.failed[].domain":"string","data.failed[].error":"string"}},{"name":"create_antivirus_clean","description":"DESTRUCTIVE - Bulk clean infected files for selected websites. For each website, cleans (removes/quarantines) the files from the first infected scan found. Irreversible when backup is false. Leave backup at its default (true) to keep a recoverable copy first.","method":"POST","path_template":"/servers/{server}/websites-zone/antivirus-clean","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"website_ids","type":"array","required":true,"description":"Array of website IDs, up to 500 per call, or \"all\" to apply to every active website on the server (no limit)"},{"name":"backup","type":"boolean","required":false,"description":"Create backup before cleaning (default: true)"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.success":"array","data.failed":"array","data.failed[].domain":"string","data.failed[].error":"string","data.skipped":"array"}},{"name":"create_maintenance_mode","description":"Bulk enable/disable maintenance mode for selected websites.","method":"POST","path_template":"/servers/{server}/websites-zone/maintenance-mode","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"website_ids","type":"array","required":true,"description":"Array of website IDs, up to 500 per call, or \"all\" to apply to every active website on the server (no limit)"},{"name":"enable","type":"boolean","required":true,"description":"Enable or disable maintenance mode"},{"name":"exclude_ips","type":"array","required":false,"description":"IPs to exclude from maintenance mode"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.success":"array","data.failed":"array","data.failed[].domain":"string","data.failed[].error":"string"}},{"name":"create_general_settings","description":"Bulk update general settings for selected websites. Includes SEO, maintenance, WordPress, request headers, CORS, and security settings.","method":"POST","path_template":"/servers/{server}/websites-zone/general-settings","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"website_ids","type":"array","required":true,"description":"Array of website IDs, up to 500 per call, or \"all\" to apply to every active website on the server (no limit)"},{"name":"seo_block","type":"boolean","required":false,"description":"Block search engine indexing"},{"name":"maintenance_enable","type":"boolean","required":false,"description":"Enable maintenance mode"},{"name":"maintenance_exclude_ips","type":"array","required":false,"description":"Public IP addresses or public IP ranges allowed to bypass maintenance mode. Each item is a single public IP or a public CIDR range. Private and reserved ranges are not accepted."},{"name":"requests_allowed_methods","type":"array","required":false,"description":"Allowed HTTP request methods for the site. Each item is one of GET, POST, HEAD, PUT, DELETE, OPTIONS, PURGE. The list must include GET, POST and HEAD."},{"name":"requests_header_x_content_type_options","type":"boolean","required":false,"description":"Send the X-Content-Type-Options nosniff response header."},{"name":"requests_header_referrer_policy","type":"string","required":false,"description":"Referrer-Policy response header value. One of no_policy, no-referrer, no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url. Use no_policy to send no Referrer-Policy header."},{"name":"requests_content_security_policy_toggle","type":"boolean","required":false,"description":"Enable a custom Content-Security-Policy header. When true, provide requests_content_security_policy. When false, any custom policy is cleared."},{"name":"requests_content_security_policy","type":"string","required":false,"description":"Custom Content-Security-Policy header value, up to 5000 characters. Required when requests_content_security_policy_toggle is true. The literal word \"false\" is not accepted as a value. To turn the header off, set requests_content_security_policy_toggle to false."},{"name":"requests_permissions_policy_toggle","type":"boolean","required":false,"description":"Enable a custom Permissions-Policy header. When true, provide requests_permissions_policy. When false, any custom policy is cleared."},{"name":"requests_permissions_policy","type":"string","required":false,"description":"Custom Permissions-Policy header value, up to 5000 characters. Required when requests_permissions_policy_toggle is true. The literal word \"false\" is not accepted as a value. To turn the header off, set requests_permissions_policy_toggle to false."},{"name":"requests_allow_frame_from_toggle","type":"boolean","required":false,"description":"Enable iframe-embedding control, which builds the CSP frame-ancestors directive. When true, provide requests_allow_frame_from. When false, iframe embedding control is turned off."},{"name":"requests_allow_frame_from","type":"array","required":false,"description":"Origins allowed to embed this site in an iframe. Each item is self, none (deny all embedding), a full origin like https://app.example.com, a bare domain like example.com, a wildcard subdomain like *.example.com or https://*.example.com, or a hostname like localhost. Up to 2048 characters per item."},{"name":"requests_allow_cors_from_toggle","type":"boolean","required":false,"description":"Enable CORS. When true, provide requests_allow_cors_from and requests_allow_cors_methods. When false, CORS is turned off."},{"name":"requests_allow_cors_from","type":"array","required":false,"description":"Origins allowed to make cross-origin requests. Each item is a full origin like https://app.example.com, a bare domain like example.com, a hostname like localhost, or * for all origins. A wildcard subdomain is not accepted for CORS. Up to 255 characters per item."},{"name":"requests_allow_cors_methods","type":"array","required":false,"description":"HTTP methods allowed for cross-origin requests. Each item is one of GET, POST, DELETE, PUT, PATCH, OPTIONS, HEAD."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.success":"array","data.failed":"array","data.failed[].domain":"string","data.failed[].error":"string"}},{"name":"create_image_optimizer","description":"Bulk update image optimizer settings for selected websites.","method":"POST","path_template":"/servers/{server}/websites-zone/image-optimizer","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"website_ids","type":"array","required":true,"description":"Array of website IDs, up to 500 per call, or \"all\" to apply to every active website on the server (no limit)"},{"name":"auto_optimize","type":"boolean","required":false,"description":"Enable auto optimization"},{"name":"quality","type":"integer","required":false,"description":"Image quality (1-100)"},{"name":"convert_to_webp","type":"boolean","required":false,"description":"Convert images to WebP format"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.success":"array","data.failed":"array","data.failed[].domain":"string","data.failed[].error":"string"}},{"name":"create_redis","description":"Bulk update Redis settings for selected websites.","method":"POST","path_template":"/servers/{server}/websites-zone/redis","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"website_ids","type":"array","required":true,"description":"Array of website IDs, up to 500 per call, or \"all\" to apply to every active website on the server (no limit)"},{"name":"active","type":"boolean","required":true,"description":"Enable or disable Redis"},{"name":"maxmemory_policy","type":"string","required":false,"description":"Max memory policy: allkeys-lru, allkeys-lfu, allkeys-random, volatile-lru, volatile-lfu, volatile-random, volatile-ttl, noeviction"},{"name":"appendonly","type":"boolean","required":false,"description":"Enable append-only file persistence"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.success":"array","data.failed":"array","data.failed[].domain":"string","data.failed[].error":"string"}},{"name":"purge_redis","description":"DESTRUCTIVE - Bulk purge Redis cache for selected websites.","method":"POST","path_template":"/servers/{server}/websites-zone/redis-purge","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"website_ids","type":"array","required":true,"description":"Array of website IDs, up to 500 per call, or \"all\" to apply to every active website on the server (no limit)"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.success":"array","data.failed":"array","data.failed[].domain":"string","data.failed[].error":"string"}},{"name":"create_accessibility","description":"Bulk update accessibility settings for selected websites. Omitted fields keep each website's own stored value, so you can change one setting across many websites without resending the rest.","method":"POST","path_template":"/servers/{server}/websites-zone/accessibility","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"website_ids","type":"array","required":true,"description":"Array of website IDs, up to 500 per call, or \"all\" to apply to every active website on the server (no limit)"},{"name":"enable","type":"boolean","required":true,"description":"Enable or disable accessibility widget"},{"name":"langs","type":"array","required":false,"description":"Languages offered by the widget: en, he, ar. At least one when sent. Omit to keep each website's stored languages"},{"name":"default_lang","type":"string","required":false,"description":"Default language: en, he, ar. Must be one of the languages in langs"},{"name":"location_vertical","type":"string","required":false,"description":"Desktop vertical position: top or bottom"},{"name":"location_horizontal","type":"string","required":false,"description":"Desktop horizontal position: left or right"},{"name":"mobile_location_vertical","type":"string","required":false,"description":"Mobile vertical position: top or bottom"},{"name":"mobile_location_horizontal","type":"string","required":false,"description":"Mobile horizontal position: left or right"},{"name":"space_vertical","type":"integer","required":false,"description":"Desktop vertical offset in pixels (0-1000)"},{"name":"space_horizontal","type":"integer","required":false,"description":"Desktop horizontal offset in pixels (0-1000)"},{"name":"mobile_space_vertical","type":"integer","required":false,"description":"Mobile vertical offset in pixels (0-1000)"},{"name":"mobile_space_horizontal","type":"integer","required":false,"description":"Mobile horizontal offset in pixels (0-1000)"},{"name":"mode","type":"string","required":false,"description":"Widget color mode: light or dark"},{"name":"statement_link","type":"string","required":false,"description":"Link to an external accessibility statement page (max 2048 characters)"},{"name":"statement_title","type":"string","required":false,"description":"Title shown above the accessibility statement (max 300 characters)"},{"name":"statement_body","type":"string","required":false,"description":"Accessibility statement text shown inside the widget (max 100000 characters)"},{"name":"exclude_uri","type":"array","required":false,"description":"Paths where the widget must not load, each starting with /, up to 200 entries"},{"name":"gdpr_category","type":"string","required":false,"description":"Cookie consent category that must be accepted before the widget loads (letters, digits, underscore and hyphen). Send an empty string to fall back to the necessary category. The widget then still goes through the consent runner, and because the necessary category is always on, it loads as soon as the runner starts. When the cookie banner is switched off for the website, the widget loads straight away"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.success":"array","data.failed":"array","data.failed[].domain":"string","data.failed[].error":"string"}},{"name":"create_custom_pages","description":"DESTRUCTIVE - Bulk apply the server's configured custom default page to selected websites for the specified type. Overwrites each site's existing page of that type.","method":"POST","path_template":"/servers/{server}/websites-zone/custom-pages","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"website_ids","type":"array","required":true,"description":"Array of website IDs, up to 500 per call, or \"all\" to apply to every active website on the server (no limit)"},{"name":"page_type","type":"string","required":true,"description":"Page type: maintenance, suspended, waf, nojs, challenge_fallback, error_400, error_401, error_402, error_403, error_404, error_405, error_408, error_409, error_413, error_419, error_429, error_500, error_501, error_502, error_503, error_504, error_505"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.success":"array","data.failed":"array","data.failed[].domain":"string","data.failed[].error":"string"}},{"name":"restore_pages","description":"DESTRUCTIVE - Bulk restore pages to system defaults for selected websites.","method":"POST","path_template":"/servers/{server}/websites-zone/restore-pages","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"website_ids","type":"array","required":true,"description":"Array of website IDs, up to 500 per call, or \"all\" to apply to every active website on the server (no limit)"},{"name":"page_type","type":"string","required":true,"description":"Page type: maintenance, suspended, waf, nojs, challenge_fallback, error_400, error_401, error_402, error_403, error_404, error_405, error_408, error_409, error_413, error_419, error_429, error_500, error_501, error_502, error_503, error_504, error_505"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.success":"array","data.failed":"array","data.failed[].domain":"string","data.failed[].error":"string"}},{"name":"create_ai_crawlers_settings","description":"Bulk update AI crawler settings for selected websites.","method":"POST","path_template":"/servers/{server}/websites-zone/ai-crawlers-settings","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"website_ids","type":"array","required":true,"description":"Array of website IDs, up to 500 per call, or \"all\" to apply to every active website on the server (no limit)"},{"name":"rules","type":"object","required":true,"description":"AI-crawler rules as a slug-keyed OBJECT (not an array): {\"<crawler-slug>\": <rule>, ...} where <crawler-slug> is a slug from the ai-crawler-definitions endpoint (e.g. anthropic, google-ai). Each <rule> is either the string \"allow\" or \"block\", OR an object {\"mode\": \"allow\"|\"rate_limit\"|\"block\", \"limit\": <integer 1-10000, required when mode is rate_limit>, \"interval\": \"second\"|\"minute\"|\"hour\" (required when mode is rate_limit), \"scope\": \"scanning\"|\"user_action\"|\"both\" (optional)}."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.success":"array","data.failed":"array","data.failed[].domain":"string","data.failed[].error":"string"}},{"name":"list_ai_crawler_definitions","description":"Get available AI crawler definitions with names, slugs, and user agents.","method":"GET","path_template":"/servers/{server}/websites-zone/ai-crawler-definitions","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"integer","data[].name":"string","data[].slug":"string","data[].user_agents":"array","data[].icon":"string"}}]},{"name":"servers_firewall","scope":"servers:firewall","description":"Manage your servers including power controls, monitoring, blocked IPs, IP lists, and MySQL details.","actions":[{"name":"list_open_ports","description":"Get a paginated list of open firewall ports on the server.","method":"GET","path_template":"/servers/{server}/firewall/open-ports","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"direction","type":"string","required":false,"description":"Filter by direction: incoming or outgoing"},{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page (max 100)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","meta":"object","meta.current_page":"integer","meta.last_page":"integer","meta.per_page":"integer","meta.total":"integer"}},{"name":"create_open_ports","description":"Open a new port on the server firewall.","method":"POST","path_template":"/servers/{server}/firewall/open-ports","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"direction","type":"string","required":true,"description":"Port direction: incoming or outgoing"},{"name":"port","type":"string","required":true,"description":"Port number or range"},{"name":"protocol","type":"string","required":true,"description":"Protocol. One of: tcp, udp, both"},{"name":"allowed_ips","type":"array","required":false,"description":"IP addresses the rule is limited to. Incoming: the sources allowed to connect in. Outgoing: the destinations this server may reach. Empty means no restriction. Public addresses only, so 10.0.0.0/8, 192.168.x and 127.0.0.1 are refused. One caveat on OUTGOING rules: a few ordinary service ports are already allowed by the server's own base rules, and on those a restriction is stored and reported successful while traffic still flows, so verify the behavior rather than trusting the reply"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"update_open_ports","description":"Update an open port on the server firewall.","method":"PUT","path_template":"/servers/{server}/firewall/open-ports/{port}","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"},{"name":"port","type":"string","required":true,"description":"New port number"}],"body_params":[{"name":"direction","type":"string","required":true,"description":"Port direction: incoming or outgoing"},{"name":"old_port","type":"string","required":true,"description":"Current port number"},{"name":"protocol","type":"string","required":true,"description":"Protocol. One of: tcp, udp, both"},{"name":"allowed_ips","type":"array","required":false,"description":"IP addresses the rule is limited to. Incoming: the sources allowed to connect in. Outgoing: the destinations this server may reach. Empty means no restriction. Public addresses only, so 10.0.0.0/8, 192.168.x and 127.0.0.1 are refused. One caveat on OUTGOING rules: a few ordinary service ports are already allowed by the server's own base rules, and on those a restriction is stored and reported successful while traffic still flows, so verify the behavior rather than trusting the reply"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_open_ports","description":"DESTRUCTIVE - Remove open ports from the server firewall. Send each id exactly as the list endpoint returned it. The port half is accepted whenever it is not empty and holds only digits, commas, hyphens and spaces, which covers a single port, a start-end range and a comma-separated list of those. It is deliberately not range-checked: the server can hold a stored value this API never wrote, and anything it holds and lists has to stay removable, so an odd-looking id is passed through to be matched rather than refused. An entry that is a single value but does not meet that rule is IGNORED rather than rejected, and the call still answers 200 with success true, so read the message rather than the status: it states how many port closures were requested and names any entries it ignored, with the reason. A count of 0 means nothing was acted on. Removal matches the stored value exactly, so a requested closure is not proof the entry existed. List the ports again to confirm. An entry sent as a list rather than a single value is refused with 422 naming the field.","method":"DELETE","path_template":"/servers/{server}/firewall/open-ports","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"ids","type":"array","required":true,"description":"Array of port IDs to remove (format: direction_port, e.g. incoming_8080). Maximum 500 per call, because each one is applied on the server individually."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"list_blocked_ips","description":"Get a paginated list of blocked IP addresses on the server.","method":"GET","path_template":"/servers/{server}/firewall/blocked-ips","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page (max 100)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"string|null","data[].ip":"string|null","data[].blocked_at":"integer|null","data[].type":"string","data[].reason":"string","data[].expires_at":"integer|null","meta":"object","meta.current_page":"integer","meta.last_page":"integer","meta.per_page":"integer","meta.total":"integer"}},{"name":"create_blocked_ips","description":"Block an IP address on the server firewall.","method":"POST","path_template":"/servers/{server}/firewall/blocked-ips","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"ip","type":"string","required":true,"description":"IP address to block"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_blocked_ips","description":"DESTRUCTIVE - Unblock IP addresses from the server firewall. An entry that is a single value but not a valid IP address or CIDR range is IGNORED rather than rejected, and the call still answers 200 with success true, so read the message rather than the status: it states how many unblocks were REQUESTED, not how many addresses were actually unblocked, and it names any entries it ignored. The node call returns nothing, so an address that was never blocked is counted the same as one that was. Read the blocked-IP list back to confirm. A count of 0 means nothing was acted on. An entry sent as a list rather than a single value is refused with 422 naming the field.","method":"DELETE","path_template":"/servers/{server}/firewall/blocked-ips","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"ids","type":"array","required":true,"description":"Array of IP addresses to unblock. Maximum 500 per call, because each one is applied on the server individually."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}}]},{"name":"servers_health","scope":"servers:health","description":"Manage your servers including power controls, monitoring, blocked IPs, IP lists, and MySQL details.","actions":[{"name":"get_health","description":"Get a paginated list of performance records on the server. Without event_filter the list returns your website-attributed records only (real alerts plus routine load-history captures) - the compatible view this endpoint has always served. With event_filter=events the list widens to real incidents of every cause: website-attributed rows keep the full row shape, and server-level incidents (capacity or maintenance) appear as SOFT rows that carry a plain-language soft_title and soft_description instead of the technical fields. Each row carries a friendly event kind and a severity. Default sort is worst-first by severity. The older /servers/{server}/peaks paths remain as permanent aliases for the same actions.","method":"GET","path_template":"/servers/{server}/health","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page (max 100)"},{"name":"event_filter","type":"string","required":false,"description":"Filter the returned rows and total by kind. 'events' returns only real incidents and widens them to every cause - server-level incidents appear as soft rows, and free-text search is not applied on this view. 'load' returns only routine load-history rows (event null). Absent returns all your website-attributed rows - the compatible default view."},{"name":"order_by","type":"string","required":false,"description":"Sort field, one of: severity, timestamp, cpu_percent. Defaults to severity (worst-first) when omitted. Under event_filter=events only severity and timestamp apply - any other explicit value is ignored there and rows return in their natural newest-first order."},{"name":"order_direction","type":"string","required":false,"description":"Sort direction, asc or desc. Defaults to desc."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"string|null","data[].timestamp":"number|null (Unix epoch seconds)","data[].type":"string (derived cause of the record - one of: website_cause, server_cause, maintenance_cause. Always website_cause outside event_filter=events)","data[].event":"string|null (the incident kind. Website-attributed values: worker_restarted, pool_full, worker_killed, app_restarted, user_redis_down, web_errors_site. Server-incident values, returned only under event_filter=events: db_slowed, db_restarted, server_pool_full, server_low_memory, server_maintenance_held, web_errors_server, es_unhealthy. The set may grow as new incident sources ship - treat an unknown value as informational. Null for legacy load records)","data[].severity":"string (one of: serious, notable, minor, info)","data[].trigger_type":"string (website-attributed rows only)","data[].cpu_percent":"float (website-attributed rows only)","data[].websites_cpu_percent":"float (website-attributed rows only)","data[].website":"string|null (domain of the affected website. Null when the record carries no website attribution, and always null on server-incident soft rows)","data[].soft_title":"string (server-incident soft rows only - plain-language incident title)","data[].soft_description":"string (server-incident soft rows only - plain-language description of what happened and what it meant for your sites)","stats":"object","stats.total_peaks":"integer (all-time count of your website-attributed records, alerts plus load history. Omitted when event_filter=events)","stats.total_health_records":"integer (additive health-named alias of total_peaks - the same value, carried so integrations can adopt the health-named field. Present exactly when total_peaks is present, and omitted the same way when event_filter=events)","stats.last_peak_time":"number|null (Unix epoch seconds - most recent website-attributed record, null if none. Omitted when event_filter=events)","stats.last_health_time":"number|null (additive health-named alias of last_peak_time - the same value, carried so integrations can adopt the health-named field. Present exactly when last_peak_time is present, and omitted the same way when event_filter=events)","stats.last_event_time":"number|null (Unix epoch seconds - most recent real alert only, null if none. Covers incidents of every cause under event_filter=events)","stats.total_load_records":"integer (all-time count of routine load-history records, where event is null. Omitted when event_filter=events)","stats.total_last_7_days":"integer (count of real alerts in the last 7 days. Covers incidents of every cause under event_filter=events)","stats.last_7_days":"array of {date: string YYYY-MM-DD, count: integer} (per-day real-alert counts, 7 entries, oldest first. Covers incidents of every cause under event_filter=events)","meta":"object","meta.total":"integer (count of rows matching the current event_filter and its scope)"}},{"name":"get_notice","description":"Get peak notification settings.","method":"GET","path_template":"/servers/{server}/health/notice","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object"}},{"name":"update_notice","description":"Update peak notification settings.","method":"PUT","path_template":"/servers/{server}/health/notice","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"enabled","type":"boolean","required":true,"description":"Enable or disable peak notifications"},{"name":"send_email","type":"boolean","required":true,"description":"Whether to send email notifications"},{"name":"emails","type":"array","required":false,"description":"Email addresses for notifications"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object"}},{"name":"servers_get_health","description":"Get details of a specific performance event. A website-attributed event returns the full detail below. A server-level incident (capacity or maintenance) returns a short plain-language soft summary instead - timestamp, type, event, severity, soft_title and soft_description, with no technical fields. The internal capacity snapshot is never included.","method":"GET","path_template":"/servers/{server}/health/{health}","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"},{"name":"health","type":"string","required":true,"description":"URL parameter"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.event":"string|null (the incident kind - the same value set as the list endpoint, and the set may grow as new incident sources ship. Null for legacy load peaks)","data.severity":"string (normally one of: serious, notable, minor, info - a legacy website-attributed record may carry its raw stored value instead. Server-incident soft summaries always carry one of the four values or null)","data.type":"string (derived cause - one of: website_cause, server_cause, maintenance_cause)","data.timestamp":"number|string|null (Unix epoch seconds - a legacy website-attributed record may carry its original date string instead. Server-incident soft summaries always carry epoch seconds or null)","data.top_websites":"object (website-attributed details only - your affected websites on the server, keyed by hosting account name)","data.soft_title":"string (server-incident soft summaries only - plain-language incident title)","data.soft_description":"string (server-incident soft summaries only - plain-language description)"}}]},{"name":"servers_notifications","scope":"servers:notifications","description":"Manage your servers including power controls, monitoring, blocked IPs, IP lists, and MySQL details.","actions":[{"name":"list_notifications","description":"Get a paginated list of server and website notifications.","method":"GET","path_template":"/servers/{server}/notifications","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"read_status","type":"string","required":false,"description":"Filter by read status: all, read, or unread (default: all)"},{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page (max 100)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"integer","data[].type":"string","data[].title":"string","data[].text":"string","data[].is_read":"boolean","data[].read_at":"string|null","data[].server_id":"integer","data[].created_at":"integer","data[].source":"string","data[].website_domain":"string|null","meta":"object","meta.current_page":"integer","meta.last_page":"integer","meta.per_page":"integer","meta.total":"integer"}},{"name":"get_unread_count","description":"Get the count of unread notifications for a server.","method":"GET","path_template":"/servers/{server}/notifications/unread-count","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.unread_count":"integer"}},{"name":"update_mark_read","description":"Mark notifications as read on a server. Leaving ids empty, null or out marks EVERY notification on the server as read, so send it explicitly.","method":"PUT","path_template":"/servers/{server}/notifications/mark-read","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"ids","type":"array","required":false,"description":"Notification IDs to mark as read. An EMPTY array, an explicit null, and an omitted field all mean MARK EVERY NOTIFICATION on this server - they do not mean \"mark nothing\". The reply reports how many rows the request actually matched, so an ID that does not belong to this server counts as zero. Every ID must be a whole number above zero. A 0 or a negative refuses the WHOLE request with 422 and marks nothing, rather than being dropped from the list. Any other unusable entry is dropped rather than refused, and the reply's message text says how many were ignored, so do not assume every ID you sent was acted on. That count is only in the English message on this endpoint, not a separate field."},{"name":"source","type":"string","required":false,"description":"Which list the IDs come from: server or website. Leaving it out means server. When you send NO ids, this also decides which list is cleared: name one and only that list is touched, leave it out and both are. Any other value is refused with 422 and nothing is changed."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_notifications","description":"DESTRUCTIVE - Delete notifications on a server. LEAVING ids EMPTY, NULL OR OUT DELETES EVERY NOTIFICATION on the server, so send it explicitly. This cannot be undone.","method":"DELETE","path_template":"/servers/{server}/notifications","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"ids","type":"array","required":false,"description":"Notification IDs to delete. An EMPTY array, an explicit null, and an omitted field all mean DELETE EVERY NOTIFICATION on this server - they do not mean \"delete nothing\". A client building this list from a user selection must not call the endpoint at all when the selection is empty. The reply reports how many rows were actually deleted, so an ID that does not belong to this server counts as zero. Every ID must be a whole number above zero. A 0 or a negative refuses the WHOLE request with 422 and deletes nothing, rather than being dropped from the list. Any other unusable entry is dropped rather than refused, and the reply's message text says how many were ignored, so do not assume every ID you sent was deleted. That count is only in the English message on this endpoint, not a separate field."},{"name":"source","type":"string","required":false,"description":"Which list the IDs come from: server or website. Leaving it out means server. When you send NO ids, this also decides which list is cleared: name one and only that list is touched, leave it out and both are. Any other value is refused with 422 and nothing is changed."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}}]},{"name":"servers_plan","scope":"servers:plan","description":"Manage your servers including power controls, monitoring, blocked IPs, IP lists, and MySQL details.","actions":[{"name":"list_stats","description":"Get server plan statistics including billing info, resources, and pricing. When Proginter has granted a payment extension on this overdue server, is_graced is true and grace_until is the date the extension runs to (the server stays online with daily card retries until then), so an overdue balance under a hold is distinguishable from one at immediate risk.","method":"GET","path_template":"/servers/{server}/plan/stats","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.price":"string","data.price_type":"string","data.next_cycle_at":"string|null","data.billing_day":"integer|null","data.overdue":"boolean","data.overdue_at":"string|null","data.grace_until":"string|null","data.is_graced":"boolean","data.cpu":"integer","data.memory":"integer","data.disk":"integer","data.currency":"string"}},{"name":"get_config","description":"Get server plan configuration options including available resources, pricing, features, and coupon info. data.autoscale.fit is the server's memory fit check. State ok means the check ran: fits tells whether the current memory size is at or above the computed safe minimum, recommended_min_mb is the minimum safe memory in MB (null when even the largest available size cannot cover current usage), resize_in_progress true means a resize is running right now so the numbers may shift, and degraded true means the check ran with partial data. When a shrink target sits at or above the safe minimum but degraded is true, the panel shows an informational partial-data note in its confirm dialog - advisory only, it never requires confirm_below_need. State server_off or unavailable means the server could not be checked right now. Read this before shrinking memory and see confirm_below_need on PUT /servers/{server}/plan. The fit check and the Server Health peaks verdict measure different things and may disagree - the fit check predicts availability after a resize, while Server Health reports observed usage.","method":"GET","path_template":"/servers/{server}/plan/config","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.current_resources":"object","data.current_resources.cpu":"integer","data.current_resources.memory":"integer","data.current_resources.disk":"integer","data.custom_cpu_core_price":"float|null","data.use_custom_pricing":"boolean","data.current_price_type":"string","data.current_price":"float","data.next_cycle_at":"string|null","data.vat_rate":"float","data.currency":"string","data.client_currency":"string","data.balance":"float","data.available_balance":"float","data.use_balance_first":"boolean","data.features_config":"array","data.current_features":"array","data.coupon":"object|null","data.coupon.code":"string","data.coupon.discount_type":"string","data.coupon.discount_value":"float","data.coupon.months_remaining":"integer|null","data.live_resize":"boolean","data.autoscale":"object","data.autoscale.enabled":"boolean","data.autoscale.window_minutes":"integer","data.autoscale.base_cpu":"integer|null","data.autoscale.base_memory":"integer|null","data.autoscale.scaled_above_base":"boolean","data.autoscale.revert_pending":"boolean","data.autoscale.fit":"object","data.autoscale.fit.state":"string","data.autoscale.fit.fits":"boolean","data.autoscale.fit.recommended_min_mb":"integer|null","data.autoscale.fit.resize_in_progress":"boolean","data.autoscale.fit.checked_at":"integer","data.autoscale.fit.degraded":"boolean","data.pricing_config":"object","data.cpu_options":"array","data.memory_options":"array","data.disk_options":"array","data.coding_agent_minimums":"object (what a server must have before the AI coding agent can be switched on for websites on it - resizing below these numbers stops it being switched on)","data.coding_agent_minimums.cores":"integer (minimum CPU cores)","data.coding_agent_minimums.memory_gb":"integer (minimum memory in GB)"}},{"name":"create_generate_hash","description":"Generate a short-lived price calculation hash. Required before calculate-price and update. The hash marks when your quote was calculated (the mid-cycle charge is prorated from that moment) and expires after 30 minutes. The final price is always recalculated on the server from the resources you send.","method":"POST","path_template":"/servers/{server}/plan/generate-hash","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.price_calculation_hash":"string","data.created_at":"string"}},{"name":"create_calculate_price","description":"Calculate price for a new server resource configuration before committing the change.","method":"POST","path_template":"/servers/{server}/plan/calculate-price","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"cpu","type":"integer","required":true,"description":"Number of CPU cores. Even values only, from 2 to 128"},{"name":"memory","type":"integer","required":true,"description":"Memory in GB. Even values only, from 4 to 256"},{"name":"disk","type":"integer","required":true,"description":"Disk space in GB. Keep the current size or increase it in steps of 20 GB, up to 2560"},{"name":"price_calculation_hash","type":"string","required":true,"description":"Hash from generate-hash endpoint"},{"name":"features","type":"object","required":false,"description":"Optional features configuration object. Currently supports backup_frequency: {\"backup_frequency\": {\"frequency\": <integer 1-4, required when backup_frequency is present>, \"full_backup\": <boolean>, \"additional_kinds\": [<one or more of \"files\", \"databases\", \"data\">, max 3 items]}}."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.calculated_price":"string|integer","data.base_price":"string","data.features_price":"string","data.breakdown":"object","data.created_at":"string","data.vat_rate":"float","data.balance":"float","data.use_balance_first":"boolean","data.currency":"string","data.client_currency":"string"}},{"name":"update_plan","description":"DESTRUCTIVE - Update server resources (CPU, memory, disk). Initiates a plan update job. Read live_resize from GET /servers/{server}/plan/config first: when true, CPU and memory changes apply while the server keeps running (downgrades step down gradually with a health check between steps) and added disk space becomes available without a restart, so there is no downtime. When live_resize is false (servers not yet migrated to live resizing), the server restarts automatically to apply the change, causing a short downtime. Disk size can only be increased, never reduced. Fails with HTTP 409 when another task is already running on the server, retry after it finishes. While Smart Autoscale runs the server ABOVE its plan size (see data.autoscale in GET /servers/{server}/plan/config), the charge due today is the difference from the boosted level - a pick at or below the boosted size charges nothing today and only changes the recurring monthly price, and the picked size becomes the new plan size. A save at the exact current boosted size requires confirm_autoscale_rebase=true, otherwise it is rejected as no change. Shrinking memory below the server's computed safe minimum (see data.autoscale.fit in GET /servers/{server}/plan/config) is allowed but needs confirm_below_need=true. Without it the request fails with HTTP 400 and a message naming the smallest safe selectable size (or stating that no available size can safely cover current usage) - resend with confirm_below_need=true to proceed anyway. Websites on the server may fail for a while if it runs below the safe minimum. A shrink to a size at or above the safe minimum never needs confirm_below_need - when its fit check ran with partial data (degraded), the panel shows an informational note only.","method":"PUT","path_template":"/servers/{server}/plan","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"cpu","type":"integer","required":true,"description":"Number of CPU cores. Even values only, from 2 to 128"},{"name":"memory","type":"integer","required":true,"description":"Memory in GB. Even values only, from 4 to 256"},{"name":"disk","type":"integer","required":true,"description":"Disk space in GB. Keep the current size or increase it in steps of 20 GB, up to 2560"},{"name":"frontend_calculated_price","type":"float","required":true,"description":"The price calculated by calculate-price endpoint"},{"name":"price_calculation_hash","type":"string","required":true,"description":"Hash from generate-hash endpoint"},{"name":"payment_method_id","type":"integer","required":false,"description":"Payment method ID. Optional if the client has a default payment method on file - backend auto-falls back to it as a safety net. Required only when no method exists and immediate charge > 0."},{"name":"use_balance","type":"boolean","required":false,"description":"Whether to use account balance"},{"name":"confirm_autoscale_rebase","type":"boolean","required":false,"description":"Only meaningful while Smart Autoscale runs the server above its plan size. Set true to confirm keeping the picked size as the permanent plan when it equals the current boosted size - without it, a same-size save is rejected as no change. This confirms a recurring price change, so only send it on an explicit user decision."},{"name":"confirm_below_need","type":"boolean","required":false,"description":"Set true to confirm a memory shrink below the server's computed safe minimum (data.autoscale.fit in GET /servers/{server}/plan/config). Without it, a below-minimum shrink fails with HTTP 400 and a message naming the smallest safe selectable size (or stating that no available size can safely cover current usage). The fit is re-checked fresh on the server at execution time, and a confirmed request proceeds even if the need grew meanwhile. Websites on the server may fail for a while below the safe minimum, so only send it on an explicit user decision - including when the fit state is server_off or unavailable and the user confirmed proceeding without a completed check. A shrink to a size at or above the safe minimum never needs this flag - a partial-data (degraded) check on a fitting target is disclosed as an informational note, not a consent gate."},{"name":"features","type":"object","required":false,"description":"Optional features configuration object. Currently supports backup_frequency: {\"backup_frequency\": {\"frequency\": <integer 1-4, required when backup_frequency is present>, \"full_backup\": <boolean>, \"additional_kinds\": [<one or more of \"files\", \"databases\", \"data\">, max 3 items]}}."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_autoscale","description":"Get the server's Smart Autoscale settings and state: whether it is enabled, the size limit (a CPU and memory cap in catalog sizes, or the scale-as-much-as-needed option), the at-most monthly price of the allowed ceiling (max_monthly_price), the plan-size base versus the currently running size, whether a return toward the plan size is scheduled for renewal, a paid hold (paid_hold is set while a larger size is already paid for the current billing period - the server keeps that entitlement even after returning to its plan size), and eligibility with a plain-language reason when autoscale is not available (only servers with standard calculated pricing that support live resizing are eligible). price_inputs carries the raw USD pricing scalars a client can use to preview the monthly cost of any cap before saving (null on non-calculated pricing).","method":"GET","path_template":"/servers/{server}/plan/autoscale","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.enabled":"boolean","data.max_as_possible":"boolean","data.max_cpu":"integer|null","data.max_memory_gb":"integer|null","data.window_minutes":"integer","data.base_cpu":"integer|null","data.base_memory":"integer|null","data.current_cpu":"integer","data.current_memory":"integer","data.scaled_above_base":"boolean","data.revert_pending":"boolean","data.eligible":"boolean","data.paid_hold":"object|null","data.paid_hold.memory_gb":"integer","data.paid_hold.paid_through":"string","data.can_enable":"boolean","data.reason":"string|null","data.currency":"string|null","data.current_monthly_price":"float|null","data.boosted_monthly_price":"float|null","data.max_monthly_price":"float|null","data.defaults":"object","data.price_inputs":"object|null","data.defaults.threshold_percent":"integer","data.defaults.window_minutes":"integer","data.defaults.cooldown_minutes":"integer","data.defaults.cpu_step":"integer","data.defaults.cpu_min":"integer","data.defaults.memory_min":"integer","data.defaults.memory_step":"integer","data.defaults.cpu_max":"integer","data.defaults.memory_max":"integer"}},{"name":"create_autoscale","description":"Update the server's Smart Autoscale settings. Turning it on requires either BOTH resource caps (max_cpu and max_memory_gb) or the scale-as-much-as-needed option (max_as_possible). Turning it off is always allowed and never changes the current server size - a running boost still returns toward the plan size at renewal. Turning it off also cancels any automatic scaling charge that is still waiting to be taken (a charge already being processed at that moment still goes through), and lowering a cap likewise cancels a waiting charge for a size above the new cap. Absent fields keep their stored values (partial update), so sending only enabled=false preserves previously saved caps. At renewal the server returns to its plan size when usage allows - if usage still needs a larger size, it keeps as much of the size it already runs as usage needs - up to the smallest size that covers usage, never above the cap (the renewal never grows the server) - keeping a size above the plan charges a one-time prorated fee automatically for the new period (a prorated amount that rounds to zero charges nothing), and the client is notified (when the kept size is still below what usage needs, the notice warns that websites may fail).","method":"POST","path_template":"/servers/{server}/plan/autoscale","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"enabled","type":"boolean","required":true,"description":"Turn Smart Autoscale on or off. Enabling requires max_cpu plus max_memory_gb, or max_as_possible. Turning it off never changes the current server size, and it cancels any automatic scaling charge that is still waiting to be taken (a charge already being processed at that moment still goes through). Lowering a cap likewise cancels a waiting charge for a size above the new cap."},{"name":"max_as_possible","type":"boolean","required":false,"description":"Allow growing to the largest available size with no cap. When true the resource caps are ignored. Quote max_monthly_price from the GET as the at-most monthly cost."},{"name":"max_cpu","type":"integer","required":false,"description":"The most CPU cores automatic upgrades may reach, in whole cores on the plan catalog grid (defaults.cpu_min plus multiples of defaults.cpu_step, up to defaults.cpu_max), never below the server's plan size. Must be sent together with max_memory_gb when setting a specific cap. Omitting the field keeps the stored value, an explicit null clears it."},{"name":"max_memory_gb","type":"integer","required":false,"description":"The most memory in GB automatic upgrades may reach, on the plan catalog grid (defaults.memory_min plus multiples of defaults.memory_step, up to defaults.memory_max), never below the server's plan size. Must be sent together with max_cpu when setting a specific cap. Omitting the field keeps the stored value, an explicit null clears it."},{"name":"window_minutes","type":"integer","required":false,"description":"Trigger window in whole minutes, 1 to 20 (recommended 5): how long usage must stay continuously high before an automatic upgrade fires. Shorter reacts faster, but a brief burst can trigger a paid upgrade. Omitting the field keeps the stored value. Sending an explicit null resets the window to the recommended default, so clients that serialize absent fields as null will reset it unintentionally - omit the field instead."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.enabled":"boolean","data.max_as_possible":"boolean","data.max_cpu":"integer|null","data.max_memory_gb":"integer|null","data.window_minutes":"integer","data.base_cpu":"integer|null","data.base_memory":"integer|null","data.current_cpu":"integer","data.current_memory":"integer","data.scaled_above_base":"boolean","data.revert_pending":"boolean","data.eligible":"boolean","data.paid_hold":"object|null","data.paid_hold.memory_gb":"integer","data.paid_hold.paid_through":"string","data.can_enable":"boolean","data.reason":"string|null","data.currency":"string|null","data.current_monthly_price":"float|null","data.boosted_monthly_price":"float|null","data.max_monthly_price":"float|null","data.defaults":"object","data.price_inputs":"object|null","data.defaults.threshold_percent":"integer","data.defaults.window_minutes":"integer","data.defaults.cooldown_minutes":"integer","data.defaults.cpu_step":"integer","data.defaults.cpu_min":"integer","data.defaults.memory_min":"integer","data.defaults.memory_step":"integer","data.defaults.cpu_max":"integer","data.defaults.memory_max":"integer"}}]},{"name":"servers_rules","scope":"servers:rules","description":"Manage your servers including power controls, monitoring, blocked IPs, IP lists, and MySQL details.","actions":[{"name":"list_rules","description":"Get a paginated list of server rules.","method":"GET","path_template":"/servers/{server}/rules","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"section","type":"string","required":false,"description":"Rule section: ddos, waf, cache (default: ddos)"},{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page (max 100)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","meta":"object","meta.current_page":"integer","meta.last_page":"integer","meta.per_page":"integer","meta.total":"integer"}},{"name":"create_rules","description":"Create a new server rule.","method":"POST","path_template":"/servers/{server}/rules","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"section","type":"string","required":false,"description":"Rule section: ddos, waf, cache (default: ddos)"},{"name":"name","type":"string","required":true,"description":"Rule name"},{"name":"description","type":"string","required":false,"description":"Rule description (optional, max 1000 chars)"},{"name":"conditions","type":"object","required":true,"description":"Rule conditions tree - a JSON OBJECT, not an array. Shape: {\"logic\":\"and|or\",\"groups\":[{\"logic\":\"and|or\",\"conditions\":[{\"field\":\"<field>\",\"operator\":\"<operator>\",\"value\":\"<value>\",\"key\":\"<name, keyed fields only>\"}],\"groups\":[]}]}. Required: top-level logic (and|or) and groups (an array with at least 1 group). Each group carries its own logic (and|or), plus optional conditions and nested groups arrays. Each condition needs field (one of: ip, country, uri, uri_path, user_agent, referer, method, host, query_string, cookie, cookie_name, cookie_value, request_header, scheme, is_ajax, is_static), operator (one of: equals, not_equals, contains, not_contains, matches, not_matches, in_cidr, not_in_cidr, in_list, not_in_list, in_range, not_in_range), and value (for field=country use 2-letter country codes in capitals, e.g. US, IL, DE). key is required only when field is cookie_value or request_header (the cookie or header name)."},{"name":"action","type":"object","required":true,"description":"Rule action: object with type and optional params. For rate_limit (waf section only): {\"type\":\"rate_limit\",\"params\":{\"requests_per_minute\":N OR \"requests_per_second\":N (one required), \"on_exceed\":\"block_request|delay|exit_http_status\" (only these three values), \"burst\":N (optional), \"scope\":\"ip\" (default, per source IP) OR \"rule\" (one shared bucket across all matching IPs, the only way to bound distributed crawlers like Facebook). If on_exceed=delay also include delay_seconds (1-30). If on_exceed=exit_http_status also include status_code (400/401/402/403/404/405/429/444/500/502/503).}}. Activation note: a scope=rule limit is saved immediately, but it can keep counting per IP address for a while before the shared count starts. Do not treat the success response as proof that the shared limit is active. Check the actual behavior before you rely on it. Other action.type values per section: WAF accepts block_request, bypass, log, exit_http_status, rate_limit, delay (a bypass action may include params {\"targets\":[...]} = a non-empty list of one or more distinct values from \"proxy\", \"xmlrpc\", \"owasp\", skipping ONLY those protections, a granular bypass, e.g. proxy-only releases a datacenter or VPN IP block while keeping XML-RPC and OWASP on. Omit params.targets for a full WAF bypass. Prefer the granular target, and REQUIRE the source IP to match: the source IP is the only non-forgeable request attribute you can pin narrowly (a country match is also non-forgeable but nation-wide), so a bypass matched only on a forgeable one like User-Agent, path, or a header lets any forger lift the WAF - match the source IP (use in_cidr, a list of CIDRs, /32 for a single IPv4 host or /128 for a single IPv6 host) with 'and' logic and keep it as narrow as the case allows. This has no exception for a bypass rule: an OWASP rule misfiring on request SHAPE across many legitimate visitors, such as OWASP CRS 920450 on WooCommerce checkout, is not a bypass-rule case, so turn off that specific rule via PUT /servers/websites/{website}/waf/modsecurity-rules/toggle with {rule_id, enabled:false} instead (one atomic call that turns off just that one rule and keeps all other OWASP protection on), never a server-tier bypass, which would lift OWASP on that path for every tenant). DDoS accepts bypass and challenge (challenge is one action that always serves the strong automatic checking page. The legacy names invisible_challenge, interstitial_challenge, and captcha_challenge are still accepted on existing rules, but use challenge for any new rule). Cache accepts bypass_cache, purge, ignore. purge REQUIRES params: scope (matched_url or all) and device (both, desktop or mobile) - a matching request clears that page, or the whole site, from the Proginter page cache and itself skips the cache. matched_url clears the address the request asked for, so a webhook or a save that should clear a different page needs scope all. The website's own caching plugin is not cleared. Only the address the visitor used is cleared, plus its www form. If the website uses wildcard subdomains, a rule does not clear subdomain addresses, only the addresses set up on the website. A server-level purge rule fires on every website on the server for every matching request, so prefer a per-website purge rule and keep the conditions narrow."},{"name":"enabled","type":"boolean","required":false,"description":"Whether the rule is enabled"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"string - UUID of the newly created server rule (use for update/delete/toggle endpoints)"}},{"name":"update_reorder","description":"Reorder server rules.","method":"PUT","path_template":"/servers/{server}/rules/reorder","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"section","type":"string","required":false,"description":"Rule section: ddos, waf, cache (default: ddos)"},{"name":"order","type":"array","required":true,"description":"Array of rule IDs in desired order"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"update_toggle","description":"Toggle a server rule on or off.","method":"PUT","path_template":"/servers/{server}/rules/{rule}/toggle","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"},{"name":"rule","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"section","type":"string","required":false,"description":"Rule section: ddos, waf, cache (default: ddos)"},{"name":"rule_id","type":"string","required":true,"description":"The rule's UUID (max 100 chars), required in the request body. Use the same value as the {rule} URL segment."},{"name":"enabled","type":"boolean","required":true,"description":"Whether to enable or disable the rule"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"update_rules","description":"Update a server rule.","method":"PUT","path_template":"/servers/{server}/rules/{rule}","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"},{"name":"rule","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"section","type":"string","required":false,"description":"Rule section: ddos, waf, cache (default: ddos)"},{"name":"rule_id","type":"string","required":true,"description":"The rule's UUID (max 100 chars), required in the request body. Use the same value as the {rule} URL segment."},{"name":"name","type":"string","required":true,"description":"Rule name"},{"name":"description","type":"string","required":false,"description":"Rule description (optional, max 1000 chars)"},{"name":"conditions","type":"object","required":true,"description":"Rule conditions tree - a JSON OBJECT, not an array. Update replaces the whole rule, so conditions is required. Shape: {\"logic\":\"and|or\",\"groups\":[{\"logic\":\"and|or\",\"conditions\":[{\"field\":\"<field>\",\"operator\":\"<operator>\",\"value\":\"<value>\",\"key\":\"<name, keyed fields only>\"}],\"groups\":[]}]}. Required: top-level logic (and|or) and groups (an array with at least 1 group). Each group carries its own logic (and|or), plus optional conditions and nested groups arrays. Each condition needs field (one of: ip, country, uri, uri_path, user_agent, referer, method, host, query_string, cookie, cookie_name, cookie_value, request_header, scheme, is_ajax, is_static), operator (one of: equals, not_equals, contains, not_contains, matches, not_matches, in_cidr, not_in_cidr, in_list, not_in_list, in_range, not_in_range), and value (for field=country use 2-letter country codes in capitals, e.g. US, IL, DE). key is required only when field is cookie_value or request_header (the cookie or header name)."},{"name":"action","type":"object","required":true,"description":"Rule action (same shape as POST endpoint): object with type and optional params. For rate_limit (waf section only): {\"type\":\"rate_limit\",\"params\":{\"requests_per_minute\":N OR \"requests_per_second\":N (one required), \"on_exceed\":\"block_request|delay|exit_http_status\" (only these three values), \"burst\":N (optional), \"scope\":\"ip\" (default) OR \"rule\" (aggregate across all matching IPs). If on_exceed=delay also include delay_seconds (1-30). If on_exceed=exit_http_status also include status_code.}}. Activation note: a scope=rule limit is saved immediately, but the shared count can take a while to start. Check the actual behavior before you rely on it. Other action.type values per section: WAF accepts block_request, bypass, log, exit_http_status, rate_limit, delay (a bypass action may include params {\"targets\":[...]} = a non-empty list of one or more distinct values from \"proxy\", \"xmlrpc\", \"owasp\", skipping ONLY those protections, a granular bypass, e.g. proxy-only releases a datacenter or VPN IP block while keeping XML-RPC and OWASP on. Omit params.targets for a full WAF bypass. Prefer the granular target, and REQUIRE the source IP to match: the source IP is the only non-forgeable request attribute you can pin narrowly (a country match is also non-forgeable but nation-wide), so a bypass matched only on a forgeable one like User-Agent, path, or a header lets any forger lift the WAF - match the source IP (use in_cidr, a list of CIDRs, /32 for a single IPv4 host or /128 for a single IPv6 host) with 'and' logic and keep it as narrow as the case allows. This has no exception for a bypass rule: an OWASP rule misfiring on request SHAPE across many legitimate visitors, such as OWASP CRS 920450 on WooCommerce checkout, is not a bypass-rule case, so turn off that specific rule via PUT /servers/websites/{website}/waf/modsecurity-rules/toggle with {rule_id, enabled:false} instead (one atomic call that turns off just that one rule and keeps all other OWASP protection on), never a server-tier bypass, which would lift OWASP on that path for every tenant). DDoS accepts bypass and challenge (challenge is one action that always serves the strong automatic checking page. The legacy names invisible_challenge, interstitial_challenge, and captcha_challenge are still accepted on existing rules, but use challenge for any new rule). Cache accepts bypass_cache, purge, ignore. purge REQUIRES params: scope (matched_url or all) and device (both, desktop or mobile) - a matching request clears that page, or the whole site, from the Proginter page cache and itself skips the cache. matched_url clears the address the request asked for, so a webhook or a save that should clear a different page needs scope all. The website's own caching plugin is not cleared. Only the address the visitor used is cleared, plus its www form. If the website uses wildcard subdomains, a rule does not clear subdomain addresses, only the addresses set up on the website. A server-level purge rule fires on every website on the server for every matching request, so prefer a per-website purge rule and keep the conditions narrow."},{"name":"enabled","type":"boolean","required":false,"description":"Whether the rule is enabled"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_rules","description":"DESTRUCTIVE - Delete server rules. The reply states how many rule deletions were REQUESTED, not how many rules were removed: the whole id list is sent in one call, so an id that matches no rule is counted the same as one that does. Read the rules list back to confirm what is gone.","method":"DELETE","path_template":"/servers/{server}/rules","path_params":[{"name":"server","type":"string","required":true,"description":"URL parameter"}],"body_params":[{"name":"section","type":"string","required":false,"description":"Rule section: ddos, waf, cache (default: ddos)"},{"name":"ids","type":"array","required":true,"description":"Array of rule IDs to delete"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}}]},{"name":"webhooks","scope":"webhooks","description":"Configure outbound webhook subscriptions for security-scanner events (scan.completed, scan.failed, scan.blocked_by_waf, fix.applied). HMAC-SHA256 signed deliveries with retry queue.","actions":[{"name":"list_webhooks","description":"Paginated list of the authenticated client's outbound webhook subscriptions, most recent first. The secret is redacted ('***') on read responses. Full plaintext is shown only ONCE on POST /webhooks and POST /webhooks/{webhook}/regenerate-secret.","method":"GET","path_template":"/webhooks","path_params":[],"body_params":[{"name":"page","type":"integer","required":false,"description":"1-based page number (default 1)."},{"name":"per_page","type":"integer","required":false,"description":"Page size (default 20, max 100)."},{"name":"search","type":"string","required":false,"description":"Case-insensitive substring filter on the webhook URL (LIKE %search%). Empty/missing = no filter."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.items":"array","data.items[].id":"integer","data.items[].url":"string","data.items[].secret":"string (always '***' on read - plaintext shown only on create + regenerate-secret)","data.items[].event_types":"array of strings","data.items[].is_active":"boolean","data.items[].last_delivery_status":"string|null","data.items[].consecutive_failures":"integer","data.items[].created_at":"string|null","data.items[].updated_at":"string|null","data.pagination.page":"integer","data.pagination.per_page":"integer","data.pagination.total":"integer","data.pagination.has_more":"boolean"}},{"name":"create_webhooks","description":"Create a new outbound webhook subscription. The plaintext signing secret is returned ONCE in the response body - store it on your side. It will never be shown again. Max 50 subscriptions per customer, max 8 event types per subscription. URL must be HTTPS and publicly reachable (no loopback, private IPs, or panel apex). Throttled to 60 requests per minute.","method":"POST","path_template":"/webhooks","path_params":[],"body_params":[{"name":"url","type":"string","required":true,"description":"HTTPS endpoint that will receive POSTed events. Max 2048 chars. No userinfo (no user:password@), no private IPs, no loopback, no panel apex."},{"name":"event_types","type":"array","required":true,"description":"Non-empty array (max 8) of event keys to subscribe to. Allowed values: scan.completed, scan.failed, scan.blocked_by_waf, fix.applied. Call GET /webhooks/event-types for the live allowlist."},{"name":"secret","type":"string","required":false,"description":"Optional caller-supplied signing secret (16-128 chars). Omit to have the panel auto-generate a 32-char random secret. Either way the plaintext is returned ONCE in this response."},{"name":"is_active","type":"boolean","required":false,"description":"Whether the subscription is active immediately (default true). Inactive subscriptions do not receive deliveries."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.id":"integer","data.url":"string","data.secret":"string (plaintext - SHOWN ONCE in this response only)","data.event_types":"array of strings","data.is_active":"boolean","data.notice":"string"}},{"name":"update_webhooks","description":"Update an existing webhook subscription. To rotate the signing secret, use POST /webhooks/{webhook}/regenerate-secret instead - supplying a 'secret' field here returns 422.","method":"PUT","path_template":"/webhooks/{webhook}","path_params":[{"name":"webhook","type":"integer","required":true,"description":"Webhook subscription id (URL parameter)."}],"body_params":[{"name":"url","type":"string","required":true,"description":"HTTPS endpoint that will receive POSTed events. Same constraints as on create."},{"name":"event_types","type":"array","required":true,"description":"Non-empty array (max 8) of event keys to subscribe to."},{"name":"is_active","type":"boolean","required":false,"description":"Enable or disable deliveries to this subscription."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.id":"integer","data.url":"string","data.secret":"string (always '***' on update - plaintext shown only on create + regenerate-secret)","data.event_types":"array of strings","data.is_active":"boolean","data.last_delivery_status":"string|null","data.consecutive_failures":"integer","data.created_at":"string|null","data.updated_at":"string|null"}},{"name":"delete_webhooks","description":"DESTRUCTIVE - Delete a single webhook subscription by id. Delivery history rows are removed via cascade. The customer's endpoint will no longer receive events from this subscription.","method":"DELETE","path_template":"/webhooks/{webhook}","path_params":[{"name":"webhook","type":"integer","required":true,"description":"Webhook subscription id (URL parameter)."}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","data.id":"integer","data.deleted":"boolean"}},{"name":"create_regenerate_secret","description":"DESTRUCTIVE - Rotate the signing secret for an existing webhook subscription. The new plaintext secret is returned ONCE in this response - store it on your side. The previous secret is invalidated immediately (no grace window). Throttled to 10 requests per minute.","method":"POST","path_template":"/webhooks/{webhook}/regenerate-secret","path_params":[{"name":"webhook","type":"integer","required":true,"description":"Webhook subscription id (URL parameter)."}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","data.id":"integer","data.secret":"string (plaintext - SHOWN ONCE in this response only)","data.notice":"string"}},{"name":"list_event_types","description":"Live allowlist of event types a customer can subscribe to, with human-readable labels, descriptions, group label, and sample_data shape per event. Use to drive a picker UI or to confirm the wire format your endpoint will receive.","method":"GET","path_template":"/webhooks/event-types","path_params":[],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.items":"array","data.items[].value":"string","data.items[].label":"string","data.items[].group":"string","data.items[].description":"string","data.items[].sample_data":"object"}},{"name":"list_deliveries","description":"Paginated audit history of delivery attempts for one subscription, most recent first. Optional status filter (pending|delivered|failed).","method":"GET","path_template":"/webhooks/{webhook}/deliveries","path_params":[{"name":"webhook","type":"integer","required":true,"description":"Webhook subscription id (URL parameter)."}],"body_params":[{"name":"page","type":"integer","required":false,"description":"1-based page number (default 1)."},{"name":"per_page","type":"integer","required":false,"description":"Page size (default 25, max 100)."},{"name":"status","type":"string","required":false,"description":"Filter to one of: pending, delivered, failed. Other values are ignored."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.items":"array","data.items[].id":"integer","data.items[].event_id":"string","data.items[].event_type":"string","data.items[].status":"string (pending|delivered|failed)","data.items[].attempts":"integer","data.items[].last_response_code":"integer|null","data.items[].last_response_excerpt":"string|null","data.items[].last_attempt_at":"string|null","data.items[].delivered_at":"string|null","data.items[].created_at":"string|null","data.pagination.page":"integer","data.pagination.per_page":"integer","data.pagination.total":"integer","data.pagination.has_more":"boolean"}},{"name":"create_retry","description":"Manually re-queue a failed delivery for one more attempt. Only deliveries in the failed state can be retried. The parent subscription must be active. Throttled to 30 requests per minute.","method":"POST","path_template":"/webhooks/{webhook}/deliveries/{delivery}/retry","path_params":[{"name":"webhook","type":"integer","required":true,"description":"Webhook subscription id (URL parameter)."},{"name":"delivery","type":"integer","required":true,"description":"Delivery row id (URL parameter)."}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.id":"integer","data.status":"string"}}]},{"name":"websites","scope":"websites","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"get_websites","description":"Get a paginated list of websites, optionally filtered by server. If your API key is limited to specific websites, the list contains only those, and the paging totals count only those. A key that was never limited to particular websites sees everything, as before.","method":"GET","path_template":"/servers/websites/{serverId?}","path_params":[{"name":"serverId","type":"integer","required":false,"description":"Server ID to filter websites (URL parameter)"}],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number for pagination"},{"name":"per_page","type":"integer","required":false,"description":"Number of items per page (max 100)"},{"name":"statuses","type":"array","required":false,"description":"Filter by statuses: pending, installing, active, suspended, migrating"},{"name":"stages","type":"boolean","required":false,"description":"Include stage websites (default: false)"},{"name":"exclude_shared","type":"boolean","required":false,"description":"Exclude shared websites (default: false). Pass true to hide websites the caller can access but does not own."},{"name":"options","type":"boolean","required":false,"description":"Set to 1 to get simplified options format for dropdowns"},{"name":"search","type":"string","required":false,"description":"Search websites by name or domain"},{"name":"client_website_group_id","type":"integer|string","required":false,"description":"Filter websites by group ID. Pass the literal string 'none', the integer 0, or the string '0' to return only ungrouped websites. Omit or pass an empty string to return all websites regardless of group."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"integer","data[].name":"string","data[].domain":"string","data[].utf_domain":"string","data[].status":"string","data[].username":"string","data[].is_stage":"boolean","data[].group_id":"integer|null","data[].group_name":"string|null","data[].group_color":"string|null (hex color)","data[].server":"object|null","meta":"object"}},{"name":"get_show","description":"Get detailed information about a specific website including server, cluster, and permission info.","method":"GET","path_template":"/servers/websites/show/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"checkIfWebActive","type":"boolean","required":false,"description":"Check if website services are active"},{"name":"includeAggregatedTasks","type":"boolean","required":false,"description":"Include aggregated tasks list"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.id":"integer","data.name":"string","data.domain":"string","data.utf_domain":"string","data.status":"string","data.username":"string","data.is_owner":"boolean","data.permission_type":"string|null","data.has_issues":"boolean","data.has_critical_issues":"boolean","data.has_active_scan_or_fix":"boolean","data.has_blocking_tasks":"boolean","data.group_id":"integer|null","data.group_name":"string|null","data.group_color":"string|null (hex color)","data.server":"object|null"}},{"name":"get_framework","description":"Get the detected framework/CMS of a website (e.g., WordPress, Laravel, etc.).","method":"GET","path_template":"/servers/websites/framework/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.framework":"string|null","data.version":"string|null"}},{"name":"get_usage","description":"Get disk and bandwidth usage statistics for a website. data.waf is false when the WAF is off, otherwise the mode name (Balanced, Strict or Advanced) with ' (log only)' appended while log-only mode is on. data.cache and data.ddos_protection are false or the mode name.","method":"GET","path_template":"/servers/websites/usage/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.disk":"object","data.disk.result":"object","data.disk.result.total":"integer","data.disk.result.files":"integer","data.disk.result.mysql":"integer","data.disk.result.mail":"integer","data.disk.time":"integer","data.bandwidth":"object","data.bandwidth.result":"float","data.bandwidth.time":"integer","data.cache":"boolean|string","data.waf":"boolean|string","data.ddos_protection":"boolean|string","data.php_version":"float","data.nodejs_main_version":"string|null","data.python_main_version":"string|null","data.databases":"integer","data.mailboxes":"integer","data.forwarders":"integer","data.ftp":"integer","data.redirects":"integer","data.pointers":"integer","data.cronjobs":"integer","data.stages":"integer"}},{"name":"get_quota_details","description":"Get detailed quota information for a website including limits and current usage.","method":"GET","path_template":"/servers/websites/quota-details/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.result":"object","data.result.total":"integer","data.result.files":"integer","data.result.mysql":"integer","data.result.postgres":"integer","data.result.mail":"integer","data.result.largest_files":"array","data.time":"integer"}},{"name":"get_traffic_overview","description":"Get traffic overview statistics including visitors, requests, countries, and platforms.","method":"GET","path_template":"/servers/websites/traffic-overview/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"timeframe","type":"string","required":false,"description":"Timeframe: today, this_month, or number of days (1-30)"},{"name":"tz","type":"string","required":false,"description":"Timezone override (e.g., America/New_York)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.total":"object","data.total.visitors":"integer","data.total.requests":"integer","data.total.challenge":"integer","data.total.waf":"integer","data.total.dynamic":"integer","data.total.static":"integer","data.total.ajax":"integer","data.total.mobile":"integer","data.total.desktop":"integer","data.timestamps":"object","data.requests":"object","data.countries":"object"}},{"name":"create_websites","description":"Create a new website on a server. If your API key was limited to particular resources, on either axis, the server you name here has to be one the key covers, or the call is refused with 403. A key its owner never limited to any resource is not affected.","method":"POST","path_template":"/servers/websites","path_params":[],"body_params":[{"name":"server_id","type":"integer","required":true,"description":"Server ID to create the website on"},{"name":"name","type":"string","required":true,"description":"Website display name"},{"name":"free_subdomain","type":"boolean","required":true,"description":"Use a free auto-generated subdomain instead of a custom domain"},{"name":"subdomain","type":"string","required":false,"description":"Optional label for the free subdomain (letters, numbers, hyphens, max 63 characters). The site becomes label.proginter.app. A taken or reserved label falls back to a random one. Only used when free_subdomain is true. The response carries the final domain."},{"name":"domain","type":"string","required":false,"description":"Domain name (required when free_subdomain is false)"},{"name":"unlimited_disk","type":"boolean","required":true,"description":"Whether disk space is unlimited"},{"name":"max_disk_gb","type":"integer","required":false,"description":"Max disk space in GB (required when unlimited_disk is false)"},{"name":"unlimited_bandwidth","type":"boolean","required":true,"description":"Whether bandwidth is unlimited"},{"name":"max_bandwidth_gb","type":"integer","required":false,"description":"Max bandwidth in GB (required when unlimited_bandwidth is false)"},{"name":"never_suspend","type":"boolean","required":false,"description":"Prevent automatic suspension (default: false)"},{"name":"php_version","type":"string","required":false,"description":"PHP version to use (e.g., '8.2')"},{"name":"limit_cpu","type":"boolean","required":true,"description":"Whether to limit CPU cores"},{"name":"cpu_cores","type":"integer","required":false,"description":"Number of CPU cores (required when limit_cpu is true)"},{"name":"limit_memory","type":"boolean","required":true,"description":"Whether to limit memory"},{"name":"memory_limit_gb","type":"integer","required":false,"description":"Memory limit in GB (required when limit_memory is true)"},{"name":"install_wordpress","type":"boolean","required":true,"description":"Install WordPress during creation"},{"name":"wp_name","type":"string","required":false,"description":"WordPress site title (required when install_wordpress is true)"},{"name":"wp_email","type":"string","required":false,"description":"WordPress admin email (required when install_wordpress is true)"},{"name":"wp_language","type":"string","required":false,"description":"WordPress language code (required when install_wordpress is true)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.website.id":"integer","data.website.username":"string","data.website.domain":"string","data.website.name":"string","data.is_free_subdomain":"boolean","data.nameservers":"array"}},{"name":"update_websites","description":"CONDITIONALLY DESTRUCTIVE - Update website settings such as name, domain, disk/bandwidth limits, www preference, and HTTP settings. Sending a domain value needs confirmation, because a NEW domain value starts a full primary-domain change and the old domain stops serving. Sending the website's current domain unchanged is a no-op on the server, but it still triggers the confirmation - omit the domain field from updates that do not change it, to avoid the prompt. Updates without a domain value need no confirmation.","method":"PUT","path_template":"/servers/websites/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"name","type":"string","required":false,"description":"New website name"},{"name":"domain","type":"string","required":false,"description":"New primary domain. Changing it starts a full domain change: the website's files, database references, DNS zone and mail move to the new domain, and the old domain stops serving."},{"name":"unlimited_disk","type":"boolean","required":false,"description":"Set disk space to unlimited"},{"name":"max_disk_gb","type":"integer","required":false,"description":"Max disk space in GB"},{"name":"unlimited_bandwidth","type":"boolean","required":false,"description":"Set bandwidth to unlimited"},{"name":"max_bandwidth_gb","type":"integer","required":false,"description":"Max bandwidth in GB"},{"name":"never_suspend","type":"boolean","required":false,"description":"Prevent automatic suspension"},{"name":"www","type":"string","required":false,"description":"WWW preference: 'none', 'www', or 'non-www'"},{"name":"allow_http","type":"boolean","required":false,"description":"Allow HTTP access"},{"name":"maintenance","type":"boolean","required":false,"description":"Enable maintenance mode during domain change"}],"requires_confirmation":false,"prerequisites":[],"requires_confirmation_when":{"domain":true},"response_structure":{"success":"boolean","data.domain_change_queued":"boolean","data.replace_www_queued":"boolean"}},{"name":"delete_websites","description":"DESTRUCTIVE - Delete a website. This is a RECOVERABLE delete: the website is suspended and moved to Deleted Websites, where it can be restored for 7 days before it is permanently removed (it is also removed automatically once the 7 days elapse). Use POST /servers/websites/deleted/{websiteId}/restore to bring it back, or DELETE /servers/websites/deleted/{websiteId} to remove it permanently now. Refused while the website has unfinished or failed work in progress, while the account has an overdue balance, or for the panel's own website. Requires owner or server-level full permission.","method":"DELETE","path_template":"/servers/websites/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_deleted","description":"List your soft-deleted (recoverable) websites. Each delete moves a website here for 7 days before it is permanently removed. Optionally pass a server ID in the URL to list only that server's deleted websites, or omit it to list deleted websites across all of your servers. Ordered soonest-expiry first. Each row carries is_deleting (a permanent delete is currently in progress) and is_restoring (a restore is currently in progress). While either is true the website is busy and a restore or permanent-delete call on it will be refused until it finishes.","method":"GET","path_template":"/servers/websites/deleted/{serverId?}","path_params":[{"name":"serverId","type":"integer","required":false,"description":"Server ID (URL parameter). Optional - omit to list deleted websites across all of your servers."}],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number (default 1)."},{"name":"per_page","type":"integer","required":false,"description":"Items per page (default 100, max 100)."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"integer","data[].domain":"string","data[].status_before_soft_delete":"string","data[].soft_deleted_at":"string","data[].soft_deleted_expires_at":"string","data[].is_deleting":"boolean","data[].is_restoring":"boolean","meta":"object","meta.current_page":"integer","meta.last_page":"integer","meta.per_page":"integer","meta.total":"integer","meta.has_more":"boolean"}},{"name":"restore","description":"Restore a soft-deleted website to its exact prior state (un-suspended, with all of its files and data, no backup needed). The website must currently be in Deleted Websites. The response message is 'Website restored.' when the restore completes instantly (no un-suspend job is needed) or 'Website restore has been started.' when an un-suspend job is dispatched. Requires owner or server-level full permission.","method":"POST","path_template":"/servers/websites/deleted/{websiteId}/restore","path_params":[{"name":"websiteId","type":"integer","required":true,"description":"Deleted website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_deleted","description":"DESTRUCTIVE - Permanently delete a soft-deleted website now, before its 7-day recovery window ends. This is IRREVERSIBLE and tears down the website together with its staging environments. The website must currently be in Deleted Websites. Refused while it has unfinished or failed work in progress, while the account has an overdue balance, or for the panel's own website. Requires owner or server-level full permission.","method":"DELETE","path_template":"/servers/websites/deleted/{websiteId}","path_params":[{"name":"websiteId","type":"integer","required":true,"description":"Deleted website ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_clone","description":"Clone a website to a new website on the same or different server. If your API key was limited to particular resources, on either axis, the target server has to be one the key covers, or the call is refused with 403. A key its owner never limited to any resource is not affected.","method":"POST","path_template":"/servers/websites/{website}/clone","path_params":[{"name":"website","type":"integer","required":true,"description":"Source website ID (URL parameter)"}],"body_params":[{"name":"server_id","type":"integer","required":true,"description":"Target server ID"},{"name":"name","type":"string","required":true,"description":"Name for the new cloned website"},{"name":"free_subdomain","type":"boolean","required":true,"description":"Use a free auto-generated subdomain"},{"name":"domain","type":"string","required":false,"description":"Domain for the clone (required when free_subdomain is false)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data.website.id":"integer","data.website.username":"string","data.website.domain":"string","data.website.name":"string","data.is_free_subdomain":"boolean","warnings":"array|null"}},{"name":"create_copy","description":"DESTRUCTIVE - Copy website content to another existing website.","method":"POST","path_template":"/servers/websites/{website}/copy","path_params":[{"name":"website","type":"integer","required":true,"description":"Source website ID (URL parameter)"}],"body_params":[{"name":"target_website_id","type":"integer","required":true,"description":"Target website ID to copy content to"},{"name":"maintenance","type":"boolean","required":false,"description":"Enable maintenance mode during copy (default: false)"},{"name":"copy_anything","type":"boolean","required":false,"description":"Copy everything (default: true)"},{"name":"copy_files","type":"boolean","required":false,"description":"Copy files (default: true)"},{"name":"copy_databases","type":"boolean","required":false,"description":"Copy databases (default: true)"},{"name":"copy_cronjobs","type":"boolean","required":false,"description":"Copy cronjobs (default: true)"},{"name":"include_paths","type":"array","required":false,"description":"Specific file paths to include"},{"name":"databases_config","type":"string","required":false,"description":"JSON array selecting which databases and tables to copy. Each item is an object with a database (the database name) and tables (either the string all, or a list of specific table names). Leave empty to copy all databases and their tables of both engines. PostgreSQL databases are named through the reserved entry <username>__postgres: an item with database \"<username>__postgres\" and tables all copies every PostgreSQL database, and one whose tables list holds PostgreSQL database names (for example [\"shop\"]) copies just those - each is always copied whole. A selection that names no PostgreSQL database leaves the target's PostgreSQL databases untouched. Only used when copy_databases is true."},{"name":"preserve_production_data","type":"boolean","required":false,"description":"Preserve WordPress production data (default: false)"},{"name":"sync_php_version","type":"boolean","required":false,"description":"Sync PHP version to target (default: true)"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","warnings":"array|null"}},{"name":"create_migrate","description":"DESTRUCTIVE - Migrate a website to a different server. If your API key was limited to particular resources, on either axis, the target server has to be one the key covers, or the call is refused with 403. A key its owner never limited to any resource is not affected.","method":"POST","path_template":"/servers/websites/{website}/migrate","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID to migrate (URL parameter)"}],"body_params":[{"name":"server_id","type":"integer","required":true,"description":"Target server ID"},{"name":"maintenance","type":"boolean","required":false,"description":"Enable maintenance mode during migration (default: false)"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","warnings":"array|null"}},{"name":"create_suspend","description":"Suspend an active website. Refused if the website has unfinished or failed work in progress (until that work completes), if the account has an overdue balance (settle it first), or if it is the panel's own website (never suspendable). Check the has_blocking_tasks flag on the website details response before calling.","method":"POST","path_template":"/servers/websites/{website}/suspend","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"reason","type":"string","required":true,"description":"Reason for suspension (max 255 characters)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_unsuspend","description":"Unsuspend a suspended website.","method":"POST","path_template":"/servers/websites/{website}/unsuspend","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_recalculate_quota","description":"Recalculate website disk quota and return updated details.","method":"POST","path_template":"/servers/websites/{website}/recalculate-quota","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object"}},{"name":"delete_quota_files","description":"DESTRUCTIVE - Delete files contributing to quota and recalculate.","method":"DELETE","path_template":"/servers/websites/{website}/quota-files","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"paths","type":"array","required":true,"description":"Array of file paths to delete"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","data":"object"}},{"name":"get_is_online","description":"Check if a website is currently online and reachable.","method":"GET","path_template":"/servers/websites/{website}/is-online","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object"}},{"name":"get_health","description":"Get website health diagnostics data.","method":"GET","path_template":"/servers/websites/{website}/health","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object"}},{"name":"get_history","description":"Get the last 12 health-scan results for a website. Returns an empty list if the site has never been scanned.","method":"GET","path_template":"/servers/websites/{website}/health/history","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"history":"array","history[].score":"integer|null","history[].security":"integer|null","history[].performance":"integer|null","history[].seo":"integer|null","history[].ssl":"integer|null","history[].issues":"integer","history[].date":"string (ISO 8601)"}},{"name":"create_rescan","description":"Trigger a fresh health scan for a website. Responds 202 Accepted with a scan ID. The scan runs in the background. If a scan is already pending or running for this website, the existing scan's id is returned instead of queuing a duplicate - safe to retry on timeout without stacking scans. Poll /servers/websites/{website}/health or /servers/websites/{website}/health/history to observe progress.","method":"POST","path_template":"/servers/websites/{website}/health/rescan","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"message":"string","scan_id":"integer"}},{"name":"get_activity","description":"Get recent website activity logs (last 20 entries).","method":"GET","path_template":"/servers/websites/{website}/activity","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"integer","data[].actor":"object|null","data[].description":"string","data[].action":"string|null - machine-readable action key. Terminal-failure values today: 'purchase.failed', 'plan.update_failed', 'autoscale.charge_failed'. Rows written by an HTTP request carry the controller method name instead. Key on this rather than display_method to tell a failed action from a successful one","data[].display_method":"string","data[].created_at":"string"}},{"name":"list_bot_names","description":"Get list of known bot names and AI crawler names.","method":"GET","path_template":"/servers/websites/bot-names","path_params":[],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.bots":"object","data.ai_bots":"object"}},{"name":"list_wp_languages","description":"Get list of available WordPress languages for installation.","method":"GET","path_template":"/servers/websites/wp-languages","path_params":[],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array"}},{"name":"list_php_installed_versions","description":"Get PHP versions installed on the website's server.","method":"GET","path_template":"/servers/websites/{website}/php-installed-versions","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array"}},{"name":"get_status","description":"Latest task status for a website. Status is one of: idle, pending, running, failed, completed. Matches the panel's task status badge semantics. If a migration into a brand new website failed on the source side and that new website was then removed automatically, this returns status=failed with rolled_back=true plus a rollback_outcome and a plain-language message (instead of a 404) for as long as the migration summary is on record.","method":"GET","path_template":"/servers/websites/{websiteId}/tasks/status","path_params":[{"name":"websiteId","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.status":"string","data.task_id":"integer|null","data.step":"string|null","data.error":"string|null","data.finished_at":"string|null","data.issue_id":"integer|null","data.partial_success":"boolean","data.rolled_back":"boolean (present only when an automatic rollback removed the website)","data.rollback_outcome":"string|null (deleted, restored, no_changes, report_only, failed, or superseded)","data.message":"string|null (plain-language rollback summary, present on a rolled-back result)"}},{"name":"delete_tasks","description":"DESTRUCTIVE - Cancel a pending task for a website before its background job begins executing. Returns 400 if the task has already started. Requires full permission on the website.","method":"DELETE","path_template":"/servers/websites/{website}/tasks/{taskId}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"taskId","type":"integer","required":true,"description":"Task ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"list_templates","description":"Get page builder templates.","method":"GET","path_template":"/servers/websites/{website}/page-builder/templates","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of templates"}},{"name":"create_templates","description":"Save a page builder template.","method":"POST","path_template":"/servers/websites/{website}/page-builder/templates","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"name","type":"string","required":true,"description":"Template name"},{"name":"category","type":"string","required":false,"description":"Template category"},{"name":"html","type":"string","required":true,"description":"Template HTML content"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.id":"integer","data.name":"string","data.html":"string"}},{"name":"delete_templates","description":"DESTRUCTIVE - Delete a page builder template.","method":"DELETE","path_template":"/servers/websites/{website}/page-builder/templates","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"template_id","type":"integer","required":true,"description":"Template ID to delete"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}}]},{"name":"websites_addons","scope":"websites:addons","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"list_addons","description":"Get all addons overview with their current status and settings.","method":"GET","path_template":"/servers/websites/{website}/addons","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.accessibility":"object (accessibility addon status)","data.accessibility.enable":"boolean","data.gdpr":"object (GDPR addon status)","data.gdpr.enable":"boolean - absent whenever data.gdpr.settings_unreadable is present: the cookie banner settings shown are then the standard ones and the site's own switch cannot be read (see the dedicated GET)","data.gdpr.cookie_banner":"boolean","data.gdpr.settings_size_bytes":"integer|null (the stored cookie banner settings' size in bytes)","data.gdpr.settings_size_limit":"integer (the largest size a save may leave, 512 KB)","data.gdpr.settings_unreadable":"string (too_big, damaged or unavailable) - present only when the stored settings could not be read and the standard settings are returned in their place. unavailable means the file could not be read just now, a temporary condition: settings_size_bytes is then null and a PUT is refused until the file can be read again. Visitors see the standard banner meanwhile, even if the site's own banner was off. If it does not clear within a few minutes, the file may need to be reset by an operator: contact support","data.gdpr.settings_categories_problem":"string - present only when the stored cookie categories cannot be used as saved (a restored or hand-edited list the rules refuse): the reason in a few words. Visitors get the standard cookie categories until a save fixes the list. Read-only, dropped on a PUT","data.security_txt":"object (security.txt addon status)","data.security_txt.enable":"boolean","data.console":"object (console addon status)","data.console.enable":"boolean","data.console.allowed_ips":"array","data.analytics":"object (analytics addon status)","data.analytics.enable":"boolean","data.analytics.provider":"string","data.clarity":"object (Clarity addon status)","data.clarity.enable":"boolean","data.shabbat":"object (Shabbat addon status)","data.shabbat.enable":"boolean","data.shabbat.enabled_holidays":"array"}},{"name":"get_accessibility","description":"Get accessibility addon settings.","method":"GET","path_template":"/servers/websites/{website}/addons/accessibility","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.enable":"boolean","data.langs":"array of strings","data.default_lang":"string","data.location_vertical":"string","data.location_horizontal":"string","data.mobile_location_vertical":"string","data.mobile_location_horizontal":"string","data.space_vertical":"integer","data.space_horizontal":"integer","data.mobile_space_vertical":"integer","data.mobile_space_horizontal":"integer","data.mode":"string","data.statement_link":"string","data.statement_title":"string","data.statement_body":"string","data.exclude_uri":"array of strings","data.gdpr_category":"string"}},{"name":"update_accessibility","description":"Update accessibility addon settings. This endpoint replaces the whole configuration, so every core field is required.","method":"PUT","path_template":"/servers/websites/{website}/addons/accessibility","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"enable","type":"boolean","required":true,"description":"Enable/disable addon"},{"name":"langs","type":"array","required":true,"description":"Supported languages, at least one item. A JSON array or a JSON-encoded string. Allowed values: en, he, ar"},{"name":"default_lang","type":"string","required":true,"description":"Default language. Must be one of the supported languages. Allowed values: en, he, ar"},{"name":"location_vertical","type":"string","required":true,"description":"Widget vertical position on desktop. Allowed values: top, bottom"},{"name":"location_horizontal","type":"string","required":true,"description":"Widget horizontal position on desktop. Allowed values: left, right"},{"name":"mobile_location_vertical","type":"string","required":true,"description":"Widget vertical position on mobile. Allowed values: top, bottom"},{"name":"mobile_location_horizontal","type":"string","required":true,"description":"Widget horizontal position on mobile. Allowed values: left, right"},{"name":"space_vertical","type":"integer","required":true,"description":"Vertical offset in pixels on desktop (0-1000)"},{"name":"space_horizontal","type":"integer","required":true,"description":"Horizontal offset in pixels on desktop (0-1000)"},{"name":"mobile_space_vertical","type":"integer","required":true,"description":"Vertical offset in pixels on mobile (0-1000)"},{"name":"mobile_space_horizontal","type":"integer","required":true,"description":"Horizontal offset in pixels on mobile (0-1000)"},{"name":"mode","type":"string","required":true,"description":"Widget color mode. Allowed values: light, dark"},{"name":"statement_link","type":"string","required":false,"description":"URL of the accessibility statement page"},{"name":"statement_title","type":"string","required":false,"description":"Accessibility statement title (used when no statement link is set)"},{"name":"statement_body","type":"string","required":false,"description":"Accessibility statement text (used when no statement link is set)"},{"name":"gdpr_category","type":"string","required":false,"description":"Cookie consent category that gates the widget (letters, numbers, hyphens, underscores). Leave it empty to fall back to the necessary category. The widget then still goes through the consent runner, and because the necessary category is always on, it loads as soon as the runner starts. When the cookie banner is switched off for the website, the widget loads straight away"},{"name":"exclude_uri","type":"array","required":false,"description":"Relative paths where the widget is not injected, each starting with /. A JSON array or a JSON-encoded string"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_security_txt","description":"Get security.txt addon settings.","method":"GET","path_template":"/servers/websites/{website}/addons/security-txt","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.enable":"boolean","data.contact":"string","data.expires":"string","data.auto_renew":"boolean","data.encryption":"string","data.acknowledgments":"string","data.policy":"string","data.hiring":"string","data.preferred_languages":"array of strings","data.owner_defaults":"object|null","data.owner_defaults.contact":"string|null","data.owner_defaults.policy":"string|null","data.is_company_server":"boolean"}},{"name":"update_security_txt","description":"Update security.txt addon settings.","method":"PUT","path_template":"/servers/websites/{website}/addons/security-txt","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"enable","type":"boolean","required":true,"description":"Enable/disable security.txt"},{"name":"contact","type":"string","required":true,"description":"Contact email (mailto: form) or URL"},{"name":"expires","type":"string","required":false,"description":"Expiry date (ISO 8601, e.g. 2027-12-31T23:59:59Z)"},{"name":"encryption","type":"string","required":false,"description":"Encryption key URL"},{"name":"acknowledgments","type":"string","required":false,"description":"Acknowledgments page URL"},{"name":"policy","type":"string","required":false,"description":"Security policy URL"},{"name":"hiring","type":"string","required":false,"description":"Security jobs page URL"},{"name":"auto_renew","type":"boolean","required":false,"description":"Auto-renew expiry"},{"name":"preferred_languages","type":"array","required":false,"description":"Preferred response languages (e.g. en, he). A JSON array or a JSON-encoded string"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_console","description":"Get console addon settings.","method":"GET","path_template":"/servers/websites/{website}/addons/console","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.enable":"boolean","data.position":"string","data.allowed_ips":"array of strings","data.gdpr_category":"string"}},{"name":"update_console","description":"Update console addon settings.","method":"PUT","path_template":"/servers/websites/{website}/addons/console","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"enable","type":"boolean","required":true,"description":"Enable/disable console"},{"name":"position","type":"string","required":true,"description":"Console position. Allowed values: bottom-right, bottom-left, top-right, top-left"},{"name":"allowed_ips","type":"array","required":false,"description":"Public IP addresses that can see the console (empty means everyone). A JSON array or a JSON-encoded string"},{"name":"gdpr_category","type":"string","required":false,"description":"Cookie consent category that gates the console (letters, numbers, hyphens, underscores). Leave it empty to fall back to the preferences category. Visitors can refuse preferences, so on a site with the cookie banner on, an empty value means the console does not load until a visitor accepts that category. When the cookie banner is switched off for the website, the console loads straight away"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"list_analytics","description":"Get analytics addon settings.","method":"GET","path_template":"/servers/websites/{website}/addons/analytics","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.enable":"boolean","data.provider":"string","data.tracking_id":"string","data.anonymize_ip":"boolean","data.cookie_domain":"string","data.exclude_uri":"array of strings","data.gdpr_category":"string"}},{"name":"update_analytics","description":"Update analytics addon settings.","method":"PUT","path_template":"/servers/websites/{website}/addons/analytics","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"enable","type":"boolean","required":true,"description":"Enable/disable analytics"},{"name":"provider","type":"string","required":true,"description":"Analytics provider. Allowed values: google, matomo, plausible, fathom, umami"},{"name":"tracking_id","type":"string","required":false,"description":"Tracking ID. Optional on this endpoint - the stored ID is used when omitted"},{"name":"anonymize_ip","type":"boolean","required":false,"description":"Anonymize visitor IP addresses"},{"name":"cookie_domain","type":"string","required":false,"description":"Cookie domain (default: auto)"},{"name":"gdpr_category","type":"string","required":false,"description":"Cookie consent category that gates the tracker (letters, numbers, hyphens, underscores). Leave it empty to fall back to the analytics category. Visitors can refuse analytics, so on a site with the cookie banner on, an empty value means the tracker does not load until a visitor accepts that category. When the cookie banner is switched off for the website, the tracker loads straight away"},{"name":"exclude_uri","type":"array","required":false,"description":"Relative paths where the tracker is not injected, each starting with /. A JSON array or a JSON-encoded string"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_clarity","description":"Get Microsoft Clarity addon settings.","method":"GET","path_template":"/servers/websites/{website}/addons/clarity","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.enable":"boolean","data.project_id":"string","data.exclude_uri":"array of strings","data.include_uri":"array of strings","data.gdpr_category":"string"}},{"name":"update_clarity","description":"Update Microsoft Clarity addon settings.","method":"PUT","path_template":"/servers/websites/{website}/addons/clarity","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"enable","type":"boolean","required":true,"description":"Enable/disable Clarity"},{"name":"project_id","type":"string","required":false,"description":"Clarity project ID. Optional on this endpoint - the stored ID is used when omitted"},{"name":"gdpr_category","type":"string","required":false,"description":"Cookie consent category that gates the tracker (letters, numbers, hyphens, underscores). Leave it empty to fall back to the analytics category. Visitors can refuse analytics, so on a site with the cookie banner on, an empty value means the tracker does not load until a visitor accepts that category. When the cookie banner is switched off for the website, the tracker loads straight away"},{"name":"exclude_uri","type":"array","required":false,"description":"Relative paths where the tracker is not injected, each starting with /. A JSON array or a JSON-encoded string"},{"name":"include_uri","type":"array","required":false,"description":"Relative paths where the tracker IS injected (empty means everywhere), each starting with /. A JSON array or a JSON-encoded string"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_gdpr","description":"Get GDPR compliance addon settings. The response also carries settings_size_bytes, the size of the stored settings in bytes (null when it could not be measured), and settings_size_limit, the largest size a save may leave (512 KB). A stored size over the limit means visitors see the standard banner texts, design and cookie policy page until a smaller save, with the site's own on/off switches and cookie categories where they can be read. Where they cannot be read, the banner stays on and the visitor data-request pages are off. settings_categories_problem, when present, means the stored cookie categories cannot be used as saved (a restored or hand-edited list the rules refuse) and visitors get the standard categories until a save fixes them. When the stored settings cannot be read at all the response carries settings_unreadable and the standard settings in their place. The value too_big means the stored file is over 8 MB, which only a restored backup can produce. The value damaged means the file does not parse. In both states the returned enable is the standard value, false, while visitors still see the banner on, so set enable deliberately on the next PUT rather than sending the standard settings back. In both states the next PUT replaces the unreadable file with the settings it carries, as long as they fit the limit and drop no cookie category another addon points at (such a PUT is refused, as in any state). The value unavailable means the file could not be read just now (a temporary condition, not damage): settings_size_bytes is null, nothing is replaced, and a PUT is refused until the file can be read again. Visitors see the standard banner meanwhile, even if the site's own banner was off. If it does not clear within a few minutes, the file may need to be reset by an operator: contact support - retry later. None of these keys is a setting: sending them back on the PUT is harmless, they are ignored.","method":"GET","path_template":"/servers/websites/{website}/addons/gdpr","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":{"enable":"boolean - absent whenever settings_unreadable is present (too_big, damaged or unavailable): the settings returned are then the standard ones and the site's own switch cannot be read, while the site keeps showing a banner to visitors, so a PUT must set enable on purpose (a blind read-then-PUT is refused as missing enable)","consent_enable":"boolean","consent_position":"string","consent_layout":"string","consent_theme":"string","consent_expiry_days":"integer","banner_title":"string|null","banner_message":"string|null","accept_all_text":"string|null","reject_all_text":"string|null","customize_text":"string|null","save_preferences_text":"string|null","privacy_policy_label":"string|null","cookie_policy_label":"string|null","always_active_label":"string|null","unsaved_warning_text":"string|null","categories":"array","privacy_policy_url":"string|null","cookie_policy_url":"string|null","do_not_track_respect":"boolean","consent_log_retention_days":"integer","consent_log_enable":"boolean","data_export_enable":"boolean","data_deletion_enable":"boolean","email_notification_enable":"boolean","generate_cookie_policy":"boolean","company_name":"string|null","contact_email":"string|null","website_url":"string|null","banner_design":"object|null","cookie_policy_page_design":"object|null","data_request_page_design":"object|null","languages":"array","default_language":"string|null","translations":"object","settings_size_bytes":"integer|null","settings_size_limit":"integer","settings_unreadable":"string (too_big, damaged or unavailable) - present only when the stored settings could not be read and the standard settings are returned in their place. While it reads too_big or damaged, the next PUT replaces the unreadable file with the settings it carries. unavailable means the file could not be read just now, a temporary condition: settings_size_bytes is then null, nothing is replaced, and a PUT is refused until the file can be read again. Visitors see the standard banner meanwhile, even if the site's own banner was off. If it does not clear within a few minutes, the file may need to be reset by an operator: contact support","settings_categories_problem":"string - present only when the stored cookie categories cannot be used as saved (a restored or hand-edited list the rules refuse): the reason in a few words. Visitors get the standard cookie categories until a PUT stores a valid list. Read-only, dropped on a PUT"}}},{"name":"update_gdpr","description":"Replace the GDPR compliance addon settings. This is a FULL SAVE, not a patch: every field marked required below must be sent on every call. Most optional fields you omit are cleared, but seven are preserved at their stored value instead: privacy_policy_label, cookie_policy_label, always_active_label, unsaved_warning_text, languages, default_language and translations. Preserved means preserved when OMITTED: a translations object or a languages list you do send replaces the stored one whole, so send every language you want to keep, with all of that language's fields, not only the one you changed. A language left out of a sent translations object is removed, with its banner text and translated category names. Clearing a preserved text field is not possible by omitting it, and sending an empty languages list is what returns the banner to a single language. Read the current settings with the GET above and send them back with your changes applied. When that GET reports settings_unreadable (too_big, damaged or unavailable) it omits enable, because the settings it returns are the standard ones and not the site's, while the site keeps showing a banner to visitors: set enable yourself in that PUT, which replaces the stored file whole (and while the file is unavailable the PUT is refused, so do not send that document back later either - read again first). All the banner settings together can be up to 512 KB, measured as they are stored (four-space indentation, non-ASCII text escaped, so Hebrew or Arabic text costs about six bytes a character). A save over that is refused whole with a message naming the current size, except a save that sets enable to false, which is accepted as long as it does not make the stored settings bigger (a few KB of growth is allowed only while the settings stay within a few KB of the limit). A save whose settings are over 8 MB is refused whatever it carries. When the stored settings cannot be read (a file over 8 MB, which only a restored backup can produce, or a file that does not parse), a save replaces them whole with the settings it carries, as long as those fit the limit. Nothing is merged with the unreadable file, and a save that drops a cookie category another addon points at is refused in that state as in any other. When the stored settings cannot be read just now (the GET then carries settings_unreadable = unavailable, a temporary condition, not damage), a save is refused whole and nothing is replaced or changed: retry later. Visitors see the site's own on/off switches and cookie categories where they can be read. Where they cannot, the banner stays on and the visitor data-request pages are off. While the stored settings are over the limit or cannot be read, visitors see the standard banner texts, design and cookie policy page.","method":"PUT","path_template":"/servers/websites/{website}/addons/gdpr","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"enable","type":"boolean","required":true,"description":"Turn the GDPR addon on or off for this website."},{"name":"consent_enable","type":"boolean","required":true,"description":"Show the cookie consent banner to visitors."},{"name":"consent_position","type":"string","required":true,"description":"Where the banner sits on the page. One of: top, bottom, bottom-left, bottom-right, center."},{"name":"consent_layout","type":"string","required":true,"description":"The banner's shape. One of: bar, box, modal, floating."},{"name":"consent_theme","type":"string","required":true,"description":"The banner's colour scheme. One of: light, dark, auto, custom."},{"name":"consent_expiry_days","type":"integer","required":true,"description":"How many days a visitor's answer is remembered before the banner asks again. 1 to 3650."},{"name":"banner_title","type":"string","required":true,"description":"The banner's heading, in the default language. Up to 500 characters."},{"name":"banner_message","type":"string","required":true,"description":"The banner's message, in the default language. Up to 5000 characters."},{"name":"accept_all_text","type":"string","required":true,"description":"Label of the accept-everything button, in the default language. Up to 255 characters."},{"name":"reject_all_text","type":"string","required":true,"description":"Label of the refuse-everything button, in the default language. Up to 255 characters."},{"name":"customize_text","type":"string","required":true,"description":"Label of the button that opens the per-category choices, in the default language. Up to 255 characters."},{"name":"save_preferences_text","type":"string","required":true,"description":"Label of the button that saves the per-category choices, in the default language. Up to 255 characters."},{"name":"categories","type":"json","required":true,"description":"The cookie categories, as a JSON string holding a list of objects. Each object has id, name, description, required, enabled, cookiePatterns, scriptPatterns and cookies. The enabled field is accepted and stored, but it no longer changes anything a visitor sees: the banner offers every category that is not required, and a required category is always on. Use required to control that. The cookies field is the table the published cookie policy page renders: a list of objects, each with name, purpose and duration, and every value written as text. The id must be one of necessary, analytics, marketing, preferences, or start with custom_. The list must include necessary. Up to 50 categories, and up to 200 entries in each pattern list. Each object may carry only those eight keys and nothing else. An id is at most 50 characters. A pattern may carry at most 4 wildcard characters (*). A category that breaks one of these refuses the whole save, and the response names the field."},{"name":"do_not_track_respect","type":"boolean","required":true,"description":"Honour a visitor's browser Do Not Track setting."},{"name":"consent_log_enable","type":"boolean","required":true,"description":"Record each visitor's consent answer in the site's consent log."},{"name":"consent_log_retention_days","type":"integer","required":true,"description":"How many days consent records are kept before the nightly cleanup removes them. 1 to 3650."},{"name":"data_export_enable","type":"boolean","required":true,"description":"Let visitors ask for a copy of their data."},{"name":"data_deletion_enable","type":"boolean","required":true,"description":"Let visitors ask for their data to be deleted."},{"name":"email_notification_enable","type":"boolean","required":true,"description":"Email the site owner when a visitor files a data request."},{"name":"generate_cookie_policy","type":"boolean","required":true,"description":"Serve a cookie policy page built from these settings at /cookie-policy/ on the site."},{"name":"privacy_policy_url","type":"string","required":false,"description":"Address of the site's privacy policy, linked from the banner. A full web address, up to 2048 characters."},{"name":"cookie_policy_url","type":"string","required":false,"description":"Address of the site's cookie policy, linked from the banner. A full web address, up to 2048 characters."},{"name":"privacy_policy_label","type":"string","required":false,"description":"Text of the privacy policy link on the banner, in the default language. Up to 255 characters."},{"name":"cookie_policy_label","type":"string","required":false,"description":"Text of the cookie policy link on the banner, in the default language. Up to 255 characters."},{"name":"always_active_label","type":"string","required":false,"description":"Text shown beside a category the visitor cannot refuse, in the default language. Up to 255 characters."},{"name":"unsaved_warning_text","type":"string","required":false,"description":"The question the banner asks before reloading a page on which the visitor has typed something, in the default language. Up to 500 characters."},{"name":"company_name","type":"string","required":false,"description":"The company name printed on the generated cookie policy and data request pages. Up to 255 characters."},{"name":"contact_email","type":"string","required":false,"description":"The address data requests are sent to. Up to 255 characters. A plain address only: letters, digits and the characters . _ + - ! # $ % & ' * / = ? ^ ` { | } ~ before the @, then a domain with at least one dot. A quoted name, an address in brackets and any character outside ASCII letters and digits are refused, because the mail transport cannot deliver to them."},{"name":"website_url","type":"string","required":false,"description":"The site address printed on the generated pages. A full web address, up to 2048 characters."},{"name":"banner_design","type":"json","required":false,"description":"Banner colours and animation, as a JSON string."},{"name":"cookie_policy_page_design","type":"json","required":false,"description":"Colours and light or dark mode for the generated cookie policy page, as a JSON string."},{"name":"data_request_page_design","type":"json","required":false,"description":"Colours and light or dark mode for the generated data request page, as a JSON string."},{"name":"languages","type":"array","required":false,"description":"Banner languages the consent banner supports. Each item is an object with code and name. code is an opaque identifier you choose (the panel UI auto-generates one). Use the same code in translations and in set_language rules. Declaring languages makes the banner multilingual."},{"name":"default_language","type":"string","required":false,"description":"Code of the default banner language, shown when no banner language rule matches a visitor."},{"name":"translations","type":"object","required":false,"description":"Per-language banner text keyed by language code. Each entry holds that language's title, message, button labels, the reload confirmation question, the privacy and cookie policy addresses for that language, and cookie-category names. A per-language address may be a full web address or a path on the site. Only the name and description of a category are read from a translation. Everything a category enforces comes from the categories field above. Sending this field replaces the stored translations whole: include every language you want to keep, each with all of its fields, because a language left out is removed. Omit the field to keep the stored translations unchanged."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"list_logs","description":"Get GDPR consent logs with filtering and pagination. Requires full permission on the website.","method":"GET","path_template":"/servers/websites/{website}/addons/gdpr/logs","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"action_filter","type":"string","required":false,"description":"Show only one kind of visitor decision. One of: accept_all, reject_all, custom, withdraw, api_update, reset. Any other value is rejected. Both withdraw and reset are the visitor taking consent back, so an audit of withdrawals must ask for both. A stored decision whose label was missing or not one of these six is listed as unknown and cannot be selected by this filter. Use search to reach it."},{"name":"domain_filter","type":"string","required":false,"description":"Filter by domain"},{"name":"search","type":"string","required":false,"description":"Search term"},{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page"},{"name":"order_by","type":"string","required":false,"description":"Sort the results by one column. One of: timestamp, domain, action. This is the whole list, not an example. Any other value is rejected."},{"name":"order_direction","type":"string","required":false,"description":"Sort direction. Either asc or desc. Any other value is rejected."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - consent logs array with pagination. Each log carries ip_hash and user_agent_hash as one-way keyed values, or null for an older record whose values were removed for privacy and for a record written while the server could not hash, so read both as optional"}},{"name":"list_requests","description":"Get GDPR data requests with filtering and pagination. Requires full permission on the website. A completed request is deleted automatically 180 days after it was completed, a rejected or never-confirmed request 180 days after it was filed. Pending and in-progress requests are kept until they are closed.","method":"GET","path_template":"/servers/websites/{website}/addons/gdpr/requests","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"status_filter","type":"string","required":false,"description":"Show only requests in one state. One of: pending_verification, pending, in_progress, completed, rejected. Any other value is rejected."},{"name":"request_type_filter","type":"string","required":false,"description":"Show only one kind of request. Either export or deletion. Any other value is rejected."},{"name":"search","type":"string","required":false,"description":"Search term"},{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page"},{"name":"order_by","type":"string","required":false,"description":"Sort the results by one column. One of: timestamp, created_at, updated_at, email, status, request_type. This is the whole list, not an example. Any other value is rejected. The value timestamp is kept as an older name for created_at and sorts by the same column."},{"name":"order_direction","type":"string","required":false,"description":"Sort direction. Either asc or desc. Any other value is rejected."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - data requests array with pagination. Each request carries ip_hash as a one-way keyed value, or null for an older request whose value was removed for privacy and for a request written while the server could not hash, so read it as optional"}},{"name":"update_requests","description":"Update a GDPR data request status. Moving a request to completed emails the person a confirmation only when you send send_email=true, which is off by default, so a plain completed call tells the person nothing. The email is only a confirmation: no export is produced and no data is deleted, so that work stays with the website owner. Moving it to rejected sends nothing at all.","method":"PUT","path_template":"/servers/websites/{website}/addons/gdpr/requests/{requestId}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"requestId","type":"string","required":true,"description":"GDPR request ID (URL parameter)"}],"body_params":[{"name":"status","type":"string","required":true,"description":"The state to move the request to. One of: pending_verification, pending, in_progress, completed, rejected. This is the whole list, not an example. Any other value is rejected."},{"name":"notes","type":"string","required":false,"description":"Operator notes kept on the request and included in the completion email. Up to 1000 characters."},{"name":"send_email","type":"boolean","required":false,"description":"Send the completion email (default: false). Only read when status is completed. The email is a confirmation that the request is closed. It carries no exported data, and setting completed deletes nothing."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","email_sent":"boolean or null. true when the completion email was accepted for delivery, false when the mail server refused it, null when no email was attempted. The status change still stands when this is false, so read it before telling anyone the requester was notified.","message":"string"}},{"name":"delete_requests","description":"DESTRUCTIVE - Delete a GDPR data request.","method":"DELETE","path_template":"/servers/websites/{website}/addons/gdpr/requests/{requestId}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"requestId","type":"string","required":true,"description":"GDPR request ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_shabbat","description":"Get Shabbat & Holiday Mode addon settings including config options.","method":"GET","path_template":"/servers/websites/{website}/addons/shabbat","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":{"enable":"boolean","minutes_before":"integer","minutes_after":"integer","enabled_holidays":"array","config":{"minutes_options":"array","holidays":"array"}}}},{"name":"update_shabbat","description":"Update Shabbat & Holiday Mode addon settings.","method":"PUT","path_template":"/servers/websites/{website}/addons/shabbat","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"enable","type":"boolean","required":true,"description":"Enable/disable Shabbat mode"},{"name":"minutes_before","type":"integer","required":false,"description":"Minutes before Shabbat to activate. Allowed values: 10 to 90 in steps of 10"},{"name":"minutes_after","type":"integer","required":false,"description":"Minutes after Shabbat to deactivate. Allowed values: 10 to 90 in steps of 10"},{"name":"enabled_holidays","type":"array","required":false,"description":"Enabled holiday keys. A JSON array or a JSON-encoded string"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"list_details","description":"Get the Shabbat page HTML content.","method":"GET","path_template":"/servers/websites/{website}/addons/shabbat/details","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"string (HTML content)"}},{"name":"get_status","description":"Get the current Shabbat enabled/disabled status.","method":"GET","path_template":"/servers/websites/{website}/addons/shabbat/status","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":{"enabled":"boolean"}}},{"name":"list_locations","description":"Get available Shabbat holiday options and minutes configuration.","method":"GET","path_template":"/servers/websites/{website}/addons/shabbat/locations","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":{"minutes_options":"array","holidays":"array"}}},{"name":"update_page","description":"Replace the Shabbat closed-hours page HTML with custom code. Requires full permission on the website.","method":"PUT","path_template":"/servers/websites/{website}/addons/shabbat/page","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"code","type":"string","required":true,"description":"Custom HTML for the Shabbat page (newlines allowed)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_page","description":"DESTRUCTIVE - Restore the Shabbat page to the default template. Requires full permission on the website.","method":"DELETE","path_template":"/servers/websites/{website}/addons/shabbat/page","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}}]},{"name":"websites_agent-readiness","scope":"websites:agent-readiness","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"list_agent_readiness","description":"Get full Agent Readiness state for a website (toggles, content signals, capability cards, score, dismissed_onboarding).","method":"GET","path_template":"/servers/websites/{website}/agent-readiness","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.baseline_enabled":"boolean (master kill switch)","data.baseline_data":"object|null (content signals: sitemap_url, privacy_url, terms_url, llms_txt, llms_full_txt, llms_txt_mode, llms_full_txt_mode, llms_auto)","data.baseline_data.sitemap_url":"string|null","data.baseline_data.privacy_url":"string|null","data.baseline_data.terms_url":"string|null","data.baseline_data.llms_txt":"string|null (markdown content served at /llms.txt)","data.baseline_data.llms_full_txt":"string|null (markdown content served at /llms-full.txt)","data.baseline_data.llms_txt_mode":"string|null (auto|manual, default manual)","data.baseline_data.llms_full_txt_mode":"string|null (auto|manual, default manual)","data.baseline_data.llms_auto":"object|null (auto-mode crawler settings: schedule_interval, categories[], manual_entries[], max_urls_per_category, max_full_pages, last_generated)","data.markdown_enabled":"boolean","data.api_catalog_data":"object|null","data.skills_data":"array","data.oauth_card_data":"object|null","data.mcp_card_data":"object|null","data.a2a_card_data":"object|null","data.cdn_override_enabled":"boolean","data.site_type":"string|null","data.na_reasons":"object|null","data.last_score":"integer|null","data.last_score_at":"string (ISO 8601)|null","data.dismissed_onboarding":"boolean"}},{"name":"update_baseline","description":"Flip the Agent Readiness master toggle (baseline_enabled). Single kill switch for the whole feature: when ON, robots.txt rules, dynamic /llms.txt + /llms-full.txt, and Link rel headers deploy plus any independently-toggled add-ons. When OFF, every panel-managed file is removed and dynamic content stops being served (add-on configs persist in DB).","method":"PUT","path_template":"/servers/websites/{website}/agent-readiness/baseline","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"baseline_enabled","type":"boolean","required":true,"description":"Master kill switch for Agent Readiness."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object (full agent-readiness state)"}},{"name":"update_content_signals","description":"Update content signals stored under baseline_data: sitemap URL, privacy URL, terms URL, plus llms.txt and llms-full.txt content (served dynamically by the panel at /llms.txt and /llms-full.txt).","method":"PUT","path_template":"/servers/websites/{website}/agent-readiness/content-signals","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"sitemap_url","type":"string","required":false,"description":"Absolute URL of the sitemap."},{"name":"privacy_url","type":"string","required":false,"description":"Absolute URL of the privacy policy page."},{"name":"terms_url","type":"string","required":false,"description":"Absolute URL of the terms-of-service page."},{"name":"llms_txt","type":"string","required":false,"description":"Markdown content served at /llms.txt. Max 65535 bytes. Newlines preserved."},{"name":"llms_full_txt","type":"string","required":false,"description":"Extended markdown content served at /llms-full.txt. Max 262144 bytes. Newlines preserved."},{"name":"llms_txt_mode","type":"string","required":false,"description":"Per-file mode: 'auto' = crawler regenerates llms.txt content on the existing daily cron + when /regenerate-llms is hit. 'manual' = panel uses the llms_txt body verbatim. Default 'manual'."},{"name":"llms_full_txt_mode","type":"string","required":false,"description":"Same as llms_txt_mode but for llms-full.txt. Default 'manual'."},{"name":"llms_auto","type":"object","required":false,"description":"Auto-mode crawler settings (only consulted when one of the modes above is 'auto'). Object with: schedule_interval (string: daily|weekly|monthly|manually), categories (array of {key, name, patterns[]}), manual_entries (array of {name, url, description?, category?}), max_urls_per_category (int 1-100), max_full_pages (int 1-500), last_generated (string ISO8601, populated by the regenerate endpoint)."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object (full agent-readiness state)"}},{"name":"create_regenerate_llms","description":"Run the llms.txt crawler now and return the generated content. The crawler scans the website (sitemap + homepage links + page bodies if generate_full=true), groups links by the customer's URL categories, and applies their max_urls_per_category / max_full_pages limits. Generated content is written to baseline_data.llms_txt (or .llms_full_txt) and last_generated is updated. Use this for an on-demand refresh between scheduled runs. One run per website per minute: a second call inside 60 seconds of the last successful run answers 429 with a Retry-After header.","method":"POST","path_template":"/servers/websites/{website}/agent-readiness/regenerate-llms","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"generate_full","type":"boolean","required":false,"description":"If true, generate llms-full.txt (extended page bodies). If false (default), generate llms.txt (link index)."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object (full agent-readiness state including the freshly populated baseline_data.llms_txt or .llms_full_txt)"}},{"name":"update_markdown","description":"Toggle the markdown negotiation add-on (Accept: text/markdown -> server returns clean markdown). Optional CDN-override with typed-phrase acknowledgment. Only deploys while the Agent Readiness master toggle is ON.","method":"PUT","path_template":"/servers/websites/{website}/agent-readiness/markdown","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"markdown_enabled","type":"boolean","required":true,"description":"Toggle markdown rendering."},{"name":"cdn_override","type":"object","required":false,"description":"Optional override object. Set acknowledgment_phrase to 'I understand my CDN must vary on Accept' to enable behind a non-varying CDN."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object (full agent-readiness state)"}},{"name":"create_api_catalog","description":"Upload an OpenAPI 3.x spec (.json or .yaml) describing the website's API. Validated and served at /.well-known/api-catalog. Note: this endpoint takes a multipart file upload (file) that cannot be sent over MCP - run the upload via the panel or a direct External API multipart call.","method":"POST","path_template":"/servers/websites/{website}/agent-readiness/api-catalog","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object (full agent-readiness state)"}},{"name":"delete_api_catalog","description":"DESTRUCTIVE - Delete the uploaded API catalog. Destructive: clears api_catalog_data and removes the file from /.well-known/api-catalog after deploy.","method":"DELETE","path_template":"/servers/websites/{website}/agent-readiness/api-catalog","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_skills","description":"Upload a skill markdown file (frontmatter + body). Sanitized + coherence-validated before persisting. Quota: 25 skills per website. Note: this endpoint takes a multipart file upload (file) that cannot be sent over MCP - run the upload via the panel or a direct External API multipart call.","method":"POST","path_template":"/servers/websites/{website}/agent-readiness/skills","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object (full agent-readiness state)"}},{"name":"delete_skills","description":"DESTRUCTIVE - Delete a skill by name. Destructive: removes the skill and its /.well-known/skills/{name}.md file after deploy.","method":"DELETE","path_template":"/servers/websites/{website}/agent-readiness/skills/{name}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"name","type":"string","required":true,"description":"Exact skill name (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"update_oauth_card","description":"Configure the OAuth capability card. URL is SSRF-hardened and probed before saving. Refused if unreachable. Mid-session disable: send enabled=false to deactivate.","method":"PUT","path_template":"/servers/websites/{website}/agent-readiness/oauth-card","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"enabled","type":"boolean","required":true,"description":"Toggle the OAuth card on/off."},{"name":"url","type":"string","required":false,"description":"Absolute URL of the OAuth authorization-server metadata document. Required when enabled is true."},{"name":"risk_acknowledged","type":"boolean","required":false,"description":"First-time enable requires risk acknowledgment in the panel UI."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object (full agent-readiness state)"}},{"name":"update_mcp_card","description":"Configure the MCP (Model Context Protocol) capability card. URL is SSRF-hardened and probed before saving.","method":"PUT","path_template":"/servers/websites/{website}/agent-readiness/mcp-card","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"enabled","type":"boolean","required":true,"description":"Toggle the MCP card on/off."},{"name":"url","type":"string","required":false,"description":"Absolute URL of the MCP server endpoint. Required when enabled is true."},{"name":"risk_acknowledged","type":"boolean","required":false,"description":"First-time enable requires risk acknowledgment."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object (full agent-readiness state)"}},{"name":"update_a2a_card","description":"Configure the A2A (Agent-to-Agent) capability card. URL is SSRF-hardened and probed before saving.","method":"PUT","path_template":"/servers/websites/{website}/agent-readiness/a2a-card","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"enabled","type":"boolean","required":true,"description":"Toggle the A2A card on/off."},{"name":"url","type":"string","required":false,"description":"Absolute URL of the A2A endpoint. Required when enabled is true."},{"name":"risk_acknowledged","type":"boolean","required":false,"description":"First-time enable requires risk acknowledgment."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object (full agent-readiness state)"}},{"name":"list_file_conflicts","description":"Detect which canonical agent-readiness paths exist on disk and how they got there (panel-managed vs customer-placed). Used by the UI to show conflict alerts in per-section modals before saving.","method":"GET","path_template":"/servers/websites/{website}/agent-readiness/file-conflicts","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.llms_txt":"string (customer-placed | not-present | unknown)","data.llms_full_txt":"string (customer-placed | not-present | unknown)","data.openapi":"string (panel-managed | customer-placed | not-present | unknown)","data.agent_skills":"array of {name: string, status: panel-managed|customer-placed}"}},{"name":"get_score","description":"Get the Agent Readiness score from the external validator (isitagentready.com). Served from a 1-hour cache. When the cache is cold this runs a live scan instead, which can take several minutes, so a cold call counts against the same limit of 6 scans per minute per customer account as the rescan endpoint. Over that limit the response still returns 200 with score null and a message in the error field.","method":"GET","path_template":"/servers/websites/{website}/agent-readiness/score","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.score":"integer|null (0-100)","data.level":"integer|null (1-5 tier from the validator)","data.level_name":"string|null","data.next_level_requirements":"array (what the site still needs for the next tier)","data.breakdown":"array of {label: string, status: string, count_in_score: boolean}","data.cached":"boolean (false means this call ran a live scan)","data.scanned_at":"string (ISO 8601)|null","data.error":"string|null (set when no score could be produced, including when the account is over its scan limit)"}},{"name":"create_rescan","description":"Trigger a fresh validator scan and update the cached score. The scan can take up to 500 seconds, so this is throttled to 6 requests per minute, counted against ONE shared budget with the panel's own rescan button. Panel activity can use it up. A second limit of 6 scans per minute applies to the customer account across every way a scan can be started, including the cold-cache read endpoint. Passing the first limit and failing the second answers 429 with the reason in the message field, so treat 429 here as either limit.","method":"POST","path_template":"/servers/websites/{website}/agent-readiness/score/rescan","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.score":"integer|null (0-100)","data.level":"integer|null (1-5 tier from the validator)","data.level_name":"string|null","data.next_level_requirements":"array (what the site still needs for the next tier)","data.breakdown":"array of {label: string, status: string, count_in_score: boolean}","data.cached":"boolean (always false here - this endpoint always scans)","data.scanned_at":"string (ISO 8601)|null","data.error":"string|null (set when no score could be produced)"}},{"name":"get_preview_as_agent","description":"Server-side fetch of the customer's homepage with Accept: text/markdown so the panel can render what an AI agent would see. Only the markdown tier is implemented today. Throttled to 30 requests per minute.","method":"GET","path_template":"/servers/websites/{website}/agent-readiness/preview-as-agent","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"tier","type":"string","required":true,"description":"Which preview to render. Only 'markdown' is implemented today."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.preview":"string (the rendered preview body, empty when error is set)","data.error":"string|null (human-readable failure reason if the fetch did not return text/markdown)","data.content_type":"string|null (Content-Type header observed from the customer site)","data.status":"integer|null (HTTP status observed from the customer site)"}},{"name":"get_test_markdown","description":"Server-side test that the customer site returns text/markdown when asked. Used by the Test markdown button to bypass browser CORS limits. Returns a structured pass/fail. Throttled to 30 requests per minute.","method":"GET","path_template":"/servers/websites/{website}/agent-readiness/test-markdown","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.ok":"boolean (true when the homepage returned a 2xx with Content-Type starting with text/markdown)","data.status":"integer|null (HTTP status from the customer site)","data.content_type":"string|null (Content-Type header observed from the customer site)","data.reason":"string|null (machine-readable failure reason: unreachable, http_<code>, wrong_content_type, or null on success)"}},{"name":"create_bulk_apply_baseline","description":"Bulk-flip the Agent Readiness master toggle (baseline_enabled) ON or OFF across multiple websites. Per-id credential validation runs INSIDE the controller because there is no {website} route param. Returns arrays of applied / skipped / unauthorized / not_found IDs.","method":"POST","path_template":"/agent-readiness/bulk-apply-baseline","path_params":[],"body_params":[{"name":"website_ids","type":"array","required":true,"description":"Array of website IDs (1-200)."},{"name":"baseline_enabled","type":"boolean","required":true,"description":"Target state for the Agent Readiness master toggle."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.applied":"array of integers","data.skipped_already_in_state":"array of integers","data.unauthorized":"array of integers (websites the credential cannot access)","data.not_found":"array of integers","data.invalid":"array of strings (entries that are not usable as a website id at all, echoed back so every entry you sent has an outcome)","data.baseline_enabled":"boolean"}}]},{"name":"websites_ai-crawlers","scope":"websites:ai-crawlers","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"list_settings","description":"Get AI crawlers settings and rules for a website. Returns one entry per recognized crawler with its current rule.","method":"GET","path_template":"/servers/websites/{website}/ai-crawlers/settings","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"integer","data[].name":"string (operator name, e.g. OpenAI)","data[].slug":"string (crawler slug used to set rules, e.g. openai)","data[].user_agents":"array of strings (HTTP user agents this crawler covers)","data[].icon":"string|null","data[].ip_range":"object|null (linked IP range for verification)","data[].ip_range.id":"integer (when ip_range is not null)","data[].ip_range.name":"string (when ip_range is not null)","data[].ip_range.title":"string (when ip_range is not null)","data[].blocked":"boolean (legacy - use rule.mode instead)","data[].rule":"object","data[].rule.mode":"string (allow|block|rate_limit)","data[].rule.limit":"integer|null (only when mode=rate_limit)","data[].rule.interval":"string|null (only when mode=rate_limit, one of: second|minute|hour)","data[].rule.scope":"string|null (only when mode=rate_limit, one of: scanning|user_action|both, defaults to scanning)"}},{"name":"update_settings","description":"Update a single AI crawler rule.","method":"PUT","path_template":"/servers/websites/{website}/ai-crawlers/settings","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"slug","type":"string","required":true,"description":"Crawler slug"},{"name":"mode","type":"string","required":true,"description":"Rule mode: allow, rate_limit, block"},{"name":"limit","type":"integer","required":false,"description":"Rate limit (required if mode=rate_limit)"},{"name":"interval","type":"string","required":false,"description":"Rate limit interval: second, minute, hour"},{"name":"scope","type":"string","required":false,"description":"Which AI traffic the rate limit applies to: scanning (robot crawlers scraping the site), user_action (a live person using an AI tool that fetches the page now), or both. Defaults to scanning when mode=rate_limit."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"update_bulk","description":"Bulk-set AI crawler rules. Mirrors the panel's Allow All / Block All buttons. IMPORTANT: this endpoint only accepts allow or block - rate_limit is rejected with a 422. Use the per-rule PUT (above) to set rate_limit on individual crawlers.","method":"PUT","path_template":"/servers/websites/{website}/ai-crawlers/settings/bulk","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"rules","type":"object","required":true,"description":"Map of crawler slug to mode. Each mode value MUST be either \"allow\" or \"block\" - rate_limit is not supported in bulk and will fail validation. Example: {\"openai\": \"block\", \"anthropic\": \"block\", \"google-ai\": \"allow\"}."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}}]},{"name":"websites_antivirus","scope":"websites:antivirus","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"list_scans","description":"Get antivirus scans list.","method":"GET","path_template":"/servers/websites/{website}/antivirus/scans","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of scans","meta":"object"}},{"name":"get_running","description":"Get running antivirus process.","method":"GET","path_template":"/servers/websites/{website}/antivirus/running","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object or empty"}},{"name":"list_stats","description":"Get antivirus statistics.","method":"GET","path_template":"/servers/websites/{website}/antivirus/stats","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - antivirus statistics (total scans, infected files count, cleaned count, last scan date)"}},{"name":"list_settings","description":"Get antivirus settings.","method":"GET","path_template":"/servers/websites/{website}/antivirus/settings","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - antivirus settings (automatic boolean, period string)"}},{"name":"update_settings","description":"Update antivirus settings.","method":"PUT","path_template":"/servers/websites/{website}/antivirus/settings","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"automatic","type":"boolean","required":false,"description":"Enable automatic scanning"},{"name":"period","type":"string","required":false,"description":"Scan period"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_scan","description":"Start a new antivirus scan.","method":"POST","path_template":"/servers/websites/{website}/antivirus/scan","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_clean","description":"Clean infected files from a scan.","method":"POST","path_template":"/servers/websites/{website}/antivirus/clean","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"scan_id","type":"string","required":true,"description":"Scan ID to clean"},{"name":"backup","type":"boolean","required":false,"description":"Create backup before cleaning"},{"name":"backup_name","type":"string","required":false,"description":"Backup name"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_antivirus","description":"Get scan details.","method":"GET","path_template":"/servers/websites/{website}/antivirus/{scan}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"scan","type":"string","required":true,"description":"Scan ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - scan details (status, started_at, completed_at, files_scanned, hits_count, website_id, scan_id, is_last_scan)"}},{"name":"list_hits","description":"Get scan hits (infected files).","method":"GET","path_template":"/servers/websites/{website}/antivirus/{scan}/hits","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"scan","type":"string","required":true,"description":"Scan ID (URL parameter)"}],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page"},{"name":"search","type":"string","required":false,"description":"Search filter"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - hit entries (file paths, threat types, severities) with pagination"}},{"name":"get_cleaned","description":"Get cleaned files for a scan.","method":"GET","path_template":"/servers/websites/{website}/antivirus/{scan}/cleaned","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"scan","type":"string","required":true,"description":"Scan ID (URL parameter)"}],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page"},{"name":"search","type":"string","required":false,"description":"Search filter"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - cleaned file entries (file paths, actions taken) with pagination"}}]},{"name":"websites_backups","scope":"websites:backups","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"list_backups","description":"List backups for a website. Supports pagination.","method":"GET","path_template":"/servers/websites/{website}/backups","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of backup objects (id, name, time, target, expire, type, domains)","meta":"pagination metadata"}},{"name":"get_running","description":"Get currently running backup process.","method":"GET","path_template":"/servers/websites/{website}/backups/running","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object|null (null when no process is running)","data.type":"string (\"backup\" or \"restore\")","data.target":"string|array (\"full\" or array of kinds)","data.time":"integer (Unix timestamp when process started)"}},{"name":"list_stats","description":"Get backup statistics.","method":"GET","path_template":"/servers/websites/{website}/backups/stats","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.amounts":"integer (total number of backups)","data.expire":"string (backup retention period)","data.newest_backup_time":"integer|null (Unix timestamp of newest backup)","data.oldest_backup_time":"integer|null (Unix timestamp of oldest backup)"}},{"name":"create_backups","description":"Create a new backup.","method":"POST","path_template":"/servers/websites/{website}/backups","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"name","type":"string","required":false,"description":"Backup name"},{"name":"full","type":"boolean","required":true,"description":"Create a full backup (files + databases + data)"},{"name":"files","type":"boolean","required":false,"description":"Include files in backup. Required when full is false."},{"name":"databases","type":"boolean","required":false,"description":"Include databases in backup. Required when full is false."},{"name":"data","type":"boolean","required":false,"description":"Include data/config in backup. Required when full is false."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_backups","description":"DESTRUCTIVE - Delete a backup.","method":"DELETE","path_template":"/servers/websites/{website}/backups","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"ids","type":"array","required":true,"description":"Array with backup time identifier"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"restore","description":"DESTRUCTIVE - Restore from a backup.","method":"POST","path_template":"/servers/websites/{website}/backups/{backup}/restore","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"backup","type":"string","required":true,"description":"Backup time identifier (URL parameter)"}],"body_params":[{"name":"full","type":"boolean","required":false,"description":"Full restore"},{"name":"files","type":"boolean","required":false,"description":"Restore files"},{"name":"databases","type":"boolean","required":false,"description":"Restore databases"},{"name":"_data","type":"boolean","required":false,"description":"Restore data/config"},{"name":"reset","type":"boolean","required":false,"description":"Reset before restore"},{"name":"maintenance","type":"boolean","required":false,"description":"Enable maintenance mode during restore"},{"name":"this_website","type":"boolean","required":false,"description":"Set true to force an in-place restore on the current website. There is no server default. On a full reset restore, if this is omitted or false and target_website is set, the restore goes to target_website instead."},{"name":"target_website","type":"integer","required":false,"description":"Target website ID for a cross-website restore. Takes effect only on a full reset restore (full and reset both true) when this_website is not true. There is no server default for this_website, so passing target_website on a full reset restore restores onto that site unless this_website is sent as true. A files-only or databases-only restore ignores it and runs in place on the current website."},{"name":"backup_name","type":"string","required":false,"description":"Name for pre-restore backup"},{"name":"selected_files","type":"array","required":false,"description":"Specific files or directories to restore, as relative paths. On a files restore this is required and must not be empty. Pass [\"/\"] (a single forward slash) to restore all files. Only used on a files restore."},{"name":"selected_databases","type":"array","required":false,"description":"Specific databases or tables to restore. On a databases restore this is required and must not be empty. Use the database name for a whole database, or \"database.table\" (dot-separated) for a single table. Pass [\"/\"] (a single forward slash) to restore all databases. PostgreSQL databases in the backup sit under one entry named \"<username>__postgres\" (as listed by the backup's mysql and mysql/structure endpoints): pass that entry to restore every PostgreSQL database in the backup, or \"<username>__postgres.<database>\" (for example \"bob__postgres.shop\") for one PostgreSQL database. A PostgreSQL database is replaced whole from the backup, while a MySQL database is merged table by table. Restoring databases also sets the database users' passwords back to the values stored in the backup, so a password changed after the backup was taken has to be set again afterwards. A selection naming a database the backup does not contain is refused and nothing is restored. On a server not yet updated for PostgreSQL restores the \"<username>__postgres\" entry is not listed at all, and a selection naming it is refused with a message that says so - nothing is restored. Only used on a databases restore."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_download","description":"Download a backup. Returns binary file data (StreamedResponse).","method":"GET","path_template":"/servers/websites/{website}/backups/{backup}/download","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"backup","type":"string","required":true,"description":"Backup time identifier (URL parameter)"}],"body_params":[{"name":"kind","type":"string","required":false,"description":"Backup kind: files, databases, or data"},{"name":"item","type":"string","required":false,"description":"Specific item to download"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"description":"Binary file stream"}},{"name":"list_files","description":"View files inside a backup. Supports pagination.","method":"GET","path_template":"/servers/websites/{website}/backups/{backup}/files","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"backup","type":"string","required":true,"description":"Backup time identifier (URL parameter)"}],"body_params":[{"name":"path","type":"string","required":false,"description":"Directory path inside backup"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of file objects","meta":"pagination metadata"}},{"name":"get_code","description":"View file content inside a backup.","method":"GET","path_template":"/servers/websites/{website}/backups/{backup}/files/code","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"backup","type":"string","required":true,"description":"Backup time identifier (URL parameter)"}],"body_params":[{"name":"file","type":"string","required":true,"description":"File path inside backup"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.content":"string"}},{"name":"get_structure","description":"View directory structure of files in a backup.","method":"GET","path_template":"/servers/websites/{website}/backups/{backup}/files/structure","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"backup","type":"string","required":true,"description":"Backup time identifier (URL parameter)"}],"body_params":[{"name":"openedPaths","type":"array","required":false,"description":"Array of already-opened paths. Every entry must be a string - a non-string entry (including a null) is rejected with 422 rather than dropped, so a folder you asked to expand is never silently missing from the returned tree."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of directory tree nodes"}},{"name":"get_mysql","description":"View the databases inside a backup. Supports pagination. MySQL databases are listed by name; every PostgreSQL database in the backup is grouped under one entry named \"<username>__postgres\" (use the structure endpoint to see the individual PostgreSQL databases).","method":"GET","path_template":"/servers/websites/{website}/backups/{backup}/mysql","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"backup","type":"string","required":true,"description":"Backup time identifier (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of database objects (id, name)","meta":"pagination metadata"}},{"name":"mysql_get_structure","description":"View the database tree inside a backup: each MySQL database with its tables, and one entry named \"<username>__postgres\" whose children are the PostgreSQL databases in the backup (plus a \"_pg_privileges\" access-information entry that is not a database). Selection values for the restore endpoint's selected_databases come from this tree. The PostgreSQL entry is absent on a server not yet updated for PostgreSQL restores (a selection naming it is refused there), so its absence never proves the backup holds no PostgreSQL database.","method":"GET","path_template":"/servers/websites/{website}/backups/{backup}/mysql/structure","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"backup","type":"string","required":true,"description":"Backup time identifier (URL parameter)"}],"body_params":[{"name":"openedPaths","type":"array","required":false,"description":"Array of already-opened paths. Every entry must be a string - a non-string entry (including a null) is rejected with 422 rather than dropped, so a folder you asked to expand is never silently missing from the returned tree."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of directory tree nodes"}},{"name":"list_share_links","description":"BREAKING CHANGE: response now includes links where ready != 1. Archives are prepared asynchronously, so newly-created links appear with ready=0 (preparing) and finish at ready=1 (ready) or ready=-1 (failed). Clients MUST filter by ready === 1 before attempting download. Pre-refactor clients assumed listed = downloadable. Get share links for a backup. Supports pagination.","method":"GET","path_template":"/servers/websites/{website}/backups/{backup}/share-links","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"backup","type":"string","required":true,"description":"Backup time identifier (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of share link objects (id, link, name, has_password, ready, downloads, created_at)","data[].ready":"integer (0=preparing, 1=ready, -1=failed)","meta":"pagination metadata"}},{"name":"create_share_links","description":"Create a share link for a backup. Response shape adds data.ready / data.status: the archive is prepared in the background, so the link URL is returned immediately and can be shared right away, but downloads are gated until ready=1. Poll GET /share-links until ready=1 before the recipient attempts download.","method":"POST","path_template":"/servers/websites/{website}/backups/{backup}/share-links","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"backup","type":"string","required":true,"description":"Backup time identifier (URL parameter)"}],"body_params":[{"name":"name","type":"string","required":true,"description":"Share link name"},{"name":"password","type":"string","required":false,"description":"Password protection for the share link (12-255 characters)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object","data.link":"string (full share link URL, shareable immediately)","data.ready":"integer (always 0 on create - archive prepared asynchronously)","data.status":"string (always 'preparing' on create)"}},{"name":"update_share_links","description":"Update a share link (name, password, or both). At least one of `name` or `password` MUST be provided - a request with both omitted is rejected with HTTP 422. Each field you send is applied: send `name` alone to rename, `password` alone to change the password, or both to do each in one call. A field you omit is left unchanged.","method":"PUT","path_template":"/servers/websites/{website}/backups/{backup}/share-links/{link}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"backup","type":"string","required":true,"description":"Backup time identifier (URL parameter)"},{"name":"link","type":"string","required":true,"description":"Share link ID (URL parameter)"}],"body_params":[{"name":"name","type":"string","required":false,"description":"New share link name"},{"name":"password","type":"string","required":false,"description":"New password for the share link (12-255 characters)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_share_links","description":"DESTRUCTIVE - Delete a share link.","method":"DELETE","path_template":"/servers/websites/{website}/backups/{backup}/share-links/{link}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"backup","type":"string","required":true,"description":"Backup time identifier (URL parameter)"},{"name":"link","type":"string","required":true,"description":"Share link ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_password","description":"DESTRUCTIVE - Remove password protection from a share link.","method":"DELETE","path_template":"/servers/websites/{website}/backups/{backup}/share-links/{link}/password","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"backup","type":"string","required":true,"description":"Backup time identifier (URL parameter)"},{"name":"link","type":"string","required":true,"description":"Share link ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"list_downloads","description":"Get download history for a share link. Supports pagination.","method":"GET","path_template":"/servers/websites/{website}/backups/{backup}/share-links/{link}/downloads","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"backup","type":"string","required":true,"description":"Backup time identifier (URL parameter)"},{"name":"link","type":"string","required":true,"description":"Share link ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of download records (ip, downloaded_at)","meta":"pagination metadata"}},{"name":"create_retry","description":"Retry preparation for a failed share link (valid only when the link's ready=-1). The archive is re-queued in the background. The link URL is unchanged so anyone who already has the URL can keep using it. Returns 409 with {message: 'Share link is not in failed state'} when the link is currently preparing (ready=0) or already ready (ready=1).","method":"POST","path_template":"/servers/websites/{website}/backups/{backup}/share-links/{link}/retry","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"backup","type":"string","required":true,"description":"Backup time identifier (URL parameter)"},{"name":"link","type":"string","required":true,"description":"Share link ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}}]},{"name":"websites_cache","scope":"websites:cache","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"get_status","description":"Get website cache status.","method":"GET","path_template":"/servers/websites/cache/status/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":{"enable":"boolean","cache_time":"string","cached_pages":"object","cached_pages.desktop":"integer","cached_pages.mobile":"integer"},"interpretation":{"cached_pages":"string","no_store_header":"string","how_to_verify_caching":"string"}}},{"name":"create_purge","description":"DESTRUCTIVE - Purge website cache.","method":"POST","path_template":"/servers/websites/cache/purge/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"uri","type":"string","required":false,"description":"Single URI path to purge (defaults to '/')"},{"name":"paths","type":"array","required":false,"description":"Array of paths to purge. If provided, overrides 'uri'"},{"name":"platform","type":"string","required":false,"description":"Platform cache to purge: 'desktop', 'mobile', or 'all' (defaults to 'all')"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_cache_history","description":"Get cache history and statistics showing cached vs non-cached request counts over time.","method":"GET","path_template":"/servers/websites/cache/cache-history/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"timeframe","type":"string|integer","required":false,"description":"Time period: 'today', 'this_month', or integer 1-30 for number of days (defaults to 'today')"},{"name":"tz","type":"string","required":false,"description":"Timezone for bucketing (e.g., 'America/New_York'). Defaults to server cluster timezone"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":{"total":"object","total.requests":"integer","total.cached":"integer","total.nocached":"integer","timestamps":"object","timestamps.requests":"object","timestamps.cached":"object","timestamps.nocached":"object"}}},{"name":"list_settings","description":"Get cache settings including bypass rules and preload configuration.","method":"GET","path_template":"/servers/websites/{website}/cache/settings","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":{"enable":"boolean","cache_time":"string","bypass_known_headers":"boolean","bypass_known_uri":"boolean","bypass_known_query_strings":"boolean","bypass_known_cookies":"boolean","ignore_known_query_strings":"boolean","ignore_all_query_strings":"boolean","preload_enable":"boolean","preload_pages_per_minute":"integer"}}},{"name":"update_settings","description":"Update cache settings including bypass rules and preload configuration. This endpoint replaces the full cache settings, so cache_time and the six boolean flags are required. Saving settings also turns caching on and regenerates its protection rules. To turn caching off, use the cache mode endpoint.","method":"PUT","path_template":"/servers/websites/{website}/cache/settings","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"cache_time","type":"string","required":true,"description":"Cache duration. Required (full-settings update). Enum: 1m, 5m, 10m, 20m, 30m, 1h, 4h, 8h, 12h, 1d, 2d, 3d, 4d, 5d, 6d, 7d, 14d, 21d, 30d"},{"name":"bypass_known_uri","type":"boolean","required":true,"description":"Skip the cache for known dynamic pages such as login and admin. Required (full-settings update)."},{"name":"bypass_known_query_strings","type":"boolean","required":true,"description":"Skip the cache for requests with known URL parameters. Required (full-settings update)."},{"name":"bypass_known_cookies","type":"boolean","required":true,"description":"Skip the cache when auth or cart cookies are present. Required (full-settings update)."},{"name":"bypass_known_headers","type":"boolean","required":true,"description":"Skip the cache for requests with authentication headers. Required (full-settings update)."},{"name":"ignore_known_query_strings","type":"boolean","required":true,"description":"Strip known tracking parameters from cache keys. Required (full-settings update)."},{"name":"ignore_all_query_strings","type":"boolean","required":true,"description":"Strip all URL parameters from cache keys for requests from known bots. Required (full-settings update)."},{"name":"preload_enable","type":"boolean","required":false,"description":"Enable cache preloading"},{"name":"preload_pages_per_minute","type":"integer","required":false,"description":"Pages to preload per minute"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_is_cached","description":"Check if a specific URI is cached.","method":"GET","path_template":"/servers/websites/{website}/cache/is-cached","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"uri","type":"string","required":true,"description":"URI path to check"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - whether the specified URI is currently cached"}},{"name":"update_mode","description":"Set cache mode (off, basic, balanced, strict). Sending category_overrides with any mode other than off turns caching on for the website and regenerates its protection rules from the overrides merged over the site's current settings (categories you omit keep their current value) instead of applying the mode preset. Sending mode off always turns caching fully off.","method":"PUT","path_template":"/servers/websites/{website}/cache/mode","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"mode","type":"string","required":true,"description":"Cache mode: off, basic, balanced, strict"},{"name":"category_overrides","type":"object","required":false,"description":"Per-category on/off overrides (boolean values). Any category you omit keeps its current value."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}}]},{"name":"websites_cron","scope":"websites:cron","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"get_cron","description":"List cronjobs for a website. Includes next run times. Supports pagination. The id of each cronjob is the value to send to the update, execute and delete endpoints. It is URL-safe base64, so it can be used in a path segment as it is.","method":"GET","path_template":"/servers/websites/{website}/cron","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number for pagination"},{"name":"per_page","type":"integer","required":false,"description":"Number of items per page. Defaults to 20. Values below 10 are raised to 10 and values above 200 are lowered to 200."},{"name":"search","type":"string","required":false,"description":"Filter cronjobs by command text"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of cronjob objects (id, minute, hour, day, month, weekday, command, reboot, next_run, next_runs)","meta":"pagination metadata"}},{"name":"get_timezone","description":"Get server timezone information for cronjob scheduling.","method":"GET","path_template":"/servers/websites/{website}/cron/timezone","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.timezone":"string (e.g., \"UTC\", \"Asia/Jerusalem\")","data.current_time":"string (current time in HH:mm:ss format)"}},{"name":"create_cron","description":"DESTRUCTIVE - Create a new cronjob. A cronjob is identified by its schedule and its command together, so posting a schedule and command that already exist is refused with a 400 rather than creating a second copy.","method":"POST","path_template":"/servers/websites/{website}/cron","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"command","type":"string","required":true,"description":"Command to execute. One line, up to 1000 characters. For a longer job, put the commands in a script file and schedule that file. Write a literal percent sign as \\% - cron treats a plain % as the end of the command and sends the rest to the command as standard input."},{"name":"reboot","type":"boolean","required":true,"description":"Run on reboot instead of a time schedule. When false, the schedule fields (minute, hour, day, month, weekday) are required."},{"name":"minute","type":"string","required":false,"description":"Cron minute field (required when reboot is false). Accepts 0-59, plus ranges, lists, steps, and the random-range form such as 6~15. A random range has to run upwards, so 15~6 is refused. A leading zero is fine, in a value and in a step"},{"name":"hour","type":"string","required":false,"description":"Cron hour field (required when reboot is false). Accepts 0-23, plus ranges, lists, steps, and the random-range form such as 2~4. A random range has to run upwards. A leading zero is fine, in a value and in a step"},{"name":"day","type":"string","required":false,"description":"Cron day of month field (required when reboot is false). Accepts 1-31, plus ranges, lists, steps, and the random-range form such as 1~5. A random range has to run upwards. A leading zero is fine, in a value and in a step"},{"name":"month","type":"string","required":false,"description":"Cron month field (required when reboot is false). Accepts 1-12 or the three-letter names JAN to DEC, plus ranges, lists, steps, and the random-range form such as 3~6. A random range has to run upwards, so DEC~MAR is refused while DEC-MAR is fine."},{"name":"weekday","type":"string","required":false,"description":"Cron day of week field (required when reboot is false). Accepts 0-7 where both 0 and 7 mean Sunday, or the three-letter names SUN to SAT, plus ranges, lists, steps, and the random-range form. A random range has to run upwards, so FRI~MON is refused while FRI-MON is fine. Note that 7 counts as seven for that test, so 1~7 works and 7~1 does not."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"update_cron","description":"DESTRUCTIVE - Update a cronjob. The existing crontab entry is removed and the new one is added, so the id changes. List the cronjobs again afterwards to get the new id. An id that cannot be read is answered with a 400, and an id that no longer matches any cronjob on the server with a 404 - re-list to get current ids rather than retrying the same call.","method":"PUT","path_template":"/servers/websites/{website}/cron/{cron}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"cron","type":"string","required":true,"description":"The cronjob id from the list endpoint, used as it is (URL parameter). It is URL-safe base64. This endpoint carries the id in the URL, so a job whose command is roughly 24,000 characters or more is refused with a 414 by the web server before the API sees it - such a job can still be listed and deleted, because DELETE carries its ids in the body."}],"body_params":[{"name":"command","type":"string","required":true,"description":"Command to execute. One line, up to 1000 characters. For a longer job, put the commands in a script file and schedule that file. Write a literal percent sign as \\% - cron treats a plain % as the end of the command and sends the rest to the command as standard input. Send the command back exactly as the listing returned it and the 1000-character limit is not applied, so you can change a job's schedule without rewriting a long command somebody created over SSH. A tab is accepted on that path too. Change the command itself and the limit applies again."},{"name":"reboot","type":"boolean","required":true,"description":"Run on reboot instead of a time schedule. When false, the schedule fields (minute, hour, day, month, weekday) are required."},{"name":"minute","type":"string","required":false,"description":"Cron minute field (required when reboot is false). Accepts 0-59, plus ranges, lists, steps, and the random-range form such as 6~15. A random range has to run upwards, so 15~6 is refused. A leading zero is fine, in a value and in a step"},{"name":"hour","type":"string","required":false,"description":"Cron hour field (required when reboot is false). Accepts 0-23, plus ranges, lists, steps, and the random-range form such as 2~4. A random range has to run upwards. A leading zero is fine, in a value and in a step"},{"name":"day","type":"string","required":false,"description":"Cron day of month field (required when reboot is false). Accepts 1-31, plus ranges, lists, steps, and the random-range form such as 1~5. A random range has to run upwards. A leading zero is fine, in a value and in a step"},{"name":"month","type":"string","required":false,"description":"Cron month field (required when reboot is false). Accepts 1-12 or the three-letter names JAN to DEC, plus ranges, lists, steps, and the random-range form such as 3~6. A random range has to run upwards, so DEC~MAR is refused while DEC-MAR is fine."},{"name":"weekday","type":"string","required":false,"description":"Cron day of week field (required when reboot is false). Accepts 0-7 where both 0 and 7 mean Sunday, or the three-letter names SUN to SAT, plus ranges, lists, steps, and the random-range form. A random range has to run upwards, so FRI~MON is refused while FRI-MON is fine. Note that 7 counts as seven for that test, so 1~7 works and 7~1 does not."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","warning":"string, only present when the change was saved but the job it replaced could not be removed, so the website may now have two similar cronjobs. List them again and remove the one you do not want."}},{"name":"delete_cron","description":"DESTRUCTIVE - Delete one or more cronjobs. Every id is read first, and an unreadable one is refused with a 400 before anything is removed. The removals themselves are applied one at a time on the server, so if one fails the ones before it have already been removed - list the cronjobs again to see what is left rather than assuming the whole call was undone. A cronjob the server cannot find is not an error when others in the same call were removed, so a retry of a partly-applied batch still succeeds - the reply reports how many were actually removed and how many were not found. If none of the ids match a cronjob on the server, the reply is a 400 and nothing is reported as deleted. One call will stop early if 50 of the ids in it turn out not to be on the server, and the reply says so and says how many were tried, because a list where that many ids are stale is a list worth rebuilding. Deleting your own cronjobs never reaches that point.","method":"DELETE","path_template":"/servers/websites/{website}/cron","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"ids","type":"array","required":true,"description":"Array of cronjob ids from the list endpoint, used as they are. Maximum 500 per call, because each one is applied on the server individually. A call stops early once 50 of the ids have come back not-found, so send ids from a recent listing rather than a stored list."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_preview_schedule","description":"Preview next 5 run times for a cron schedule.","method":"POST","path_template":"/servers/websites/{website}/cron/preview-schedule","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"minute","type":"string","required":false,"description":"Cron minute field (default: *)"},{"name":"hour","type":"string","required":false,"description":"Cron hour field (default: *)"},{"name":"day","type":"string","required":false,"description":"Cron day of month field (default: *)"},{"name":"month","type":"string","required":false,"description":"Cron month field (default: *)"},{"name":"weekday","type":"string","required":false,"description":"Cron day of week field (default: *)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.next_runs":"array of strings (up to 5 ISO 8601 timestamps in UTC)"}},{"name":"create_execute","description":"DESTRUCTIVE - Execute a cronjob immediately and return its output. The cronjob must already be in the website's crontab. A run started this way is not written to the cron log, so the response is the only record of it.","method":"POST","path_template":"/servers/websites/{website}/cron/{cron}/execute","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"cron","type":"string","required":true,"description":"The cronjob id from the list endpoint, used as it is (URL parameter). It is URL-safe base64."}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.output":"string (what the command printed to standard output, empty when it printed nothing)"}}]},{"name":"websites_databases","scope":"websites:databases","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"get_postgres_details","description":"Get the PostgreSQL connection details for the server this website runs on, so you can build a connection string for one of its databases. USE THIS ONE, not /servers/postgres-details/{server}: this version only needs access to the WEBSITE, so it works on a shared-hosting plan, while the server-scoped one needs server-level access and answers 403 to most customers. Note what these details are usable FOR: PostgreSQL normally listens on loopback only, so they let an application RUNNING ON THAT SERVER connect, not a client on your own machine. FIVE ANSWERS, so branch on the status. 200 with the details. 404 means this server cannot tell you - either it has no PostgreSQL, or its server software predates this feature - and it is permanent, so do not retry. 502 means the server answered with nothing usable, which is worth retrying. 400 means the server is busy with another operation, so wait a moment and retry. 500 is anything else. On \"host\": this endpoint reports the numeric loopback address 127.0.0.1, while the create-database and create-database-user responses report the name localhost for the same server. Both are correct and both reach the same PostgreSQL - they come from different tools - so do not treat the difference as a signal about which server you are on.","method":"GET","path_template":"/servers/websites/postgres-details/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter). The server is resolved from the website, so you never need to know or send a server id."}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.host":"string|null - the address to connect to from the server itself","data.port":"integer|null","data.socket_path":"string|null - the Unix socket directory. Use this one, not /tmp","data.remote_enabled":"boolean - whether PostgreSQL listens on more than loopback. This describes the listener only. It is not a promise that the port is reachable from outside the server, because the firewall is a separate layer"}},{"name":"get_mysql_details","description":"Get the MySQL connection details for the server this website runs on. USE THIS ONE, not /servers/mysql-details/{server}: this version only needs access to the WEBSITE, so it works on a shared-hosting plan, while the server-scoped one needs server-level access and answers 403 to most customers. THREE ANSWERS, so branch on the status. 200 with the details. 400 means the server is busy with another operation, so wait a moment and retry. 500 is anything else, including a server that cannot be reached right now.","method":"GET","path_template":"/servers/websites/mysql-details/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter). The server is resolved from the website, so you never need to know or send a server id."}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.host":"string|null","data.remote_host":"string|null","data.port":"integer","data.socket_path":"string|null"}},{"name":"get_databases","description":"Get list of databases for a website. Without \"engine\" the list covers BOTH engines, each row tagged with its engine (mysql or pg); pass engine=mysql or engine=pg to list one engine only. A row is identified by the PAIR (id, engine), never by id alone: a MySQL and a PostgreSQL database can share the same name and id, so pass the row's engine to the stats, delete and database-users endpoints. When one engine could not be listed (for example PostgreSQL is busy), the rows of the other engine are still returned and a \"warnings\" array in the response says which engine is missing - check it before you tell anyone a database is not there. The list is paged: read meta.last_page and keep asking for the next page before you tell anyone how many databases there are, or that a name is not there.","method":"GET","path_template":"/servers/websites/databases/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"engine","type":"string","required":false,"description":"Limit the list to one database engine: \"mysql\" or \"pg\" for PostgreSQL. Leave it out to list both engines together."},{"name":"page","type":"integer","required":false,"description":"Which page to return, starting at 1. Defaults to 1. Compare meta.current_page with meta.last_page to see whether more pages are left."},{"name":"per_page","type":"integer","required":false,"description":"How many databases per page. Values below 10 are raised to 10 and values above 200 are lowered to 200."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"string","data[].name":"string","data[].size":"integer","data[].engine":"string (mysql or pg)","meta":"object","warnings":"array of string (present only when one engine could not be listed - the other engine's rows are still returned)"}},{"name":"get_dbadmin","description":"Get a one-time Database Admin SSO login URL for a website. The URL logs straight into the unified Database Admin as a temporary scoped role with full read/write on this website's own databases, and is valid for about 30 seconds. Requires full website permission.","method":"GET","path_template":"/servers/websites/databases/dbadmin/{website}/{engine}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"engine","type":"string","required":true,"description":"Database engine to open: \"mysql\" or \"pg\" for PostgreSQL (URL parameter)."}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.url":"string","data.expires":"string"}},{"name":"get_structure","description":"Get the website's database structure tree (databases and their tables). Table browsing is MySQL only: passing engine=pg is refused, and PostgreSQL tables are browsed in the Database Admin instead. On a server with PostgreSQL the tree carries one extra node named <username>__postgres whose children are the PostgreSQL databases. The node is absent in four cases: PostgreSQL is not installed there; the website has no PostgreSQL database; the server has not been updated to the tools release that honours a PostgreSQL selection yet (a selection under the node would be ignored there, so it is not offered - a restore or copy naming it is refused with a message that says so); or the PostgreSQL databases could not be listed right now and the website's last usage measurement saw no PostgreSQL data. A missing node therefore never proves the website has no PostgreSQL databases - use the databases list endpoint (with its warnings array) for that. Use those names (and the same node) when selecting PostgreSQL databases for a copy, refresh or restore. That node name is reserved: a MySQL database that happens to carry it is left out of the tree.","method":"GET","path_template":"/servers/websites/databases/structure/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.":"object (tree structure with databases and tables)"}},{"name":"get_tables","description":"Get list of tables in a database. MySQL returns the table names. PostgreSQL returns the base tables across the database's non-system schemas as \"schema.table\" (for example \"public.users\"), ordered by schema then name, read through the same scoped, read-only path as the query endpoint but under its own limit of 30 listings a minute per account, shared by every website you own (it does not spend the query endpoint's raw-SQL budget). A PostgreSQL database with more than 2000 tables is answered with the first 2000 and \"truncated\": true - never a short list presented as complete; narrow with a query on information_schema.tables in that case.","method":"GET","path_template":"/servers/websites/databases/tables/{website}/{database}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"database","type":"string","required":true,"description":"Database name (URL parameter)"}],"body_params":[{"name":"engine","type":"string","required":false,"description":"Database engine: \"mysql\" or \"pg\" for PostgreSQL. Leave it out and it is worked out from the database name: MySQL when a MySQL database with that name exists, otherwise PostgreSQL. Send \"pg\" whenever the website has a MySQL database of the same name."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"string","data[].name":"string","truncated":"boolean - PostgreSQL only: true when the database holds more than 2000 tables and only the first 2000 are listed"}},{"name":"create_databases","description":"CONDITIONALLY DESTRUCTIVE - Create a new database. Needs confirmation when you send include_password=true, because that returns the new user's password in plain text, the same reason the create-database-user endpoint asks. A plain create is not gated. On PostgreSQL the returned user OWNS it and bypasses row level security, so point an RLS app at a second user. The user returned in the response OWNS this database. PostgreSQL does not apply a table's row level security policies to the table's owner unless the table is set to FORCE, and an owner can turn that back off. So if your app relies on row level security to keep its own customers apart, do not point the app at this user. Create a SECOND user with the create-database-user endpoint and point the app at that one, keeping this first user for migrations and admin work.","method":"POST","path_template":"/servers/websites/databases/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"engine","type":"string","required":false,"description":"Database engine: \"mysql\" (default) or \"pg\" for PostgreSQL."},{"name":"name","type":"string","required":true,"description":"Database name"},{"name":"create_new_user","type":"boolean","required":false,"description":"When true (default), creates a new database user for this database, on the same engine. When false, links an existing user instead. Note: existing_user is prohibited when this is true. It accepts a real JSON boolean and the ordinary written forms an HTTP client sends - the strings true, false, 1, 0, yes, no, on and off - and a value we cannot read either way is REFUSED with 422 naming this field, rather than quietly counting as false and then demanding existing_user. Sending it as an explicit null is treated as not sending it at all, so the documented default of true applies. Generated API clients emit nulls for optional fields as a matter of course, and before this it silently meant false. FROM AN MCP CLIENT send a real boolean, not the text \"true\": this field is typed as a boolean in the tool schema, so a string is refused before the call reaches us. The written forms above are for plain HTTP, where any of them works. That is the opposite of the import flags ftp and remove, which are typed as text and need the string. On PostgreSQL the user you get back OWNS the database, and PostgreSQL does not enforce a table's row level security against its owner unless the table is set to FORCE, which an owner can undo. So if your application relies on row level security to keep its own customers apart, do not point it at this user - create a SECOND user afterwards and use that one, keeping this first user for migrations and admin work. That second user can create tables by default and owns what it creates, so if it must not add tables, turn its CREATE privilege off with PUT /servers/websites/{website}/databases/users/{database}/{user}/privileges, sending {\"engine\": \"pg\", \"privilege\": \"CREATE\", \"action\": \"off\"}, before it creates anything. That switch covers every schema of the database."},{"name":"existing_user","type":"string","required":false,"description":"The existing database user to link to this database, on the same engine. Required when create_new_user is false, prohibited when create_new_user is true."},{"name":"include_password","type":"boolean","required":false,"description":"Send true to get the new user's password back in this response. A value we cannot read as true or false is REFUSED with 422 naming this field, rather than quietly counting as false and withholding the password you asked for. FROM AN MCP CLIENT send a real boolean, not the text \"true\": this field is typed as a boolean in the tool schema, so a string is refused by your own client before the call reaches us. The written forms are for plain HTTP. Defaults to false, and then data.password comes back as null and the password is not recoverable later - you would have to set a new one with the change-password endpoint. Ask for it only when your code is going to store or use it right away. It defaults to false because the password is returned in plain text: if you are calling through an AI assistant, the whole response goes into that conversation, which your AI client saves and may sync elsewhere."}],"requires_confirmation":false,"prerequisites":[],"requires_confirmation_when":{"include_password":true},"response_structure":{"success":"boolean","message":"string","data":"object","data.id":"string","data.name":"string","data.host":"string|null - the address as seen FROM this server. On PostgreSQL the server listens on loopback only, so these credentials work from an application or a shell running on that server and not from your own machine. On MySQL remote access can be switched on, and when it is, the mysql-details endpoint reports the remote host to connect to from outside. Use this value with port, or use the socket path from the details endpoint for the engine you are on","data.port":"integer|null - null on MySQL creates, which do not report a port. Use the standard MySQL port","data.database":"string|null","data.user":"string|null","data.password":"string|null - THE LIVE PASSWORD OF THE USER JUST CREATED, in plain text, and only when you sent include_password=true. Without that flag this is null, which is the default. It is returned only here and is not stored anywhere you can read it back, so save it now. It can contain any of ! @ # $ % ^ and * ( ), so percent-encode it before putting it into a connection URL and quote it before putting it into a shell command. Passing it raw into a postgres:// or mysql:// string will usually fail to parse. Also null when create_new_user is false, because linking an existing user creates no new password. If you lose it, set a new one with the change-password endpoint","data.engine":"string|null - \"mysql\" or \"pg\". Every field listed here is always present, so a null means that branch genuinely has no value, never that the field is missing"}},{"name":"create_query","description":"CONDITIONALLY DESTRUCTIVE - Run a raw SQL query against one of the website's own databases. Reads run freely. A data-modifying statement (INSERT, UPDATE, DELETE, or a DDL statement like CREATE or DROP) runs ONLY when you set \"write\" to true. Permission follows the STATEMENT, not the HTTP verb: a collaborator invited to this website with read_only access can run read statements here, and gets HTTP 403 on any data-modifying statement, exactly as in the panel. What DDL can actually do differs by engine. On MySQL you get full table DDL on the one database you name in this call: CREATE TABLE, ALTER TABLE, DROP TABLE and CREATE INDEX all work (creating views, routines, triggers and events does not). One honest limit on MySQL: this query runs as a temporary login that can reach only the database you named, and MySQL decides what a listing shows from what the login can reach. So SHOW DATABASES, SHOW SCHEMAS and a read of information_schema.SCHEMATA return only that one database. They do not fail, and the short list looks complete. To see every database on the account, use the database list endpoint instead. On PostgreSQL this endpoint runs your query as a restricted role that can read and change data but cannot create or drop schema objects, so CREATE TABLE fails with a permission error. To make schema changes on PostgreSQL you have two routes, and which one is open to you depends on where your code runs. The first is to connect to PostgreSQL directly as the database owner, using the credentials returned when the database was created (you only get the password back on that call if you sent include_password=true, so if you did not, use the change-password endpoint to set a new one) - but only FROM A SHELL OR AN APPLICATION ON THAT SERVER, because PostgreSQL listens on loopback only. It is not reachable from your own machine and cannot be used over MCP at all, so if you are reading this through an API client or an assistant, this route is closed to you. The second route, and the only one open to a remote or MCP caller, is the database import endpoint - but read how it takes the file: uploading one needs a multipart/form-data request, which this API accepts but an MCP client cannot send. Over MCP, first put the .sql file on the website (the files scope deploys files), then call import with \"engine\" set to \"pg\", \"ftp\" set to \"true\", \"file\" set to that path on the server, and \"remove\" set to \"true\". The \"engine\" value is not optional here: import defaults to MySQL, so leaving it out sends your PostgreSQL dump to the wrong database engine. Two things to know, because the staged file sits in your site's PUBLIC folder. Treat that file as reachable by anyone for as long as it is there: servers running the newest configuration refuse to serve the common dump file endings, older ones do not, and unusual multi-part archive names are not all covered either way. And \"remove\" deletes the file only after a SUCCESSFUL import, so if the import fails the file stays behind - delete it yourself before you walk away. IF YOU NO LONGER HAVE THAT PASSWORD, the change-password endpoint will give you a working one - but read this first: it does not reveal the existing password, it RESETS the owner to a new random one and returns that. Anything already connecting with the old password stops working until you update it, so do not call it on a live database owner without planning for that. Several read-shaped statements count as data-modifying too and need \"write\" set to true: one that changes a sequence (setval(), nextval(), or the MariaDB \"NEXT VALUE FOR\" and \"PREVIOUS VALUE FOR\" syntax), one that changes a PostgreSQL large object (lo_unlink, lo_put and that family), one that hands SQL to a function that runs it (ts_stat, query_to_xml, crosstab, connectby, dblink), one that writes a file with SELECT ... INTO OUTFILE, and an EXPLAIN of a statement that would execute. Supports MySQL (default) and PostgreSQL. The query is tenant-scoped, so it can only reach this website's own DATA. One honest limit on PostgreSQL: the cluster's shared catalog views (pg_database, pg_roles and the like) are readable by every role PostgreSQL creates, which is the engine's own default, so a query against those lists names from the whole server rather than just your own. Your data is not reachable that way and neither is anyone else's, but the names are. Results come back typed: a read returns columns and rows, and a write reports affected_rows (the row count). On a MySQL node that has not yet received the latest server update, a read's columns and a write's affected_rows both come back null. ERRORS COME IN TWO SHAPES, so branch on the HTTP status rather than on a body key. An ENGINE error, meaning your statement reached the database and the database rejected it, is HTTP 400 with {\"message\": \"...\", \"proper\": false} and carries no \"success\" key. An INPUT refusal, meaning the panel rejected the request before running it, is HTTP 400 or 422 with {\"success\": false, \"message\": \"...\"}. Input refusals cover an unrecognized engine, a query that cannot be parsed or scoped to your databases, and a data-modifying statement sent without \"write\": true. Every successful response carries \"success\": true. On an engine error the message carries the database engine's OWN error text, so you can read the real reason - a syntax error, a missing column, or a permission denial - and fix the statement yourself. Internal server paths and internal role names are stripped out first, the message is capped at 2000 characters ending with \" [truncated]\" when it was cut, and a support code in the form \"(Support Hash: ERR_XXXXXXXX)\" is appended after that cap. Quote that code to support if you need help, and ignore it otherwise. You do not need any special request header to receive this. Every response under this API is JSON, including errors, whether or not you send \"Accept: application/json\". Sending it is still good practice, and it was required until 2026-08-05, so an older integration that added it can leave it in place. RATE LIMIT: this endpoint allows 6 requests per minute, which is lower than the rest of the API because each query keeps a database connection open on your server while it runs. The six are counted against the ACCOUNT THAT OWNS THE WEBSITE, and they are ONE budget for that whole account rather than six per site: every website the account owns draws on the same six, along with that account's own keys, its MCP clients, Proper, and anyone invited to any of those sites, with full or read_only access. So extra keys do not raise it, spending the six on one of your sites means the next call to another of your sites is refused, and if you were invited to somebody else's website you can be refused because THEY have been running queries. A CALL THAT NEVER RUNS ANY SQL CAN STILL SPEND ONE OF THE SIX: the budget is charged as soon as we have to ask your server something on your behalf, which happens when you leave \"database\" out on a read. On a site with two or more databases that call comes back asking you to name one, and it has already cost a slot - so a script looping on that answer runs out of budget without a single statement executing. Naming \"database\" avoids it entirely. Over the limit you get 429 with a Retry-After header saying how many seconds to wait. TIME LIMITS: there are two of them and they mean different things. YOUR STATEMENT GETS 45 SECONDS. On PostgreSQL the database server always stops it there. On MySQL it normally does, and when it cannot, the panel stops the whole call a few seconds later instead, so 45 is the number to budget against either way, and the message you get back says which of the two happened. SEPARATELY, the panel waits about 60 seconds for your server to answer at all, then stops waiting and returns an engine-shaped error (HTTP 400, {\"message\": \"...\", \"proper\": false}). That longer wait is about reaching your server, not about how long your statement may run, so budget your query against the 45 and never against the 60. One more thing sits outside both: if you leave \"database\" out on a read, we first ask your server which databases exist, and that lookup has a 60-second wait of its own on top, so the whole call can take about two minutes. When we cannot reach your server on the fast channel we re-issue that lookup on a slower one, where it can take up to five minutes. Naming \"database\" in the request keeps you inside the single 60-second wait, which matters most over MCP, because MCP clients here give up after two minutes and hand you a transport error instead of our message. Do NOT treat that as a plain failure and retry it blindly, because what it means depends on what you sent. STOPPING THE WAIT DOES NOT CANCEL YOUR STATEMENT - it may still be running on the server - so this is never a promise that nothing happened. For a READ, the statement changed nothing by itself, but your query may still be running, so wait a moment before sending it again rather than retrying immediately and leaving two copies of an expensive read running at once. One exception worth knowing: a read that CALLS A FUNCTION can write through that function, and we classify by reading your SQL, so we cannot see inside one you wrote. If your query calls a function of your own that writes, check the data before you resend. For a WRITE we cannot tell whether your statement was applied, so check the data in your database before you send it again, or you may repeat the change. The message you get back says which of the two it is.","method":"POST","path_template":"/servers/websites/databases/query/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"engine","type":"string","required":false,"description":"Database engine: \"mysql\" (default) or \"pg\" for PostgreSQL."},{"name":"query_text","type":"string","required":true,"description":"The SQL statement to run, one statement at a time. Sending two separated by a semicolon comes back as a 422 rather than running either. On MySQL that also covers a block that WRAPS several statements in BEGIN ... END, which the server itself would accept as one - split it up, or use the database import endpoint to run a whole script. A bare SELECT is auto-limited to 100 rows, so add your own LIMIT and OFFSET to page through more. ERRORS COME BACK IN TWO SHAPES, so branch on the HTTP status and not on a body key. An ENGINE error - your statement reached the database and the database rejected it - is HTTP 400 with {\"message\": \"...\", \"proper\": false} and carries NO \"success\" key. An INPUT refusal - rejected before it ran, for an unrecognized engine, a query that cannot be scoped to your databases, or a write sent without \"write\": true - is HTTP 400 or 422 with {\"success\": false, \"message\": \"...\"}. On an engine error the message carries the database's OWN text, so you can read the real reason and fix the statement: internal server paths and internal role names are stripped first, the message is capped at 2000 characters ending in \" [truncated]\" when it was cut, and a support code like \"(Support Hash: ERR_XXXXXXXX)\" is appended after that cap. Quote that code to support if you need help, ignore it otherwise."},{"name":"database","type":"string","required":false,"description":"The database to run against. Required for a write. A read auto-resolves the website's single database when omitted, but that convenience costs you twice. It costs TIME: resolving it means asking your server for the database list first, which has its own 60-second wait on top of the statement's. And it costs BUDGET: because we have to call your server, one of the account's 6 queries a minute is spent at that point, before any SQL runs - so on a site with two or more databases, where the answer is \"specify which one\", the slot is gone anyway. Name it here whenever you already know it, and always over MCP, where the client gives up after two minutes."},{"name":"write","type":"boolean","required":false,"description":"Set to true to allow a data-modifying statement (INSERT, UPDATE, DELETE, or DDL) to run. A value we cannot read as true or false is REFUSED with 422 naming this field, rather than quietly counting as false. FROM AN MCP CLIENT send a real boolean, not the text \"true\": this field is typed as a boolean in the tool schema, so a string is refused by your own client before the call reaches us. The written forms are for plain HTTP. WHAT DDL CAN DO DIFFERS BY ENGINE. On MySQL you get full table DDL on the one database you name in this call: CREATE TABLE, ALTER TABLE, DROP TABLE and CREATE INDEX all work (views, routines, triggers and events do not). One honest limit on MySQL: this query runs as a temporary login that can reach only the database you named, and MySQL decides what a listing shows from what the login can reach. So SHOW DATABASES, SHOW SCHEMAS and a read of information_schema.SCHEMATA return only that one database. They do not fail, and the short list looks complete. To see every database on the account, use the database list endpoint instead. On PostgreSQL this endpoint runs your query as a restricted role that can read and change DATA but cannot create or drop SCHEMA OBJECTS, so CREATE TABLE fails with a permission error even with write set to true. TO MAKE SCHEMA CHANGES ON PostgreSQL you have two routes. (1) Connect to PostgreSQL directly as the database owner, using the credentials returned when the database was created (you only get the password back on that call if you sent include_password=true, so if you did not, use the change-password endpoint to set a new one) - but only FROM A SHELL OR AN APPLICATION RUNNING ON THAT SERVER, because PostgreSQL listens on loopback only, so this route does not work from your own machine and cannot be used over MCP at all. (2) The database import endpoint, which is the only route available to a remote or MCP caller: put a .sql file on the website (the files scope deploys files), then call import with \"engine\" set to \"pg\", \"ftp\" set to \"true\", \"file\" set to that path, and \"remove\" set to \"true\". Send \"engine\": \"pg\" there: an import without it is REFUSED for a name that exists only on PostgreSQL (an import has no undo), and a name both engines share lands on MySQL. The staged file sits in your site's PUBLIC folder while it is there, so treat it as reachable by anyone and delete it as soon as the import finishes (\"remove\" only deletes after a SUCCESSFUL import, and even then check \"removed\" in the response, because the deletion itself can fail). TWO THINGS MAKE IT SAFER and you want both, because neither is a guarantee on its own. First, put the file inside a folder whose name starts with a dot: servers carrying that rule answer 400 for anything inside a dot-named folder. Read that the same way as the file-ending rule, as how the servers are set up rather than a promise every one of them keeps. Second, and this part depends on no server rule at all, give the folder or the file a name nobody could guess, generated fresh for each run - for example \".dbstage-8f3c1a9d47/dump.sql\". Do not reuse a path you read in documentation or used last time, because a fixed path is the first thing anyone would try. TWO LIMITS, so you do not over-trust any of it. Do NOT use \".well-known/\": that one folder is deliberately served, so a file in it stays reachable. And this is how the servers are configured today rather than a promise, so still delete the file yourself once the import is done. A read-shaped statement counts as data-modifying too when it changes a sequence (setval(), nextval(), or the MariaDB \"NEXT VALUE FOR\" and \"PREVIOUS VALUE FOR\" syntax), changes a PostgreSQL large object (lo_unlink, lo_put and that family), hands SQL to a function that runs it (ts_stat, query_to_xml, crosstab, connectby, dblink), writes a file with SELECT ... INTO OUTFILE, or is an EXPLAIN of a statement that would execute. A read does not need it. Defaults to false, meaning reads only. THIS FLAG ALSO DECIDES WHAT A TIMEOUT MEANS TO YOU. Your statement gets 45 seconds, which is the shorter of the two limits and the one to budget against. The panel separately waits about 60 seconds for the server to answer at all (longer on a read that leaves \"database\" out, which the endpoint description explains), and giving up on that wait does not cancel your statement. With write false, the statement changed nothing by itself, but your read may still be running, so pause before sending it again. The one exception: a read that calls a function of your own can write through that function, and we classify by reading your SQL rather than by running it, so check the data before resending if yours does. With write true, a timeout means we cannot tell whether the statement was applied, so check the data before resending rather than retrying automatically."}],"requires_confirmation":false,"prerequisites":[],"requires_confirmation_when":{"write":true},"response_structure":{"success":"boolean","data":"object","data.engine":"string, \"mysql\" or \"pg\"","data.database":"string, the database this query actually ran against. Always present, including when you left the database name out and let us pick it. It is also the visibility boundary on MySQL, so a listing in your result describes this database and not your whole account.","data.is_read_only":"boolean, true when the query was classified as a read","data.columns":"an array of column-name strings for a read or a RETURNING write, an empty array for a plain write that returns no columns, or null on a legacy MySQL node that does not send column names","data.rows":"an array of row arrays (each row is an array of that row's column values in order) for a read or a RETURNING write, or an empty array for a plain write","data.row_count":"integer, the number of rows returned (the row count for a read or a RETURNING write, or 0 for a plain write)","data.affected_rows":"for a write, the number of rows affected - on MySQL the rows actually changed, on PostgreSQL the rows matched by the WHERE clause even if unchanged (so a no-op UPDATE returns 0 on MySQL but the matched count on PostgreSQL). Null for a read, and also null for a MySQL write on a node that has not yet received the typed-driver update (the same legacy-node caveat as columns)."}},{"name":"delete_databases","description":"DESTRUCTIVE - Delete databases. An explicit engine applies to the whole batch (pass engine=pg to delete PostgreSQL databases); without one, each name is routed to the engine that has it.","method":"DELETE","path_template":"/servers/websites/databases/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"engine","type":"string","required":false,"description":"Database engine for the whole batch: \"mysql\" or \"pg\" for PostgreSQL. Leave it out and each name is deleted as a MySQL database. A name that exists only as a PostgreSQL database is refused, and nothing in the batch is deleted, until you send engine=pg: a delete cannot be undone, so it never falls through to the other engine on its own."},{"name":"ids","type":"array","required":true,"description":"Array of database names to delete. Maximum 500 per call, because each one is applied on the server individually."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_stats","description":"Get database statistics. PostgreSQL returns engine-native stat keys (size_bytes, active_connections, tables).","method":"GET","path_template":"/servers/websites/databases/stats/{website}/{database}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"database","type":"string","required":true,"description":"Database name (URL parameter)"}],"body_params":[{"name":"engine","type":"string","required":false,"description":"Database engine: \"mysql\" or \"pg\" for PostgreSQL. Leave it out and it is worked out from the database name: MySQL when a MySQL database with that name exists, otherwise PostgreSQL. Send it when the website has a MySQL and a PostgreSQL database with the same name."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object"}},{"name":"get_processes","description":"Get this website's live database processes (running queries), tenant-scoped. Works for MySQL and PostgreSQL. Pass engine=pg for PostgreSQL, which returns PostgreSQL-native row keys (pid, usename, datname, state, query_start, query, duration) instead of the MySQL keys shown below.","method":"GET","path_template":"/servers/websites/databases/processes/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"engine","type":"string","required":false,"description":"Database engine: \"mysql\" (default) or \"pg\" for PostgreSQL."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"integer","data[].user":"string","data[].host":"string","data[].db":"string","data[].command":"string","data[].time":"integer","data[].state":"string","data[].info":"string|null"}},{"name":"delete_processes","description":"DESTRUCTIVE - Kill (terminate) this website's own database processes. Works for MySQL and PostgreSQL. The server verifies each process belongs to this website before terminating it, so only your own connections can be killed. Pass engine=pg for PostgreSQL.","method":"DELETE","path_template":"/servers/websites/databases/processes/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"engine","type":"string","required":false,"description":"Database engine: \"mysql\" (default) or \"pg\" for PostgreSQL."},{"name":"ids","type":"array","required":true,"description":"Process IDs to kill"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_all","description":"Get all database users for a website.","method":"GET","path_template":"/servers/websites/databases/users/all/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"engine","type":"string","required":false,"description":"Database engine: \"mysql\" (default) or \"pg\" for PostgreSQL."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"string","data[].name":"string"}},{"name":"get_users","description":"Get users for a specific database.","method":"GET","path_template":"/servers/websites/databases/users/{website}/{database}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"database","type":"string","required":true,"description":"Database name (URL parameter)"}],"body_params":[{"name":"engine","type":"string","required":false,"description":"Database engine: \"mysql\" or \"pg\" for PostgreSQL. Leave it out and it is worked out from the database name: MySQL when a MySQL database with that name exists, otherwise PostgreSQL. Send it when the website has a MySQL and a PostgreSQL database with the same name."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"string","data[].name":"string","meta":"object"}},{"name":"create_users","description":"DESTRUCTIVE - Create a database user. Needs confirmation because it creates a new login that can reach this database, and when you send include_password=true it returns that login's password in plain text. On PostgreSQL it can create tables by default, so revoke CREATE if this user must not add any. A user owns the tables it creates. If this user must not add tables, turn its CREATE privilege off with PUT /servers/websites/{website}/databases/users/{database}/{user}/privileges, sending {\"engine\": \"pg\", \"privilege\": \"CREATE\", \"action\": \"off\"}. That covers every schema of the database.","method":"POST","path_template":"/servers/websites/databases/users/{website}/{database}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"database","type":"string","required":true,"description":"Database name (URL parameter)"}],"body_params":[{"name":"engine","type":"string","required":false,"description":"Database engine: \"mysql\" or \"pg\" for PostgreSQL. Leave it out and it is worked out from the database name: MySQL when a MySQL database with that name exists, otherwise PostgreSQL. Send it when the website has a MySQL and a PostgreSQL database with the same name."},{"name":"name","type":"string","required":true,"description":"New user name (without username_ prefix). Must start with a letter and may contain letters, numbers, underscores, and dashes. Maximum 32 characters. On PostgreSQL this user can create tables by default, and it owns the tables it creates, which lets it turn off row level security on those tables. If this user must not add tables, turn its CREATE privilege off with PUT /servers/websites/{website}/databases/users/{database}/{user}/privileges, sending {\"engine\": \"pg\", \"privilege\": \"CREATE\", \"action\": \"off\"}, before it creates anything. That switch covers every schema of the database."},{"name":"include_password","type":"boolean","required":false,"description":"Send true to get the new user's password back in this response. A value we cannot read as true or false is REFUSED with 422 naming this field, rather than quietly counting as false and withholding the password you asked for. FROM AN MCP CLIENT send a real boolean, not the text \"true\": this field is typed as a boolean in the tool schema, so a string is refused by your own client before the call reaches us. The written forms are for plain HTTP. Defaults to false, and then data.password comes back as null and the password is not recoverable later - you would have to set a new one with the change-password endpoint. Ask for it only when your code is going to store or use it right away. It defaults to false because the password is returned in plain text: if you are calling through an AI assistant, the whole response goes into that conversation, which your AI client saves and may sync elsewhere."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object","data.id":"string","data.name":"string","data.host":"string|null - the address as seen FROM this server. On PostgreSQL the server listens on loopback only, so these credentials work from an application or a shell running on that server and not from your own machine. On MySQL remote access can be switched on, and when it is, the mysql-details endpoint reports the remote host to connect to from outside. Use this value with port, or use the socket path from the details endpoint for the engine you are on","data.port":"integer|null - null on MySQL, which does not report a port. Use the standard MySQL port","data.database":"string|null - the database this user was granted access to","data.user":"string|null","data.password":"string|null - THE LIVE PASSWORD OF THE USER JUST CREATED, in plain text, and only when you sent include_password=true. Without that flag this is null, which is the default. It is returned only here and is not stored anywhere you can read it back, so save it now. It can contain any of ! @ # $ % ^ and * ( ), so percent-encode it before putting it into a connection URL and quote it before putting it into a shell command. Passing it raw into a postgres:// or mysql:// string will usually fail to parse. If you lose it, set a new one with the change-password endpoint","data.engine":"string|null - \"mysql\" or \"pg\". Every field listed here is always present, so a null means that branch genuinely has no value, never that the field is missing"}},{"name":"create_link","description":"DESTRUCTIVE - Give an existing database user access to an existing database (full access to that one database). Refuses a user that already has access to the database.","method":"POST","path_template":"/servers/websites/databases/users/link/{website}/{database}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"database","type":"string","required":true,"description":"Database name (URL parameter)"}],"body_params":[{"name":"engine","type":"string","required":false,"description":"Database engine: \"mysql\" or \"pg\" for PostgreSQL. Leave it out and it is worked out from the database name: MySQL when a MySQL database with that name exists, otherwise PostgreSQL. Send it when the website has a MySQL and a PostgreSQL database with the same name."},{"name":"user","type":"string","required":true,"description":"Existing database user to attach. The full prefixed name or the short name both work. The user must already exist on the account."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object","data.id":"string","data.name":"string"}},{"name":"delete_users","description":"DESTRUCTIVE - Delete a database user.","method":"DELETE","path_template":"/servers/websites/databases/users/{website}/{database}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"database","type":"string","required":true,"description":"Database name (URL parameter)"}],"body_params":[{"name":"engine","type":"string","required":false,"description":"Database engine: \"mysql\" or \"pg\" for PostgreSQL. Leave it out and it is worked out from the database name: MySQL when a MySQL database with that name exists, otherwise PostgreSQL. Send it when the website has a MySQL and a PostgreSQL database with the same name."},{"name":"ids","type":"array","required":true,"description":"The database user to delete, as a one-item array of user names, for example [\"myapp_reader\"]. The full prefixed name or the short name both work. Only the FIRST entry is used: sending more than one name still deletes just that first user, and the reply does not say so. Delete the rest with one call each."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_optimize_all","description":"Start optimizing all of a website's databases. This QUEUES the work on your server and answers straight away, so a success here means the request was accepted, not that the optimization has finished. There is no job id to poll, and a queued run can still fail on the server. On MySQL it runs OPTIMIZE, on PostgreSQL it runs VACUUM ANALYZE, and each engine has its own queue, so call it once per engine if the site holds databases in both.","method":"POST","path_template":"/servers/websites/{website}/databases/optimize-all","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"engine","type":"string","required":false,"description":"Database engine: \"mysql\" (default) or \"pg\" for PostgreSQL."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_download","description":"Download a database as a SQL file. Returns binary stream, not JSON.","method":"GET","path_template":"/servers/websites/{website}/databases/{database}/download","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"database","type":"string","required":true,"description":"Database name (URL parameter)"}],"body_params":[{"name":"engine","type":"string","required":false,"description":"Database engine: \"mysql\" or \"pg\" for PostgreSQL. Leave it out and it is worked out from the database name: MySQL when a MySQL database with that name exists, otherwise PostgreSQL. Send it when the website has a MySQL and a PostgreSQL database with the same name. PostgreSQL downloads are always plain .sql (the compressed flag is ignored)."},{"name":"compressed","type":"string","required":false,"description":"Set to true to download as gzipped SQL (default: false). Any ordinary true or false value works - true, \"true\", 1, \"1\", \"yes\", \"on\" and their false counterparts - and a value we cannot read as either is refused with 422 naming this field. In the MCP tool schema this flag accepts EITHER a boolean or the text \"true\", because download and export are two actions of one tool and share this parameter name. Over plain HTTP any ordinary true or false value works. PostgreSQL exports are never gzipped, so on engine \"pg\" this flag is ignored and you always get plain SQL."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"content_type":"application/octet-stream (binary download)"}},{"name":"create_export","description":"Export a database to a file on the server (relative path returned).","method":"POST","path_template":"/servers/websites/{website}/databases/{database}/export","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"database","type":"string","required":true,"description":"Database name (URL parameter)"}],"body_params":[{"name":"engine","type":"string","required":false,"description":"Database engine: \"mysql\" or \"pg\" for PostgreSQL. Leave it out and it is worked out from the database name: MySQL when a MySQL database with that name exists, otherwise PostgreSQL. Send it when the website has a MySQL and a PostgreSQL database with the same name. PostgreSQL exports are always plain .sql (the compressed flag is ignored)."},{"name":"compressed","type":"boolean","required":false,"description":"Compress the export (default: false). Any ordinary true or false value works - true, \"true\", 1, \"1\", \"yes\", \"on\" and their false counterparts - and a value we cannot read as either is refused with 422 naming this field. In the MCP tool schema this flag accepts EITHER a boolean or the text \"true\", because download and export are two actions of one tool and share this parameter name. Over plain HTTP any ordinary true or false value works. PostgreSQL exports are never gzipped, so on engine \"pg\" this flag is ignored and you always get plain SQL."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data.path":"string"}},{"name":"create_import","description":"DESTRUCTIVE - Load a .sql or .dump file into one of the website's databases. This is also the route for making SCHEMA changes on PostgreSQL, because the query endpoint runs as a restricted role that cannot create or drop schema objects - and for a remote or MCP caller it is the ONLY route, since PostgreSQL listens on loopback only. SET \"engine\" TO \"pg\" for a PostgreSQL database. Leave it out and the engine is worked out from the name (MySQL first, then PostgreSQL) - but a name that exists ONLY as a PostgreSQL database is REFUSED without \"engine\": \"pg\", because an import writes into the database with no undo, and a name both engines share lands on MySQL. ON POSTGRESQL THE FILE IS LOADED INTO THE EXISTING DATABASE AS IT IS: the database must already exist (the import never creates one), nothing in it is dropped first, an object the database already holds under the same name makes the import fail with import_failed, and the objects loaded before that failure stay in place - to replace a PostgreSQL database whole, restore it from a backup instead. THE STAGED FILE SITS IN YOUR SITE'S PUBLIC FOLDER while it is there, because the path is relative to public_html, so treat it as downloadable by anyone who guesses the name and delete it the moment the import finishes. \"remove\" only deletes after a SUCCESSFUL import, so a failed import leaves the file in place and you have to remove it yourself. Even on success the deletion can fail on its own, for example when the server is busy right after a large import, so check \"removed\" in the response rather than assuming: false means the file is still there. TWO THINGS MAKE IT SAFER and you want both, because neither is a guarantee on its own. First, put it inside a folder whose name starts with a dot: servers carrying that rule answer 400 for anything inside a dot-named folder. That is how the servers are set up rather than a promise every one of them keeps. Second, and this part depends on no server rule at all, give the folder or the file a name nobody could guess, generated fresh for each run - for example \".dbstage-8f3c1a9d47/dump.sql\". Do not reuse a path you read in documentation or used last time, because a fixed path is the first thing anyone would try. Read that the same way as the file-ending rule: it is how the servers are set up, not a promise every one of them keeps. TWO LIMITS: do NOT use \".well-known/\", which is deliberately served, and treat this as how the servers are configured today rather than a promise - delete the file yourself once the import is done. ON POSTGRESQL, RE-APPLY YOUR SECOND USER'S PRIVILEGES AFTER THE IMPORT, IN TWO STEPS. The import does not run as the database owner, so the tables it creates carry no privileges for any OTHER database user attached to that database. If you created a second user for your application (which the create-database and create-user endpoints tell you to do for row-level security), it cannot read the imported tables until you do BOTH of these, in this order: (1) POST /servers/websites/{website}/databases/users/{database}/{user}/privileges/preset with {\"engine\": \"pg\", \"preset\": \"full\"}, then (2) PUT /servers/websites/{website}/databases/users/{database}/{user}/privileges with {\"engine\": \"pg\", \"privilege\": \"CREATE\", \"action\": \"off\"}. Step 2 is NOT optional: \"full\" is the only preset that restores write access, and it also re-grants CREATE, which is exactly the privilege the row-level-security guidance tells you to take away. Both steps reach every schema in the database, so the two steps are complete even on a database with other schemas. The only other preset, \"readonly\", would leave your application unable to write.","method":"POST","path_template":"/servers/websites/{website}/databases/{database}/import","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"database","type":"string","required":true,"description":"Database name (URL parameter)"}],"body_params":[{"name":"engine","type":"string","required":false,"description":"Database engine: \"mysql\" or \"pg\" for PostgreSQL. Leave it out and it is worked out from the database name: MySQL when a MySQL database with that name exists, otherwise PostgreSQL - so a dump for a PostgreSQL database that has no MySQL twin lands on PostgreSQL (the target database must already exist - the import never creates it), but send \"pg\" whenever the website has a MySQL database of the same name. PostgreSQL accepts a plain .sql or a pg_dump custom-format .dump file. ON POSTGRESQL, FINISH WITH THE PRIVILEGE STEP OR THE IMPORT IS ONLY HALF DONE. The import does not run as the database owner, so any SECOND database user attached to that database has no privileges on the tables it just created and the application gets permission errors on its own tables. Restoring that access takes TWO calls in this order, and doing only the first is worse than doing nothing: POST /servers/websites/{website}/databases/users/{database}/{user}/privileges/preset with {\"engine\": \"pg\", \"preset\": \"full\"}, then PUT /servers/websites/{website}/databases/users/{database}/{user}/privileges with {\"engine\": \"pg\", \"privilege\": \"CREATE\", \"action\": \"off\"}. \"full\" is the only preset that restores write access and it also re-grants CREATE, which is the privilege the row-level-security guidance says to remove, so the second call is required. \"readonly\" would leave the application unable to write. Both calls reach every schema in the database."},{"name":"ftp","type":"string","required":false,"description":"Set to \"true\" to import a file that is already on the website instead of uploading one. A value we cannot read as true or false is REFUSED with 422 naming this field, rather than quietly counting as false. An MCP client must use this mode, because it cannot send a file upload, and must send the value as the text \"true\" because this field is typed as text in the tool schema. Over plain HTTP any ordinary true value works. Remember \"engine\": \"pg\" on a PostgreSQL import - without it a name that exists only on PostgreSQL is refused (an import has no undo), and a name both engines share lands on MySQL."},{"name":"file","type":"string","required":true,"description":"The file to import, and it means one of two things. When \"ftp\" is \"true\", send the path of a .sql or .dump file already on the website, relative to public_html - this is the mode to use from MCP. Getting the file onto the site first is where callers get stuck on size, because the two write routes differ: the file deploy action takes at most 1 MB per file and 8 MB per call, so a real database dump usually will not fit. For anything larger, create the file instead - POST /servers/websites/{website}/files with \"type\": \"file\", \"upload\": false and the SQL in \"code\", which has no such limit - or put it there over FTP. Otherwise send the file itself as a multipart/form-data upload, which needs a real file upload and so is only available to HTTP clients. IN THE \"ftp\" MODE THE FILE IS IN YOUR PUBLIC WEB FOLDER, because that path is relative to public_html and the file deploy endpoints can only write there. Anyone who guesses the name can download it while it is there, and a database dump is your whole database, so use an unguessable name and delete the file as soon as the import finishes. \"remove\" does that for you ONLY after a successful import - if the import fails, the file stays and removing it is on you. Safer still, put it inside a folder whose name starts with a dot AND give that folder a name nobody could guess, generated fresh for each run - for example \".dbstage-8f3c1a9d47/dump.sql\". Servers carrying the dot-folder rule answer 400 for anything inside such a folder, but that is how the servers are set up rather than a promise every one of them keeps, which is why the unguessable half matters: do not reuse a path you read in documentation or used last time. Read that the same way as the file-ending rule: it is how the servers are set up, not a promise every one of them keeps. Two limits - do NOT use \".well-known/\", which is deliberately served, and this is how servers are configured today rather than a promise, so still delete the file yourself."},{"name":"remove","type":"string","required":false,"description":"Set to true to delete the staged file from the website after the import. Any ordinary true value works - true, \"true\", 1, \"1\", \"yes\", \"on\" - and so does any ordinary false one - false, \"false\", 0, \"0\", \"no\", \"off\". A value that is neither is REFUSED with 422 naming this field, and the import does not run: a typo like \"ture\" used to count as false and silently leave your dump on the website, which is the opposite of what you asked for. To mean false, send a false value or leave the field out. FROM AN MCP CLIENT send it as the text \"true\", because this field is typed as text in the tool schema and a bare true is refused before the call reaches us. It only deletes after a SUCCESSFUL import: a failed import leaves the file in place ON PURPOSE, so a large upload that failed partway does not have to be uploaded again. The error response does not tell you the path, so keep the path you sent and delete the file yourself after a failure. Check \"removed\" in the success response before you assume the file is gone: it is true when the file was deleted, false when deletion was asked for and did not happen, and null when you did not ask. Applies to the \"ftp\" mode only."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","removed":"boolean|null - whether the staged file was deleted after a successful import. true means it is gone, false means you asked for deletion and it did not happen so the file is still in your web folder and you should delete it, null means you did not ask. Present on the \"ftp\" mode only"}},{"name":"list_hosts","description":"Get the list of allowed hosts for a database user. MySQL only. PostgreSQL has no per-host user grants.","method":"GET","path_template":"/servers/websites/{website}/databases/users/{database}/hosts","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"database","type":"string","required":true,"description":"Database name (URL parameter)"}],"body_params":[{"name":"user","type":"string","required":true,"description":"Database user name (query parameter)"},{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of {host}","meta.current_page":"integer","meta.per_page":"integer","meta.total":"integer"}},{"name":"create_hosts","description":"DESTRUCTIVE - Add a host for a database user. MySQL only. PostgreSQL has no per-host user grants. Needs confirmation because it widens who can reach the database: if remote MySQL access is on for this server, adding a host that is a wildcard, a pattern, or any plain hostname opens port 3306 to everyone, not only to the host you named. Removing a host is already confirmed, and adding one carries the same weight.","method":"POST","path_template":"/servers/websites/{website}/databases/users/{database}/hosts","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"database","type":"string","required":true,"description":"Database name (URL parameter)"}],"body_params":[{"name":"user","type":"string","required":true,"description":"Database user name"},{"name":"host","type":"string","required":true,"description":"Host to add: a public IP, a public IPv4 range in CIDR form (e.g. 203.0.113.0/24), a domain, localhost, or % for any host. Private and internal addresses are REFUSED - 192.168.x.x, 10.x.x.x and 172.16-31.x.x all come back as a 422, so allowing an office network needs its PUBLIC address rather than the one the machine reports for itself."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_hosts","description":"DESTRUCTIVE - Remove a host from a database user. MySQL only. PostgreSQL has no per-host user grants. Send the host exactly as the host list shows it. Removal accepts any value already on the list, including private addresses like 10.0.0.1 and wildcard patterns like 192.168.%.%, which the ADD endpoint refuses - grants of that shape were made before that rule existed and are exactly the ones a customer wants taken away.","method":"DELETE","path_template":"/servers/websites/{website}/databases/users/{database}/hosts","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"database","type":"string","required":true,"description":"Database name (URL parameter)"}],"body_params":[{"name":"user","type":"string","required":true,"description":"Database user name"},{"name":"host","type":"string","required":true,"description":"Host to remove"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_is_allowed","description":"Check if MySQL remote host connections are allowed on this server.","method":"GET","path_template":"/servers/websites/{website}/databases/is-allowed","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.allowed":"boolean"}},{"name":"list_privileges","description":"Get privileges for a database user on a specific database. Both engines return a flat {privilege: boolean} map.","method":"GET","path_template":"/servers/websites/{website}/databases/users/{database}/{user}/privileges","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"database","type":"string","required":true,"description":"Database name (URL parameter)"},{"name":"user","type":"string","required":true,"description":"Database user name (URL parameter)"}],"body_params":[{"name":"engine","type":"string","required":false,"description":"Database engine: \"mysql\" or \"pg\" for PostgreSQL. Leave it out and it is worked out from the database name: MySQL when a MySQL database with that name exists, otherwise PostgreSQL. Send it when the website has a MySQL and a PostgreSQL database with the same name."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object (privilege name -> boolean/string)"}},{"name":"update_privileges","description":"DESTRUCTIVE - Set a single privilege for a database user. Needs confirmation in both directions: turning a privilege on widens what this user can do to the data, and turning one off can break an application that is using it right now. PostgreSQL privileges are SELECT/INSERT/UPDATE/DELETE/TRUNCATE/REFERENCES/TRIGGER (table-level) plus USAGE/CREATE (schema-level). Schema-level privileges apply to every schema of the database.","method":"PUT","path_template":"/servers/websites/{website}/databases/users/{database}/{user}/privileges","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"database","type":"string","required":true,"description":"Database name (URL parameter)"},{"name":"user","type":"string","required":true,"description":"Database user name (URL parameter)"}],"body_params":[{"name":"engine","type":"string","required":false,"description":"Database engine: \"mysql\" or \"pg\" for PostgreSQL. Leave it out and it is worked out from the database name: MySQL when a MySQL database with that name exists, otherwise PostgreSQL. Send it when the website has a MySQL and a PostgreSQL database with the same name."},{"name":"privilege","type":"string","required":true,"description":"Privilege name. For MySQL (engine=mysql), one of: ALTER, ALTER ROUTINE, CREATE, CREATE ROUTINE, CREATE TEMPORARY TABLES, CREATE VIEW, DELETE, DROP, EVENT, EXECUTE, INDEX, INSERT, LOCK TABLES, REFERENCES, SELECT, SHOW VIEW, TRIGGER, UPDATE. For PostgreSQL (engine=pg), one of: SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER, USAGE, CREATE."},{"name":"action","type":"string","required":true,"description":"Action: 'on' or 'off'"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_preset","description":"DESTRUCTIVE - Apply a privileges preset (full or readonly) to a database user. Needs confirmation because a preset rewrites every privilege this user has at once, not only the ones it adds: applying readonly to the user an application writes with will stop that application writing, immediately.","method":"POST","path_template":"/servers/websites/{website}/databases/users/{database}/{user}/privileges/preset","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"database","type":"string","required":true,"description":"Database name (URL parameter)"},{"name":"user","type":"string","required":true,"description":"Database user name (URL parameter)"}],"body_params":[{"name":"engine","type":"string","required":false,"description":"Database engine: \"mysql\" or \"pg\" for PostgreSQL. Leave it out and it is worked out from the database name: MySQL when a MySQL database with that name exists, otherwise PostgreSQL. Send it when the website has a MySQL and a PostgreSQL database with the same name."},{"name":"preset","type":"string","required":true,"description":"Preset name: 'full' or 'readonly'. ON POSTGRESQL, \"full\" IS NOT THE LAST CALL. It restores write access and it also re-grants CREATE, which is the privilege the row-level-security guidance says to remove, so a user left on bare \"full\" can create and therefore OWN tables and switch forced row-level security off on them. Follow it immediately with PUT /servers/websites/{website}/databases/users/{database}/{user}/privileges sending {\"engine\": \"pg\", \"privilege\": \"CREATE\", \"action\": \"off\"}. That follow-up reaches every schema in the database. \"readonly\" is the only other preset and it would leave the application unable to write."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"update_password","description":"DESTRUCTIVE - Set a NEW random password for a database user and return it. This does NOT reveal the user's current password - it replaces it, so anything still connecting with the old password stops working until you update it. On a live database owner that means your site's own configuration file, so plan for that before calling this. When engine is omitted the database name is looked up on MySQL first, then PostgreSQL - but because a rotation cannot be undone, a name that exists only as a PostgreSQL database is refused with 422 and nothing changes: send engine=pg to rotate a PostgreSQL user's password.","method":"PUT","path_template":"/servers/websites/{website}/databases/users/{database}/{user}/password","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"database","type":"string","required":true,"description":"Database name (URL parameter)"},{"name":"user","type":"string","required":true,"description":"Database user name (URL parameter)"}],"body_params":[{"name":"engine","type":"string","required":false,"description":"Database engine: \"mysql\" or \"pg\" for PostgreSQL. Leave it out and it is worked out from the database name: MySQL when a MySQL database with that name exists, otherwise PostgreSQL. Send it when the website has a MySQL and a PostgreSQL database with the same name."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data.password":"string - the NEW password. Both engines","data.database":"string - the full prefixed database name. Both engines","data.user":"string - the full prefixed user name. Both engines","data.host":"string - both engines, but it means different things. On PostgreSQL it is always \"localhost\", because PostgreSQL has no per-host user grants. On MySQL it is the user's allowed hosts, comma-separated, so it can be a list","data.engine":"string - \"pg\" or \"mysql\", matching the engine you sent"}}]},{"name":"websites_ddos","scope":"websites:ddos","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"get_status","description":"Get DDoS protection status and settings.","method":"GET","path_template":"/servers/websites/ddos/status/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":{"automode":"boolean","enable":"boolean","level":"integer (1=invisible, 2=max - the level previously named captcha)","internal_flood_protection":"boolean"}}},{"name":"get_attacks_statistics","description":"Get DDoS attack statistics over time, including request breakdowns by clean vs attack traffic.","method":"GET","path_template":"/servers/websites/ddos/attacks-statistics/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"timeframe","type":"string|integer","required":false,"description":"Time period: 'today', 'this_month', or integer 1-30 for number of days (defaults to 'today')"},{"name":"tz","type":"string","required":false,"description":"Timezone for bucketing (e.g., 'America/New_York'). Defaults to server cluster timezone"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":{"total":"object","total.requests":"integer","total.clean":"integer","total.attacks":"integer","timestamps":"object","timestamps.requests":"object","timestamps.clean":"object","timestamps.attacks":"object","attacks":"object","attacks.ips":"object","attacks.countries":"object","countries":"object"}}},{"name":"get_realtime_attacks","description":"Get realtime DDoS challenge request data including top attacking IPs and countries.","method":"GET","path_template":"/servers/websites/ddos/realtime-attacks/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":{"total":"integer","requests":"object","requests.ips":"object","requests.countries":"object","countries":"object"}}},{"name":"get_levels","description":"Get available DDoS challenge levels.","method":"GET","path_template":"/servers/websites/ddos/levels/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":[{"id":"string","name":"string"}]}},{"name":"list_settings","description":"Get DDoS protection settings.","method":"GET","path_template":"/servers/websites/{website}/ddos/settings","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":{"automode":"boolean","enable":"boolean","level":"integer (1=invisible, 2=max - the level previously named captcha)","internal_flood_protection":"boolean","internal_flood_protection_mode":"string (auto or manual)","internal_flood_protection_session_cap":"integer (requests per second per visitor session, 0 means no rate cap)","internal_flood_protection_concurrency_cap":"integer (simultaneous requests per address)","internal_flood_protection_watch_only":"boolean (limits are measured but not enforced yet)","flood_caps":"object with the effective limits the site is using right now (session_cap, session_cap_source of learned/default/manual, concurrency_cap, mode, watch_only). Present only when the site is on this release","bypass_known_bots":"boolean","whitelist_timeout_minutes":"integer"}}},{"name":"update_settings","description":"Update DDoS protection settings. Full update: this endpoint replaces the whole settings set rather than patching it. Always required: protection_level, internal_flood_protection, bypass_known_bots, whitelist_timeout_minutes. When internal_flood_protection is true, internal_flood_protection_mode is required (auto or manual). In manual mode, internal_flood_protection_session_cap and internal_flood_protection_concurrency_cap are also required.","method":"PUT","path_template":"/servers/websites/{website}/ddos/settings","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"protection_level","type":"string","required":true,"description":"Protection level: automatic, invisible, max (captcha is accepted as the legacy alias for max)"},{"name":"internal_flood_protection","type":"boolean","required":true,"description":"Enable internal flood protection (the flood fields below apply only when this is true)"},{"name":"internal_flood_protection_mode","type":"string","required":false,"description":"Flood cap mode: auto (a learned or standard limit) or manual (your own numbers). Required when internal_flood_protection is true"},{"name":"internal_flood_protection_session_cap","type":"integer","required":false,"description":"Requests per second from one visitor session, 0-1000 (0 means no per-session rate cap, the per-address concurrency cap still applies). Required when internal_flood_protection is true and mode is manual"},{"name":"internal_flood_protection_concurrency_cap","type":"integer","required":false,"description":"Simultaneous requests from one address, 200-10000. Required when internal_flood_protection is true and mode is manual"},{"name":"bypass_known_bots","type":"boolean","required":true,"description":"Let known service IP ranges (search engines, CDNs, payment providers) bypass DDoS challenges"},{"name":"whitelist_timeout_minutes","type":"integer","required":true,"description":"How long verified visitors stay trusted, in minutes (1-1440)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_nojs_page","description":"Get the no-JavaScript page HTML (shown to visitors whose browser has JavaScript turned off).","method":"GET","path_template":"/servers/websites/{website}/ddos/nojs-page","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.code":"string (HTML content)","data.is_default":"boolean"}},{"name":"update_nojs_page","description":"Set custom no-JavaScript page HTML.","method":"PUT","path_template":"/servers/websites/{website}/ddos/nojs-page","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"code","type":"string","required":true,"description":"HTML content for the no-JavaScript page"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_nojs_page","description":"DESTRUCTIVE - Restore the no-JavaScript page to default.","method":"DELETE","path_template":"/servers/websites/{website}/ddos/nojs-page","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_fallback_page","description":"Get the security check page HTML (shown briefly in rare cases while a visitor's connection is checked).","method":"GET","path_template":"/servers/websites/{website}/ddos/fallback-page","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.code":"string (HTML content)","data.is_default":"boolean"}},{"name":"update_fallback_page","description":"Set custom security check page HTML.","method":"PUT","path_template":"/servers/websites/{website}/ddos/fallback-page","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"code","type":"string","required":true,"description":"HTML content for the security check page"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_fallback_page","description":"DESTRUCTIVE - Restore the security check page to default.","method":"DELETE","path_template":"/servers/websites/{website}/ddos/fallback-page","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_block_on_server","description":"Block an IP on the server firewall (permanent server-level block, independent of the per-site DDoS protection).","method":"POST","path_template":"/servers/websites/{website}/ddos/block-on-server","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"ip","type":"string","required":true,"description":"IP address to block on server"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"update_mode","description":"Set DDoS protection mode (off, basic, balanced, strict). Sending mode off always applies the full off preset and removes the protection rules. The sync_rules_only flag is ignored when the mode is off.","method":"PUT","path_template":"/servers/websites/{website}/ddos/mode","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"mode","type":"string","required":true,"description":"DDoS mode: off, basic, balanced, strict"},{"name":"sync_rules_only","type":"boolean","required":false,"description":"Only sync rules without changing mode settings"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}}]},{"name":"websites_devtools","scope":"websites:devtools","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"create_search_replace","description":"DESTRUCTIVE - Search and replace in the website's files and/or its databases. The database side covers every database of the website on both engines (MySQL and PostgreSQL) unless \"engine\" limits it to one.","method":"POST","path_template":"/servers/websites/{website}/dev-tools/search-replace","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"search","type":"string","required":true,"description":"Search string"},{"name":"replace","type":"string","required":true,"description":"The text to put in its place. Up to 255 characters. It must be different from \"search\"."},{"name":"destination","type":"string","required":true,"description":"Where to replace: \"files\", \"databases\", or \"both\"."},{"name":"case_sensitive","type":"boolean","required":true,"description":"Set to true to match upper and lower case exactly, false to ignore case. There is no default, so send it either way."},{"name":"regex","type":"boolean","required":true,"description":"Set to true to read \"search\" as a regular expression, false to treat it as plain text. There is no default, so send it either way."},{"name":"engine","type":"string","required":false,"description":"Limit the database side to one engine: \"mysql\" or \"pg\" for PostgreSQL. Leave it out to replace in every database of the website on both engines."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","data.files_modified":"integer - files changed (0 on a database-only run)","data.db_rows_modified":"integer - database rows changed (0 on a files-only run)","data.engines_searched":"array of string - the database engines the run covered (mysql, pg); present on an account-wide database run","data.warnings":"array of string - present only when the run could not cover something: an engine that was skipped, or tables that were only partly updated. Read it before you tell anyone the replacement is complete.","data.pg_tables_partial":"array of string - present only when PostgreSQL tables under row-level security were only partly updated (schema.table names)"}},{"name":"create_search_in_files","description":"Search for a piece of text inside the website's files and get back the files and line numbers that contain it. To search inside a database instead, use the search-in-database endpoint.","method":"POST","path_template":"/servers/websites/{website}/dev-tools/search-in-files","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"pattern","type":"string","required":true,"description":"The text to look for. Up to 255 characters."},{"name":"directory","type":"string","required":false,"description":"Folder to search in, relative to the website root. Leave it out to search the whole site."},{"name":"file_types","type":"string","required":false,"description":"Limit the search to certain file endings, for example \"php,js,css\". Leave it out to search every file."},{"name":"case_sensitive","type":"boolean","required":true,"description":"Set to true to match upper and lower case exactly, false to ignore case. There is no default, so send it either way."},{"name":"regex","type":"boolean","required":true,"description":"Set to true to read \"pattern\" as a regular expression, false to treat it as plain text. There is no default, so send it either way."},{"name":"recursive","type":"boolean","required":true,"description":"Set to true to search inside sub-folders as well, false to search only the folder itself. There is no default, so send it either way."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - search results (matches array with file paths/line numbers or table/column locations)"}},{"name":"create_search_in_database","description":"Search the row content of one of the website's databases for a piece of text, and get back the matching rows with a snippet of where each match sits. Works on MySQL and PostgreSQL. You do not need to say which engine the database uses - it is worked out from the database name. If you have a MySQL and a PostgreSQL database with the SAME short name, that guess picks the MySQL one, so send \"engine\" yourself to reach the PostgreSQL database. A PostgreSQL search covers every one of that database's schemas and returns schema-qualified table names. Very large searches are capped, and the response says so when that happens.","method":"POST","path_template":"/servers/websites/{website}/dev-tools/search-in-database","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"database","type":"string","required":true,"description":"The database to search, for example \"myuser_shop\"."},{"name":"pattern","type":"string","required":true,"description":"The text to look for."},{"name":"case_sensitive","type":"boolean","required":false,"description":"Set to true to match upper and lower case exactly. Defaults to false."},{"name":"engine","type":"string","required":false,"description":"Which engine the database belongs to, \"mysql\" or \"pg\". Leave it out and it is worked out from the database name. Send it only when you have a MySQL and a PostgreSQL database with the SAME name, because then the name alone cannot say which one you mean and MySQL is picked."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - search results. A match identifies the TABLE and COLUMN the value was found in, and on PostgreSQL the table name is schema-qualified. A database search does not return file paths or line numbers"}},{"name":"create_find_files","description":"Find files and folders by name. This matches the NAME only. To search inside file contents, use the search-in-files endpoint.","method":"POST","path_template":"/servers/websites/{website}/dev-tools/find-files","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"pattern","type":"string","required":true,"description":"The name to look for, for example \"*.log\". Up to 255 characters."},{"name":"directory","type":"string","required":false,"description":"Folder to search in, relative to the website root. Leave it out to search the whole site."},{"name":"type","type":"string","required":false,"description":"What to return: \"file\", \"directory\", or \"all\". Defaults to \"all\"."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - matching file paths array"}},{"name":"create_send_request","description":"Send an HTTP request from the server.","method":"POST","path_template":"/servers/websites/{website}/dev-tools/send-request","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"url","type":"string","required":true,"description":"The full address to request, including http:// or https://. Up to 2048 characters."},{"name":"method","type":"string","required":true,"description":"One of GET, POST, PUT, DELETE, PATCH, HEAD or OPTIONS. There is no default, so send it."},{"name":"headers","type":"string","required":false,"description":"Extra request headers, one per line, each written as \"Name: value\". Up to 5000 characters."},{"name":"body","type":"string","required":false,"description":"The request body to send. Up to 10000 characters."},{"name":"timeout","type":"integer","required":false,"description":"How long to wait for an answer, in seconds, between 1 and 300."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - HTTP response details (status_code, headers, body)"}},{"name":"list_whois","description":"Perform a WHOIS lookup.","method":"GET","path_template":"/servers/websites/{website}/dev-tools/whois","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"domain","type":"string","required":true,"description":"Domain to look up"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - WHOIS lookup results (registrar, creation/expiry dates, nameservers, registrant info)","data.found":"boolean (CONDITIONAL - present and false ONLY when the domain has no WHOIS record at all, which usually means it is not registered. A normal lookup omits this key and carries the record fields instead)","data.note":"string (CONDITIONAL - present with found=false, a plain-English explanation of the empty answer)"}},{"name":"create_ping","description":"Ping a host from the server.","method":"POST","path_template":"/servers/websites/{website}/dev-tools/ping","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"host","type":"string","required":true,"description":"A domain name or an IPv4 address to ping."},{"name":"count","type":"integer","required":false,"description":"How many pings to send, between 1 and 20."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - ping results (packets sent/received, latency, packet loss)"}},{"name":"get_dns_lookup","description":"Perform a DNS lookup.","method":"GET","path_template":"/servers/websites/{website}/dev-tools/dns-lookup","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"domain","type":"string","required":true,"description":"Domain to look up"},{"name":"type","type":"string","required":true,"description":"The record type to look up: A, AAAA, CNAME, MX, TXT, NS or SOA. There is no default, so send it."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - DNS lookup results (A, AAAA, CNAME, MX, NS, TXT, SOA records)","data.records":"array - the records found. An EMPTY array is a successful answer, not an error: it means the lookup worked and the domain publishes no record of that type. A domain that does not exist at all also answers this way rather than failing.","data.note":"string (CONDITIONAL - present when records is empty, saying in plain words which of the two empty cases this is: no records of that type, or the domain does not exist in DNS at all)"}},{"name":"create_fix_website","description":"Fix common website issues.","method":"POST","path_template":"/servers/websites/{website}/dev-tools/fix-website","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object"}},{"name":"create_clear_wordpress","description":"DESTRUCTIVE - Delete WordPress content in bulk. You choose which kinds of content go, and the deletion cannot be undone unless you ask for a backup first.","method":"POST","path_template":"/servers/websites/{website}/dev-tools/clear-wordpress","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"types","type":"array","required":true,"description":"Which kinds of content to delete, at least one. Allowed values: \"posts\", \"pages\", \"comments\", \"categories\", \"tags\", \"users\", \"attachments\", \"products\", \"products_categories\", \"products_tags\", \"products_attributes\", \"orders\"."},{"name":"admin_user_id","type":"integer","required":false,"description":"The WordPress user to keep when \"users\" is in the list. Required in that case, ignored otherwise."},{"name":"remove_unattached_only","type":"boolean","required":false,"description":"When deleting attachments, set this to true to remove only the media files that no post or page uses."},{"name":"backup","type":"boolean","required":false,"description":"Set to true to take a backup before anything is deleted."},{"name":"backup_name","type":"string","required":false,"description":"A name for that backup, up to 100 characters."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object"}},{"name":"list_wp_admins","description":"Get WordPress admin users list.","method":"GET","path_template":"/servers/websites/{website}/dev-tools/wp-admins","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of admin users"}}]},{"name":"websites_domains","scope":"websites:domains","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"get_ns_details","description":"Get nameserver details for a website domain.","method":"GET","path_template":"/servers/websites/domains/ns-details/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.nameservers":"array","data.domain":"string"}},{"name":"get_dns_health","description":"Diagnose DNS health by comparing the panel's dns_approved flag against the domain's actual public NS records and the panel's canonical NS. Returns one of six verdicts: healthy, panel_state_stale, pending_panel_catchup, registrar_not_pointed_at_us, no_delegation, indeterminate. It also resolves the domain's A record and compares it to the website's server IP, returning resolved_ip, resolves_to_server_ip and reachability_checked so a caller can tell whether the site already reaches this server despite a third-party, changed or absent nameserver delegation - a true resolves_to_server_ip means the site is reachable here, so a nameserver change is NOT the fix. For a pointer domain, every nameserver-state verdict is reported as indeterminate because a pointer is validated by its A/CNAME record at the server IP, not by nameserver delegation. When the public nameservers belong to a known CDN/proxy (Cloudflare, Sucuri, etc.), a public_ns_provider field names it, indicating the delegation is likely a deliberate CDN setup rather than a misconfiguration to fix with a nameserver change. Two fields are CONDITIONAL (optional keys - absent, not null, when their condition does not hold): off_server_ips is present only in the mixed-records case (this server's IP plus at least one stray off-server A record), and public_ns_provider is present only when a known CDN/proxy is detected.","method":"GET","path_template":"/servers/websites/domains/dns-health/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"domain","type":"string","required":false,"description":"Specific domain to probe - must be either the website main domain or one of its active pointers. Omit to probe the website main domain."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.domain":"string","data.probed_target":"string","data.panel_dns_approved":"boolean","data.public_resolution_works":"boolean","data.public_nameservers":"array","data.our_nameservers":"array","data.ns_matches_ours":"boolean","data.resolved_ip":"string|null","data.resolves_to_server_ip":"boolean","data.reachability_checked":"boolean","data.a_records_empty":"boolean - disambiguates the reachability_checked=true with resolves_to_server_ip=false states: true means the domain publishes NO A and NO AAAA record at all (it resolves nowhere), false means it resolved somewhere else and resolved_ip carries where, or that it resolves over IPv6 only (see ipv6_only)","data.ipv6_only":"boolean - true means the domain publishes no A (IPv4) record but does publish an AAAA (IPv6) record, so it resolves but almost no visitor can reach it. The fix is to add an A record pointing at the website's server","data.probe_error":"string|null","data.a_probe_error":"string|null","data.verdict":"string","data.off_server_ips":"array (CONDITIONAL - present only in the mixed-records case: this server's IP plus at least one stray off-server A record)","data.public_ns_provider":"string (CONDITIONAL - present only when the public nameservers belong to a known CDN/proxy)"}},{"name":"get_redirects","description":"Get list of domain redirects.","method":"GET","path_template":"/servers/websites/domains/redirects/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"string","data[].source":"string","data[].dest":"string","data[].status_code":"integer","meta":"object"}},{"name":"create_redirects","description":"Create a domain redirect.","method":"POST","path_template":"/servers/websites/domains/redirects/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"source","type":"string","required":true,"description":"Source path. Must start with a forward slash (/)."},{"name":"dest","type":"string","required":true,"description":"Destination URL or path. Must start with http://, https://, or /."},{"name":"status_code","type":"integer","required":true,"description":"Redirect status code. One of: 301, 302."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object","data.id":"string","data.source":"string","data.dest":"string","data.status_code":"integer"}},{"name":"delete_redirects","description":"DESTRUCTIVE - Delete domain redirects.","method":"DELETE","path_template":"/servers/websites/domains/redirects/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"ids","type":"array","required":true,"description":"Array of redirect IDs to delete. Maximum 500 per call, because each one is applied on the server individually."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_subdomains_wildcard","description":"Get subdomains wildcard setting.","method":"GET","path_template":"/servers/websites/domains/subdomains-wildcard/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.enable":"boolean","data.directory":"string|null"}},{"name":"create_subdomains_wildcard","description":"Set subdomains wildcard setting.","method":"POST","path_template":"/servers/websites/domains/subdomains-wildcard/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"enable","type":"boolean","required":true,"description":"Enable or disable the subdomains wildcard."},{"name":"directory","type":"string","required":false,"description":"Optional folder the wildcard subdomains serve from. It is counted from inside the website's own main folder, not from the account home, so \"blog\" means the site's blog folder. The folder must already exist on the server: if it does not, the subdomains keep serving the main folder and no error is reported. That is also why \"public_html\" is usually the wrong value, since the main folder already is public_html and the resulting public_html/public_html rarely exists. Allowed characters are letters, numbers, and _ - . / - the value cannot contain a \"..\" path segment, and cannot start with a slash except for the single \"/\" described next. Leave it out, or send an empty value or a single \"/\", to serve from the main folder itself (the panel's folder picker calls this Home). On servers that use the .htaccess manager the folder is not applied and the subdomains always serve the main folder, though a value you send is still checked for a valid format."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_records","description":"Get DNS records for a website domain.","method":"GET","path_template":"/servers/websites/domains/records/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"string","data[].name":"string","data[].type":"string","data[].value":"string","data[].ttl":"integer","meta":"object"}},{"name":"create_records","description":"Create a DNS record.","method":"POST","path_template":"/servers/websites/domains/records/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"type","type":"string","required":true,"description":"Record type. One of: A, AAAA, CNAME, MX, TXT, SRV."},{"name":"name","type":"string","required":true,"description":"Record name (the subdomain, or @ for the root domain)."},{"name":"ttl","type":"integer","required":true,"description":"Time to live in seconds (1 to 86400)."},{"name":"a_value","type":"string","required":false,"description":"IPv4 address. Required when type is A."},{"name":"aaaa_value","type":"string","required":false,"description":"IPv6 address. Required when type is AAAA."},{"name":"cname_value","type":"string","required":false,"description":"Target hostname. Required when type is CNAME."},{"name":"txt_value","type":"string","required":false,"description":"Text value (up to 8192 characters). Required when type is TXT."},{"name":"mx_value","type":"string","required":false,"description":"Mail server hostname. Required when type is MX and absolute_toggle is false."},{"name":"mx_priority","type":"integer","required":false,"description":"Mail server priority (0 or higher). Required when type is MX and absolute_toggle is false."},{"name":"srv_priority","type":"integer","required":false,"description":"SRV priority. Required when type is SRV and absolute_toggle is false."},{"name":"srv_weight","type":"integer","required":false,"description":"SRV weight. Required when type is SRV and absolute_toggle is false."},{"name":"srv_port","type":"integer","required":false,"description":"SRV port. Required when type is SRV and absolute_toggle is false."},{"name":"srv_target","type":"string","required":false,"description":"SRV target hostname. Required when type is SRV and absolute_toggle is false."},{"name":"absolute_toggle","type":"boolean","required":false,"description":"For MX and SRV only. When true, send the full record value in absolute_value instead of the structured fields."},{"name":"absolute_value","type":"string","required":false,"description":"The full record value as a single string. Required when type is MX or SRV and absolute_toggle is true."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object","data.id":"string","data.name":"string","data.type":"string","data.value":"string","data.ttl":"integer"}},{"name":"update_records","description":"Update a DNS record.","method":"PUT","path_template":"/servers/websites/domains/records/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"id","type":"string","required":true,"description":"Identifier of the record to update. Use the base64-encoded name|type|value value returned as id in the records list."},{"name":"type","type":"string","required":true,"description":"Record type. One of: A, AAAA, CNAME, MX, TXT, SRV."},{"name":"name","type":"string","required":true,"description":"Record name (the subdomain, or @ for the root domain)."},{"name":"ttl","type":"integer","required":true,"description":"Time to live in seconds (1 to 86400)."},{"name":"a_value","type":"string","required":false,"description":"IPv4 address. Required when type is A."},{"name":"aaaa_value","type":"string","required":false,"description":"IPv6 address. Required when type is AAAA."},{"name":"cname_value","type":"string","required":false,"description":"Target hostname. Required when type is CNAME."},{"name":"txt_value","type":"string","required":false,"description":"Text value (up to 8192 characters). Required when type is TXT."},{"name":"mx_value","type":"string","required":false,"description":"Mail server hostname. Required when type is MX and absolute_toggle is false."},{"name":"mx_priority","type":"integer","required":false,"description":"Mail server priority (0 or higher). Required when type is MX and absolute_toggle is false."},{"name":"srv_priority","type":"integer","required":false,"description":"SRV priority. Required when type is SRV and absolute_toggle is false."},{"name":"srv_weight","type":"integer","required":false,"description":"SRV weight. Required when type is SRV and absolute_toggle is false."},{"name":"srv_port","type":"integer","required":false,"description":"SRV port. Required when type is SRV and absolute_toggle is false."},{"name":"srv_target","type":"string","required":false,"description":"SRV target hostname. Required when type is SRV and absolute_toggle is false."},{"name":"absolute_toggle","type":"boolean","required":false,"description":"For MX and SRV only. When true, send the full record value in absolute_value instead of the structured fields."},{"name":"absolute_value","type":"string","required":false,"description":"The full record value as a single string. Required when type is MX or SRV and absolute_toggle is true."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object","data.id":"string","data.name":"string","data.type":"string","data.value":"string","data.ttl":"integer"}},{"name":"delete_records","description":"DESTRUCTIVE - Delete DNS records.","method":"DELETE","path_template":"/servers/websites/domains/records/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"ids","type":"array","required":true,"description":"Array of record IDs to delete. Each id is the base64-encoded name|type|value value returned as id in the records list. Maximum 500 per call, because each one is applied on the server individually."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_default_records","description":"Get default DNS records for a website.","method":"GET","path_template":"/servers/websites/domains/default-records/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].name":"string","data[].type":"string","data[].value":"string","data[].ttl":"integer"}},{"name":"create_default_records","description":"DESTRUCTIVE - Restore default DNS records.","method":"POST","path_template":"/servers/websites/domains/default-records/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_www","description":"Get the www redirect and allow_http settings for a website.","method":"GET","path_template":"/servers/websites/{website}/domains/www","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.www":"string","data.allow_http":"boolean|string"}},{"name":"list_pointers","description":"Get a paginated list of domain pointers for a website.","method":"GET","path_template":"/servers/websites/{website}/domains/pointers","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page (max 200)"},{"name":"search","type":"string","required":false,"description":"Search term"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","meta.current_page":"integer","meta.per_page":"integer","meta.total":"integer"}},{"name":"create_pointers","description":"Create a new domain pointer.","method":"POST","path_template":"/servers/websites/{website}/domains/pointers","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"domain","type":"string","required":true,"description":"Domain to point"},{"name":"directory","type":"string","required":false,"description":"Directory path for the pointer, relative to the website root. Defaults to the site root (/) when omitted or empty"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data.id":"integer","data.domain":"string"}},{"name":"update_pointers","description":"Update a domain pointer directory.","method":"PUT","path_template":"/servers/websites/{website}/domains/pointers/{pointer}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"pointer","type":"integer","required":true,"description":"Pointer ID (URL parameter)"}],"body_params":[{"name":"directory","type":"string","required":false,"description":"New directory path, relative to the website root. Defaults to the site root (/) when omitted or empty"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_pointers","description":"DESTRUCTIVE - Delete one or more domain pointers.","method":"DELETE","path_template":"/servers/websites/{website}/domains/pointers","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"ids","type":"array","required":true,"description":"Array of pointer IDs to delete. Maximum 500 per call, because each one is applied on the server individually."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"list_websites","description":"Get a list of websites available for DNS record copying.","method":"GET","path_template":"/servers/websites/{website}/domains/copy-records/websites","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of {id, label}"}},{"name":"create_copy_records","description":"Copy DNS records from another website.","method":"POST","path_template":"/servers/websites/{website}/domains/copy-records","path_params":[{"name":"website","type":"integer","required":true,"description":"Target website ID (URL parameter)"}],"body_params":[{"name":"source_id","type":"integer","required":true,"description":"Source website ID to copy records from"},{"name":"records","type":"array","required":true,"description":"Array of records to copy, each with name, type, value, ttl"},{"name":"overwrite","type":"boolean","required":true,"description":"Whether to overwrite existing records with same name+type"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.copied":"integer","data.skipped":"integer"}},{"name":"get_dnssec","description":"Get DNSSEC status for a website domain.","method":"GET","path_template":"/servers/websites/{website}/domains/dnssec","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object (DNSSEC status)"}},{"name":"enable_dnssec","description":"Enable DNSSEC for a website domain.","method":"POST","path_template":"/servers/websites/{website}/domains/dnssec/enable","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"disable_dnssec","description":"DESTRUCTIVE - Disable DNSSEC for a website domain.","method":"POST","path_template":"/servers/websites/{website}/domains/dnssec/disable","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_regenerate_keys","description":"DESTRUCTIVE - Regenerate DNSSEC keys for a website domain. This replaces the signing keys, so the DS record at the domain registrar must be updated to the new key or DNSSEC validation fails and the domain can stop resolving.","method":"POST","path_template":"/servers/websites/{website}/domains/dnssec/regenerate-keys","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}}]},{"name":"websites_files","scope":"websites:files","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"create_deploy","description":"CONDITIONALLY DESTRUCTIVE - Deploy a set of files to the website in one call. Send the whole file set in the request body: the panel packs it into a single archive, transfers it once and expands it on the server, so the files land together instead of one at a time. Any file the website can serve or run goes through here, PHP included - this is not limited to plain HTML pages. Paths are relative to the folder THIS DEPLOY TARGETS, which is the website's main folder when directory is left out and the named folder when it is set. So with directory left out, \"index.html\" is the site root. With directory set to \"public\", \"index.html\" lands at public/index.html and you do NOT repeat the folder in the path - sending \"public/index.html\" alongside directory \"public\" writes to public/public/index.html and still answers 200. \"assets/app.css\" is a file inside an assets folder under the target. A path cannot start with a slash, contain a \"..\" or \".\" step, or have a space at the start or end of a name. Send text in content, or base64 in content_base64 for images and other binary files, but not both for the same file. An empty file is allowed - send content as an empty string. Limits per call: 200 files, 1 MB per file, 8 MB in total, and 16 MB for the whole request body, which answers 413 when exceeded. A request that arrives chunked - a Transfer-Encoding body with no Content-Length - answers 411, so build the whole JSON body first and send it in one piece. An ordinary empty POST is not this case and answers 422. The 411 carries the same validation-failure body shape as a 422, so read the status rather than the body to tell them apart. A file already on the site at the same path is only replaced when overwrite is true, otherwise the call is refused and names the paths that would have been replaced. That check is made just before the files are written, and it protects you from other deploys, but not from a file created in between by FTP, SSH, a cron job or the site itself. A newly created website has an EMPTY folder unless you asked for WordPress when you created it, so the first deploy to a new site replaces nothing and needs no overwrite flag. Set overwrite only when you genuinely mean to replace files that are already there, which is also when this call counts as destructive. This call MERGES rather than mirrors: files already on the site that you do not send are left alone and keep being served, so removing a page means deleting it, not just leaving it out of the next deploy. The transfer is all or nothing, but the set is expanded over the live folder, so a failure during expansion can leave part of it written. Expanding the set normalises the permissions of the files and folders it CREATES. A file or folder that was already there and that you did not send keeps its own permissions, including one you deliberately made private. The exception is a file you DO send with overwrite true: it is replaced, so it lands with standard permissions rather than whatever the old copy had. If that matters for a specific file, read its permissions with GET /servers/websites/{website}/files/info before and after. If the call cannot proceed the answer is 409 with a retryable flag and a website_status. Act on retryable, not on the message and not on website_status. retryable true means the same call is worth repeating, and it covers five situations of which only one is the site still being set up: the site is still being created or moved, another deploy is already running on the same website, the website has an open issue, the website has another job still running, or the server the website runs on is starting up or restarting. Only the first happens on a site that is not active yet. The other four happen on a site that IS active, so those answers carry website_status active beside a message about waiting. Every 409 from this endpoint carries website_status, so it is always there to read, and reading it is still not how you decide what to do. retryable false means repeating the call will never help: the site is paused, or it failed to be created or moved. A retryable 409 carries retry_after, the number of seconds to wait before calling again, currently 10. This endpoint accepts 12 calls per minute, so waiting retry_after between attempts uses about half the allowance and leaves room for the call that finally succeeds. Polling faster spends the whole allowance in well under a minute and the next call is refused for rate limiting instead, which looks like a different failure. A website that failed to be created or failed to move is not retryable at all, and the refusal points you at GET /servers/websites/{websiteId}/tasks/status, which carries the reason. There is no separate readiness endpoint: the response returns the site URL, and the way to confirm the deploy is serving is to request that URL until it answers. That URL is the site address plus the folder you deployed into, which is the live address as long as that folder sits inside the folder the website serves. If the site serves from a sub-folder of its own, or you want proof that YOUR files are the ones being served rather than whatever was there before, deploy a small file with known contents and request that file instead.","method":"POST","path_template":"/servers/websites/{website}/deploy","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"files","type":"array","required":true,"description":"The files to deploy. Each item is an object with: path (string, required, relative to the folder this deploy targets, which is the directory param when set and the website main folder when it is not), content (string, the file text), content_base64 (string, the file bytes as base64 for binary files). Give content or content_base64 for each file, not both."},{"name":"directory","type":"string","required":false,"description":"Optional folder inside the website to deploy into, for example public. Relative, no leading slash and no \"..\" step. Leave it out to deploy to the website main folder."},{"name":"overwrite","type":"boolean","required":false,"description":"Replace files that already exist at the same paths. Defaults to false, which refuses the deploy if it would replace anything and tells you which names collided. This is the flag named by destructive_when, and it is read for truthiness rather than for the exact JSON literal true - 1 and \"1\" also turn it on. A confirmation gate that tests only for true will therefore pass a payload carrying 1 straight through to an irreversible replacement, so test it the same way the endpoint does."}],"requires_confirmation":false,"prerequisites":[],"requires_confirmation_when":{"overwrite":true},"response_structure":{"success":"boolean","message":"string","data":"object with url, files_deployed, bytes_written and directory","code":"string, on failure only - the stable reason identifier described under Making API Requests"}},{"name":"list_files","description":"List files in a directory. Supports pagination.","method":"GET","path_template":"/servers/websites/{website}/files","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"path","type":"string","required":false,"description":"Directory path to list (empty for root)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of file objects (name, type, relative_path, size, last_modified, permissions)","meta":"pagination metadata"}},{"name":"create_files","description":"CONDITIONALLY DESTRUCTIVE - Create a file or directory, or upload files. Accepts multipart/form-data for file uploads. Note: this endpoint takes a multipart file upload (files) that cannot be sent over MCP - run the upload via the panel or a direct External API multipart call.","method":"POST","path_template":"/servers/websites/{website}/files","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"type","type":"string","required":true,"description":"Type: 'file' or 'directory'"},{"name":"name","type":"string","required":false,"description":"File or directory name. Required when creating a file or directory by name (when upload is false). Not needed when uploading."},{"name":"path","type":"string","required":false,"description":"Parent directory path"},{"name":"upload","type":"boolean","required":true,"description":"Set to true to upload files (multipart/form-data), or false to create a file or directory by name."},{"name":"code","type":"string","required":false,"description":"File content (for creating a file with content)"},{"name":"extract_after_upload","type":"boolean","required":false,"description":"Extract archives after upload"},{"name":"clean_archive","type":"boolean","required":false,"description":"Delete archive after extraction"},{"name":"allow_overwrite","type":"boolean","required":false,"description":"Replace the existing file at the same path if one exists. Default false."}],"requires_confirmation":false,"prerequisites":[],"requires_confirmation_when":{"allow_overwrite":true,"extract_after_upload":true},"response_structure":{"success":"boolean","message":"string","extraction_errors":"array of {file, message, code} objects - present only when extract_after_upload was requested and one or more archives could not be extracted (the uploads themselves succeeded). message is plain English written for a person. code is a stable identifier to branch on. It is null when the failure was unexpected, and also null when the server asked for that failure to be hidden - in that case message is 'Unknown error happened (Support Hash: ...)' and the code is withheld on purpose, so treat a null code with a support hash as 'report this', not 'retry forever'. Codes that mean the archive itself is the problem and a retry will not help: archive_damaged, archive_password_protected, archive_unsupported_compression, archive_unsafe_layout, invalid_source_extension, account_archive_in_docroot. Codes worth retrying later: insufficient_disk_space, extract_out_of_memory, archive_extract_timeout, archive_extract_interrupted, identity_not_dropped. extract_tool_unavailable means the server is missing the program for this format, so retrying will not help until an operator fixes the server. archive_extract_unconfirmed means the server could not confirm the result either way. It is sent both when the archive unpacked but the extractor reported a warning, and when the extractor may never have started, so do not assume the files are there. Look at the destination folder before deciding. The archive is kept in both cases"}},{"name":"update_files","description":"DESTRUCTIVE - Update file content.","method":"PUT","path_template":"/servers/websites/{website}/files","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"id","type":"string","required":true,"description":"File relative path"},{"name":"code","type":"string","required":false,"description":"New file content"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_files","description":"DESTRUCTIVE - Delete files.","method":"DELETE","path_template":"/servers/websites/{website}/files","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"ids","type":"array","required":true,"description":"Array of file relative paths to delete"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"list_directories","description":"Get directory tree structure.","method":"GET","path_template":"/servers/websites/{website}/files/directories","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"path","type":"string","required":false,"description":"Current path to expand"},{"name":"openedPaths","type":"array","required":false,"description":"Array of already-opened paths. Every entry must be a string - a non-string entry (including a null) is rejected with 422 rather than dropped, so a folder you asked to expand is never silently missing from the returned tree."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of directory tree nodes"}},{"name":"get_files_only","description":"Get file tree (files only, no directories).","method":"GET","path_template":"/servers/websites/{website}/files/files-only","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"path","type":"string","required":false,"description":"Current path to expand"},{"name":"openedPaths","type":"array","required":false,"description":"Array of already-opened paths. Every entry must be a string - a non-string entry (including a null) is rejected with 422 rather than dropped, so a folder you asked to expand is never silently missing from the returned tree."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of file tree nodes"}},{"name":"create_move","description":"CONDITIONALLY DESTRUCTIVE - Move files to a destination directory.","method":"POST","path_template":"/servers/websites/{website}/files/move","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"files","type":"array","required":true,"description":"Array of file objects to move, each an object with a relative_path string (for example [{\"relative_path\": \"public_html/old.txt\"}])."},{"name":"destination","type":"string","required":true,"description":"Destination directory path"},{"name":"overwrite","type":"boolean","required":false,"description":"Replace files that already exist at the destination, and merge folders (replacing only clashing files, keeping the rest). Default false (the move fails if the destination already exists)."}],"requires_confirmation":false,"prerequisites":[],"requires_confirmation_when":{"overwrite":true},"response_structure":{"success":"boolean","message":"string","code":"string, on failure only - the stable reason identifier described under Making API Requests"}},{"name":"create_copy","description":"CONDITIONALLY DESTRUCTIVE - Copy files to a destination directory.","method":"POST","path_template":"/servers/websites/{website}/files/copy","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"files","type":"array","required":true,"description":"Array of file objects to copy, each an object with a relative_path string (for example [{\"relative_path\": \"public_html/file.txt\"}])."},{"name":"destination","type":"string","required":true,"description":"Destination directory path"},{"name":"new_name","type":"string","required":false,"description":"New name for single file copy"},{"name":"overwrite","type":"boolean","required":false,"description":"Replace files that already exist at the destination, and merge folders (replacing only clashing files, keeping the rest). Default false (the copy fails if a destination folder already exists)."}],"requires_confirmation":false,"prerequisites":[],"requires_confirmation_when":{"overwrite":true},"response_structure":{"success":"boolean","message":"string"}},{"name":"create_shortcut","description":"Create symbolic links (shortcuts) for files.","method":"POST","path_template":"/servers/websites/{website}/files/shortcut","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"files","type":"array","required":true,"description":"Array of file objects to link, each with a required relative_path string (name and type optional)."},{"name":"destination","type":"string","required":false,"description":"Destination directory for shortcut"},{"name":"custom_name","type":"string","required":false,"description":"Custom name for single-file shortcut"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_rename","description":"Rename a file or directory.","method":"POST","path_template":"/servers/websites/{website}/files/rename","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"id","type":"string","required":true,"description":"File relative path"},{"name":"name","type":"string","required":true,"description":"New file/directory name"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_permissions","description":"Set file/directory permissions (chmod).","method":"POST","path_template":"/servers/websites/{website}/files/permissions","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"files","type":"array","required":true,"description":"Array of file objects, each an object with a relative_path string (for example [{\"relative_path\": \"public_html/app.php\"}])."},{"name":"mode","type":"string","required":true,"description":"Permission mode (e.g., '755', '644')"},{"name":"recursive","type":"boolean","required":false,"description":"Apply recursively to directories"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_download","description":"Download a single file. Returns binary file data (StreamedResponse).","method":"GET","path_template":"/servers/websites/{website}/files/download","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"file","type":"string","required":true,"description":"File relative path to download"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"description":"Binary file stream"}},{"name":"create_compress","description":"Compress files into an archive.","method":"POST","path_template":"/servers/websites/{website}/files/compress","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"files","type":"array","required":true,"description":"Array of file objects to compress, each an object with relative_path, name, and type strings (for example [{\"relative_path\": \"public_html/app.php\", \"name\": \"app.php\", \"type\": \"file\"}])."},{"name":"format","type":"string","required":true,"description":"Archive format: zip, tar, tar.gz, tar.bz2, tar.xz, tar.lz4, tar.zst"},{"name":"destination","type":"string","required":true,"description":"Destination directory for the archive"},{"name":"name","type":"string","required":false,"description":"Custom archive filename"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","code":"string, on failure only - the stable reason identifier described under Making API Requests"}},{"name":"create_extract","description":"DESTRUCTIVE - Extract an archive file.","method":"POST","path_template":"/servers/websites/{website}/files/extract","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"file","type":"string","required":true,"description":"Archive file relative path"},{"name":"destination","type":"string","required":true,"description":"Destination directory for extraction"},{"name":"clean_archive","type":"boolean","required":false,"description":"Delete the archive once the extraction is confirmed. An extraction that fails, or that the server cannot confirm, never reaches the delete step, so the archive is still there. The delete is a separate step after a confirmed extraction, so an error from THAT step can arrive with the archive already gone. Check whether the file is still listed rather than inferring it from the status."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","code":"string, on failure only - the stable reason identifier described under Making API Requests"}},{"name":"get_code","description":"Get file content for editing.","method":"GET","path_template":"/servers/websites/{website}/files/code","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"file","type":"string","required":true,"description":"File relative path"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.content":"string"}},{"name":"list_programs","description":"Get available editor programs for a file type.","method":"GET","path_template":"/servers/websites/{website}/files/programs","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"path","type":"string","required":true,"description":"File path to check programs for"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of available programs"}},{"name":"create_run_command","description":"Run a command on a file using a specific program.","method":"POST","path_template":"/servers/websites/{website}/files/run-command","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"program","type":"string","required":true,"description":"Program name to use"},{"name":"command","type":"string","required":true,"description":"Command to run"},{"name":"path","type":"string","required":false,"description":"Working directory path"},{"name":"file","type":"string","required":false,"description":"Target file path"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"command output"}},{"name":"create_protection","description":"Add password protection to a directory.","method":"POST","path_template":"/servers/websites/{website}/files/protection","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"directory","type":"string","required":true,"description":"Directory path to protect"},{"name":"user","type":"string","required":true,"description":"Username for protection"},{"name":"password","type":"string","required":true,"description":"Password for protection"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_protection","description":"DESTRUCTIVE - Remove password protection from a directory.","method":"DELETE","path_template":"/servers/websites/{website}/files/protection","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"directory","type":"string","required":true,"description":"Directory path to unprotect"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_info","description":"Get file information including size, permissions, and optional checksums.","method":"GET","path_template":"/servers/websites/{website}/files/info","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"file","type":"string","required":true,"description":"File relative path"},{"name":"checksums","type":"boolean","required":false,"description":"Include file checksums (MD5, SHA1, SHA256)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.name":"string","data.type":"string","data.size":"integer","data.last_modified":"integer (Unix timestamp)","data.permissions":"string","data.target":"string|null (symlink target)","data.checksums":"object|null (when checksums=true)","data.checksums.md5":"string","data.checksums.sha1":"string","data.checksums.sha256":"string","data.checksums.sha512":"string"}},{"name":"get_download_archive","description":"Download multiple files as an archive. Returns binary file data (StreamedResponse).","method":"GET","path_template":"/servers/websites/{website}/files/download-archive","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"files","type":"array","required":true,"description":"Array of file objects to include in the archive, each an object with relative_path, name, and type strings (for example [{\"relative_path\": \"public_html/app.php\", \"name\": \"app.php\", \"type\": \"file\"}])."},{"name":"format","type":"string","required":true,"description":"Archive format: zip, tar, tar.gz, tar.bz2, tar.xz, tar.lz4, tar.zst"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"description":"Binary file stream"}},{"name":"get_trash","description":"List trash entries. Supports pagination.","method":"GET","path_template":"/servers/websites/{website}/files/trash","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of trash entries (name, type, size, deleted_at, original_path)","meta":"pagination metadata"}},{"name":"get_size","description":"Get total trash size.","method":"GET","path_template":"/servers/websites/{website}/files/trash/size","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.size":"integer (size in bytes)"}},{"name":"create_trash","description":"Move files to trash.","method":"POST","path_template":"/servers/websites/{website}/files/trash","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"files","type":"array","required":true,"description":"Array of file objects to move to trash, each an object with relative_path, name, and type strings (for example [{\"relative_path\": \"public_html/old.txt\", \"name\": \"old.txt\", \"type\": \"file\"}])."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"restore","description":"Restore files from trash.","method":"POST","path_template":"/servers/websites/{website}/files/trash/restore","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"entries","type":"array","required":true,"description":"Array of trash entry IDs to restore"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_trash","description":"DESTRUCTIVE - Permanently delete files from trash.","method":"DELETE","path_template":"/servers/websites/{website}/files/trash","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"entries","type":"array","required":true,"description":"Array of trash entry IDs to permanently delete"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_empty","description":"DESTRUCTIVE - Empty the entire trash and recalculate disk quota.","method":"DELETE","path_template":"/servers/websites/{website}/files/trash/empty","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object","data.result":"object","data.result.total":"float (total quota in MB)","data.result.files":"float (files size in MB)","data.result.mysql":"float (MySQL size in MB)","data.result.postgres":"float (PostgreSQL size in MB)","data.result.mail":"float (mail size in MB)","data.result.tmp":"float (tmp size in MB)","data.result.log":"float (log size in MB)","data.result.redis":"float (Redis size in MB)","data.result.optimization_backups":"float (optimization backups size in MB)","data.result.system":"float (system size in MB)","data.result.stages":"float (staging sites size in MB)","data.result.trash":"float (trash size in MB)","data.result.largest_files":"array","data.time":"integer (Unix timestamp)"}}]},{"name":"websites_ftp","scope":"websites:ftp","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"list_details","description":"Get FTP connection details for a website.","method":"GET","path_template":"/servers/websites/{website}/ftp/details","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object"}},{"name":"get_ip_restriction","description":"Get FTP IP restriction status.","method":"GET","path_template":"/servers/websites/{website}/ftp/ip-restriction","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.ip_restriction_enabled":"boolean"}},{"name":"update_ip_restriction","description":"Enable or disable FTP IP restriction (whitelist). Requires full permission on the website.","method":"PUT","path_template":"/servers/websites/{website}/ftp/ip-restriction","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"enabled","type":"boolean","required":true,"description":"Whether IP restriction is active"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object","data.enabled":"boolean"}},{"name":"get_main_account","description":"Get the main FTP account password.","method":"GET","path_template":"/servers/websites/{website}/ftp/main-account","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.password":"string"}},{"name":"list_accounts","description":"Get FTP accounts list (excludes main account). Supports pagination.","method":"GET","path_template":"/servers/websites/{website}/ftp/accounts","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","meta":"object"}},{"name":"create_accounts","description":"Create a new FTP account.","method":"POST","path_template":"/servers/websites/{website}/ftp/accounts","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"owner_name","type":"string","required":true,"description":"Display name for the FTP account"},{"name":"access_to_all_files","type":"boolean","required":true,"description":"Whether the account can access all files"},{"name":"accessed_path","type":"string","required":false,"description":"Restricted path (required when access_to_all_files is false)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object"}},{"name":"update_accounts","description":"Update an FTP account.","method":"PUT","path_template":"/servers/websites/{website}/ftp/accounts/{account}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"account","type":"string","required":true,"description":"FTP username (URL parameter)"}],"body_params":[{"name":"owner_name","type":"string","required":true,"description":"Display name for the FTP account"},{"name":"access_to_all_files","type":"boolean","required":true,"description":"Whether the account can access all files"},{"name":"accessed_path","type":"string","required":false,"description":"Restricted path (required when access_to_all_files is false)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_accounts","description":"DESTRUCTIVE - Delete one or more FTP accounts.","method":"DELETE","path_template":"/servers/websites/{website}/ftp/accounts","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"ids","type":"array","required":true,"description":"Array of FTP usernames to delete. Maximum 500 per call, because each one is applied on the server individually."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"update_password","description":"Change FTP account password. Returns the new password.","method":"PUT","path_template":"/servers/websites/{website}/ftp/accounts/{account}/password","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"account","type":"string","required":true,"description":"FTP username (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data.password":"string"}},{"name":"list_sessions","description":"Get FTP sessions list. Requires ftp_username query parameter.","method":"GET","path_template":"/servers/websites/{website}/ftp/sessions","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"ftp_username","type":"string","required":true,"description":"FTP username to get sessions for (query parameter)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","meta":"object"}},{"name":"delete_sessions","description":"DESTRUCTIVE - Kill FTP sessions. Provide either a session PID in \"ids\", or set \"all\": true to kill all sessions.","method":"DELETE","path_template":"/servers/websites/{website}/ftp/sessions","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"ftp_username","type":"string","required":true,"description":"FTP username"},{"name":"all","type":"boolean","required":false,"description":"Set to true to kill all sessions. When true, \"ids\" is ignored."},{"name":"ids","type":"array","required":false,"description":"Array containing a single session PID to kill. Required unless \"all\" is true."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_enabled","description":"Check if FTP IP whitelist is enabled.","method":"GET","path_template":"/servers/websites/{website}/ftp/whitelist/enabled","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.enabled":"boolean"}},{"name":"get_whitelist","description":"Get FTP whitelist IPs. Requires ftp_username query parameter.","method":"GET","path_template":"/servers/websites/{website}/ftp/whitelist","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"ftp_username","type":"string","required":true,"description":"FTP username (query parameter)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","meta":"object"}},{"name":"create_whitelist","description":"Add an IP to the FTP whitelist.","method":"POST","path_template":"/servers/websites/{website}/ftp/whitelist","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"ftp_username","type":"string","required":true,"description":"FTP username"},{"name":"ip","type":"string","required":true,"description":"IP address or CIDR range"},{"name":"permanent","type":"boolean","required":true,"description":"Whether the whitelist entry is permanent"},{"name":"expire_at","type":"integer","required":false,"description":"Unix timestamp for expiry (required when permanent is false)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"update_whitelist","description":"Update an FTP whitelist entry.","method":"PUT","path_template":"/servers/websites/{website}/ftp/whitelist/{entry}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"entry","type":"string","required":true,"description":"IP address (URL parameter)"}],"body_params":[{"name":"ftp_username","type":"string","required":true,"description":"FTP username"},{"name":"permanent","type":"boolean","required":true,"description":"Whether the whitelist entry is permanent"},{"name":"expire_at","type":"integer","required":false,"description":"Unix timestamp for expiry (required when permanent is false)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_whitelist","description":"DESTRUCTIVE - Delete FTP whitelist entries. A text entry that is not a valid IP address or CIDR range is IGNORED rather than rejected, and the call still answers 200 with success true, so read the message rather than the status: it states how many removals were REQUESTED, not how many entries were actually deleted, and it names any entries it ignored. The node call returns nothing, so an entry that was not in the whitelist is counted the same as one that was. Read the whitelist back to confirm. A count of 0 means nothing was acted on. An entry that is not text is refused with 422 naming the field, so a mixed batch is rejected whole rather than partly ignored.","method":"DELETE","path_template":"/servers/websites/{website}/ftp/whitelist","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"ftp_username","type":"string","required":true,"description":"FTP username"},{"name":"ids","type":"array","required":true,"description":"Array of IP addresses to remove. Maximum 500 per call, because each one is applied on the server individually."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}}]},{"name":"websites_git","scope":"websites:git","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"get_status","description":"Get git status for a repository.","method":"GET","path_template":"/servers/websites/{website}/git/status","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"path","type":"string","required":false,"description":"Repository path relative to home directory"},{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page"},{"name":"search","type":"string","required":false,"description":"Search filter"},{"name":"category","type":"string","required":false,"description":"File category filter"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - is_repo (boolean), branch (string), staged/unstaged/untracked file arrays, and pagination info. Returns {is_repo: false} if not a git repo"}},{"name":"list_branches","description":"Get git branches for a repository.","method":"GET","path_template":"/servers/websites/{website}/git/branches","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"path","type":"string","required":false,"description":"Repository path"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of branch objects"}},{"name":"get_log","description":"Get git commit log.","method":"GET","path_template":"/servers/websites/{website}/git/log","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"path","type":"string","required":false,"description":"Repository path"},{"name":"branch","type":"string","required":false,"description":"Branch name"},{"name":"limit","type":"integer","required":false,"description":"Number of commits (default 50)"},{"name":"skip","type":"integer","required":false,"description":"Number of commits to skip (default 0)"},{"name":"search","type":"string","required":false,"description":"Search in commit messages"},{"name":"file_path","type":"string","required":false,"description":"Filter by file path"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of commit objects"}},{"name":"get_diff","description":"Get git diff for files.","method":"GET","path_template":"/servers/websites/{website}/git/diff","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"path","type":"string","required":false,"description":"Repository path"},{"name":"file_path","type":"string","required":false,"description":"File to diff"},{"name":"staged","type":"boolean","required":false,"description":"Show staged changes (default false)"},{"name":"commit","type":"string","required":false,"description":"Commit hash to diff"},{"name":"commit2","type":"string","required":false,"description":"Second commit hash for comparison"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - git diff output"}},{"name":"create_branches","description":"Create a new git branch.","method":"POST","path_template":"/servers/websites/{website}/git/branches","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"path","type":"string","required":false,"description":"Repository path"},{"name":"name","type":"string","required":true,"description":"Branch name"},{"name":"start_point","type":"string","required":false,"description":"Starting point (commit/branch)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_branches","description":"DESTRUCTIVE - Delete a git branch.","method":"DELETE","path_template":"/servers/websites/{website}/git/branches","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"path","type":"string","required":false,"description":"Repository path"},{"name":"name","type":"string","required":true,"description":"Branch name to delete"},{"name":"force","type":"boolean","required":false,"description":"Force delete"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_rename","description":"Rename a git branch.","method":"POST","path_template":"/servers/websites/{website}/git/branches/rename","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"path","type":"string","required":false,"description":"Repository path"},{"name":"name","type":"string","required":true,"description":"Current branch name"},{"name":"new_name","type":"string","required":true,"description":"New branch name"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_stage","description":"Stage files for commit.","method":"POST","path_template":"/servers/websites/{website}/git/stage","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"path","type":"string","required":false,"description":"Repository path"},{"name":"files","type":"array","required":false,"description":"Array of file paths to stage"},{"name":"all_files","type":"boolean","required":false,"description":"Stage all changed files"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_unstage","description":"Unstage files.","method":"POST","path_template":"/servers/websites/{website}/git/unstage","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"path","type":"string","required":false,"description":"Repository path"},{"name":"files","type":"array","required":false,"description":"Array of file paths to unstage"},{"name":"all_files","type":"boolean","required":false,"description":"Unstage all files"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_commit","description":"Create a git commit.","method":"POST","path_template":"/servers/websites/{website}/git/commit","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"path","type":"string","required":false,"description":"Repository path"},{"name":"message","type":"string","required":true,"description":"Commit message"},{"name":"files","type":"array","required":false,"description":"Files to include in commit"},{"name":"all_changes","type":"boolean","required":false,"description":"Include all changed files"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"checkout","description":"DESTRUCTIVE - Checkout a branch, file, or commit.","method":"POST","path_template":"/servers/websites/{website}/git/checkout","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"path","type":"string","required":false,"description":"Repository path"},{"name":"branch","type":"string","required":false,"description":"Branch name to checkout"},{"name":"file_path","type":"string","required":false,"description":"File to checkout"},{"name":"create","type":"boolean","required":false,"description":"Create new branch"},{"name":"commit","type":"string","required":false,"description":"Commit hash to checkout"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"push","description":"DESTRUCTIVE - Push commits to a remote already saved on this website. A repository address in the remote field is refused: register it with POST /servers/websites/{website}/git/remotes first, then push to the name you gave it.","method":"POST","path_template":"/servers/websites/{website}/git/push","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"path","type":"string","required":false,"description":"Repository path"},{"name":"remote","type":"string","required":false,"description":"The name of a remote already saved on this website, such as origin. Default: origin. A repository address is refused: register it first with POST /servers/websites/{website}/git/remotes, then send the name you gave it. Letters, digits, dots, underscores and hyphens, starting with a letter or a digit, up to 100 characters. A slash is allowed between name parts, as in team/origin, but a part is never empty and never . or .., so no path can be sent as a name"},{"name":"branch","type":"string","required":false,"description":"Branch to push"},{"name":"force","type":"boolean","required":false,"description":"Force push"},{"name":"set_upstream","type":"boolean","required":false,"description":"Set upstream tracking"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","code":"string (CONDITIONAL, 400 only) - the closed set of reasons a push can stop, so you branch on the code and never on the message text. The 400 body is {message, proper, code} and carries no success key. SAFE TO RETRY: push_timeout (the remote did not answer within 10 minutes - the push may or may not have reached the remote, so read the remote before retrying), push_failed (rejected credentials, an unreachable host, a non-fast-forward push, a protected branch, or a repository the remote does not have - read message for which, and note that a non-fast-forward needs a pull first, not a retry). CALLER ERROR, do not retry unchanged: remote_name_required (remote must be a saved remote NAME, not a repository address), not_a_git_repo (there is no repository at that path)"}},{"name":"pull","description":"Pull changes from remote. By default this applies the remote changes only when the website has no commits of its own. If the website does have its own commits, the pull stops, changes nothing, and returns the error code pull_diverged. To join the two histories instead, send strategy=merge or strategy=rebase - both rewrite the files of a LIVE website, and a merge can leave conflict markers in them. Read data.summary for what the pull did.","method":"POST","path_template":"/servers/websites/{website}/git/pull","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"path","type":"string","required":false,"description":"Repository path"},{"name":"remote","type":"string","required":false,"description":"The name of a remote already saved on this website, such as origin. Default: origin. A repository address is refused: register it first with POST /servers/websites/{website}/git/remotes, then send the name you gave it. Letters, digits, dots, underscores and hyphens, starting with a letter or a digit, up to 100 characters. A slash is allowed between name parts, as in team/origin, but a part is never empty and never . or .., so no path can be sent as a name"},{"name":"branch","type":"string","required":false,"description":"Branch to pull"},{"name":"rebase","type":"boolean","required":false,"description":"Deprecated, kept so older integrations keep working. Use strategy instead. When strategy is not sent, rebase=true means strategy=rebase."},{"name":"strategy","type":"string","required":false,"description":"How to join the histories: ff_only (default), merge, or rebase. ff_only applies the remote changes only when the website has no commits of its own, and otherwise stops and changes nothing. merge and rebase both rewrite the files of a LIVE website, and a merge can leave conflict markers in them. Send this field ONLY after a pull returned pull_diverged. A server old enough not to know the field yet refuses the whole request, and only a server new enough to accept it can return pull_diverged in the first place."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string - the server's own summary of what the pull did when it sent one, so a pull that MERGED remote commits into the live document root does not read as a plain success. Falls back to a generic sentence on older servers","data":"object|null","data.status":"string - \"success\"","data.summary":"string - plain-English outcome: already up to date, fast-forward, completed by MERGING (the website had its own commits), or rebase","data.head_commit":"string (CONDITIONAL - short commit id the site is on after the pull. Absent when it could not be read)","code":"string (CONDITIONAL, 400 only) - the closed set of reasons a pull can stop, so you branch on the code and never on the message text. The 400 body is {message, proper, code} and carries no success key. SAFE TO RETRY: pull_diverged (the website has its own commits, nothing was changed - retry with strategy=merge or strategy=rebase once the customer has chosen), pull_failed (credentials, an unreachable host, a protected branch or an unknown repository - read message for which). READ THE REPOSITORY FIRST, THEN RETRY: pull_timeout (the remote did not answer within 10 minutes and the pull was stopped - do not assume nothing changed, because stopping the command does not always stop the git process behind it, so read GET /servers/websites/{website}/git/status before pulling again). CALLER ERROR, do not retry unchanged: pull_bad_strategy (strategy was not one of ff_only, merge or rebase), remote_name_required (remote must be a saved remote NAME, not a repository address), not_a_git_repo (there is no repository at that path). NEEDS A PERSON, NEVER RETRY: pull_conflict (the merge stopped on conflicting lines and was rolled back), merge_in_progress (a merge or rebase from an earlier attempt is still open), pull_incomplete (the merge or rebase is still open), pull_cleanup_failed (the rollback itself failed, so the website's files may hold conflict markers right now - a retry can make it worse). For the last four, tell the customer and stop"}},{"name":"init","description":"Initialize or clone a git repository.","method":"POST","path_template":"/servers/websites/{website}/git/init","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"path","type":"string","required":false,"description":"Repository path"},{"name":"clone_url","type":"string","required":false,"description":"URL to clone from (if not provided, initializes empty repo)"},{"name":"bare","type":"boolean","required":false,"description":"Initialize as bare repository"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_stash","description":"List stashed changes.","method":"GET","path_template":"/servers/websites/{website}/git/stash","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"path","type":"string","required":false,"description":"Repository path"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of stash entries"}},{"name":"stash_push","description":"DESTRUCTIVE - Push changes to stash.","method":"POST","path_template":"/servers/websites/{website}/git/stash/push","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"path","type":"string","required":false,"description":"Repository path"},{"name":"message","type":"string","required":false,"description":"Stash message"},{"name":"include_untracked","type":"boolean","required":false,"description":"Include untracked files"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"stash_pop","description":"Pop stashed changes.","method":"POST","path_template":"/servers/websites/{website}/git/stash/pop","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"path","type":"string","required":false,"description":"Repository path"},{"name":"stash_index","type":"integer","required":false,"description":"Stash index (default 0)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_apply","description":"Apply stashed changes without removing from stash.","method":"POST","path_template":"/servers/websites/{website}/git/stash/apply","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"path","type":"string","required":false,"description":"Repository path"},{"name":"stash_index","type":"integer","required":false,"description":"Stash index (default 0)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"stash_drop","description":"DESTRUCTIVE - Drop a stash entry.","method":"POST","path_template":"/servers/websites/{website}/git/stash/drop","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"path","type":"string","required":false,"description":"Repository path"},{"name":"stash_index","type":"integer","required":false,"description":"Stash index (default 0)"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_clear","description":"DESTRUCTIVE - Clear all stash entries.","method":"POST","path_template":"/servers/websites/{website}/git/stash/clear","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"path","type":"string","required":false,"description":"Repository path"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"list_remotes","description":"List git remotes.","method":"GET","path_template":"/servers/websites/{website}/git/remotes","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"path","type":"string","required":false,"description":"Repository path"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of remote objects"}},{"name":"create_remotes","description":"Add a git remote.","method":"POST","path_template":"/servers/websites/{website}/git/remotes","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"path","type":"string","required":false,"description":"Repository path"},{"name":"name","type":"string","required":true,"description":"Remote name"},{"name":"url","type":"string","required":true,"description":"Remote URL"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_remotes","description":"DESTRUCTIVE - Remove a git remote.","method":"DELETE","path_template":"/servers/websites/{website}/git/remotes","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"path","type":"string","required":false,"description":"Repository path"},{"name":"name","type":"string","required":true,"description":"Remote name to remove"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"update_remotes","description":"Set URL for a git remote.","method":"PUT","path_template":"/servers/websites/{website}/git/remotes/{remote}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"remote","type":"string","required":true,"description":"Remote name (URL parameter)"}],"body_params":[{"name":"path","type":"string","required":false,"description":"Repository path"},{"name":"url","type":"string","required":true,"description":"New remote URL"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}}]},{"name":"websites_groups","scope":"websites:groups","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"list_website_groups","description":"List website groups (folders) the authenticated client owns. Each group is a panel-side folder that organizes websites for filtering and bulk actions. Groups never reach managed servers.","method":"GET","path_template":"/website-groups","path_params":[],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number for pagination."},{"name":"per_page","type":"integer","required":false,"description":"Number of items per page. Minimum 10, maximum 200, default 50."},{"name":"search","type":"string","required":false,"description":"Filter groups by name (case-insensitive substring)."},{"name":"all","type":"boolean","required":false,"description":"Set to 1 to return every group without pagination (use sparingly)."},{"name":"order_by","type":"string","required":false,"description":"Column to sort by. Allowed: name, created_at. Default: name."},{"name":"order_direction","type":"string","required":false,"description":"Sort direction. Allowed: asc, desc. Default: asc."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"integer","data[].name":"string","data[].color":"string (hex color, 3 or 6 digit)","data[].websites_count":"integer","data[].created_at":"string (ISO 8601)","data[].updated_at":"string (ISO 8601)","meta":"object"}},{"name":"create_website_groups","description":"Create a new website group. Group names are unique per client. The color is stored as a hex string and used to render the group badge on the website list.","method":"POST","path_template":"/website-groups","path_params":[],"body_params":[{"name":"name","type":"string","required":true,"description":"Group display name (max 100 chars, must be unique within your account)."},{"name":"color","type":"string","required":true,"description":"Hex color used for the group badge. Required leading '#'. Exactly 3 or 6 hex digits (no alpha channel). Case-insensitive. No whitespace. Examples: #0d6efd or #fff."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.id":"integer","data.name":"string","data.color":"string (hex color)","data.websites_count":"integer (always 0 on create)","data.created_at":"string (ISO 8601)","data.updated_at":"string (ISO 8601)","message":"string"}},{"name":"update_website_groups","description":"Rename and/or recolor an existing group.","method":"PUT","path_template":"/website-groups/{group}","path_params":[{"name":"group","type":"integer","required":true,"description":"Group ID (URL parameter)."}],"body_params":[{"name":"name","type":"string","required":true,"description":"New group name (max 100 chars, unique within your account)."},{"name":"color","type":"string","required":true,"description":"Hex color. Required leading '#'. Exactly 3 or 6 hex digits (no alpha channel). Case-insensitive. No whitespace."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.id":"integer","data.name":"string","data.color":"string","data.websites_count":"integer","data.created_at":"string (ISO 8601)","data.updated_at":"string (ISO 8601)","message":"string"}},{"name":"delete_website_groups","description":"DESTRUCTIVE - Delete one or more groups in a single transaction. Member websites are automatically un-grouped (their group_id flips back to null). No website is deleted by this action.","method":"DELETE","path_template":"/website-groups","path_params":[],"body_params":[{"name":"ids","type":"array","required":true,"description":"Array of positive-integer group IDs to delete. Each ID must be a positive integer, maximum 100 per call."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object","data.deleted":"integer (rows actually deleted, may differ from requested when ids include cross-tenant or stale entries)","data.requested":"integer (count of ids submitted in the body array)"}},{"name":"update_group","description":"Assign a single website to a group, or remove it from its current group by passing the 'none' sentinel. Only the website owner can change its group. Stage websites cannot be assigned.","method":"PUT","path_template":"/servers/websites/{website}/group","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)."}],"body_params":[{"name":"client_website_group_id","type":"integer|string","required":true,"description":"Target group ID (integer or numeric string). Pass null, the literal string 'none', the integer 0, the string '0', or an empty string '' to remove the website from its current group."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"update_bulk","description":"Assign many websites to a group (or remove them from any group via the 'none' sentinel) in one transaction. Per-website ownership and stage gates apply. Cross-tenant or stage IDs are silently skipped to prevent ID-existence probing. The response counts how many writes actually landed vs the total requested.","method":"PUT","path_template":"/servers/websites/group/bulk","path_params":[],"body_params":[{"name":"website_ids","type":"array","required":true,"description":"Array of positive-integer website IDs to target (1-100 per call)."},{"name":"client_website_group_id","type":"integer|string","required":true,"description":"Target group ID (integer or numeric string). Pass null, the literal string 'none', the integer 0, the string '0', or an empty string '' to bulk-unassign every targeted website. When applied is less than total in the response, per-id failure reasons (cross-tenant, stage-excluded, or non-owner) are intentionally not returned to prevent ID-existence probing. Callers should re-fetch the targeted websites if they need to know which retained their prior group assignment."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.applied":"integer (number of websites actually updated)","data.total":"integer (number of website IDs submitted)"}}]},{"name":"websites_image-optimizer","scope":"websites:image-optimizer","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"list_settings","description":"Get image optimizer settings.","method":"GET","path_template":"/servers/websites/{website}/image-optimizer/settings","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - image optimizer settings (compression quality, formats, exclusions, etc.)"}},{"name":"update_settings","description":"Update image optimizer settings.","method":"PUT","path_template":"/servers/websites/{website}/image-optimizer/settings","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"quality","type":"integer","required":false,"description":"Image quality 1-100"},{"name":"auto_optimize","type":"boolean","required":false,"description":"Enable auto-optimization"},{"name":"convert_to_webp","type":"boolean","required":false,"description":"Convert images to WebP"},{"name":"backup_originals","type":"boolean","required":false,"description":"Keep backup of originals"},{"name":"max_width","type":"integer","required":false,"description":"Maximum image width 100-10000"},{"name":"max_height","type":"integer","required":false,"description":"Maximum image height 100-10000"},{"name":"preserve_metadata","type":"boolean","required":false,"description":"Preserve image metadata"},{"name":"backup_retention_days","type":"integer","required":false,"description":"Backup retention in days (0,7,30,60,90)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_scan","description":"Scan for images that can be optimized.","method":"POST","path_template":"/servers/websites/{website}/image-optimizer/scan","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"path","type":"string","required":false,"description":"Path to scan"},{"name":"include_optimized","type":"boolean","required":false,"description":"Include already optimized images"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - image scan results (total images found, optimizable count, sizes)"}},{"name":"list_backup_files","description":"Get backup files list with pagination.","method":"GET","path_template":"/servers/websites/{website}/image-optimizer/backup-files","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page"},{"name":"search","type":"string","required":false,"description":"Search filter"},{"name":"order_by","type":"string","required":false,"description":"Sort field"},{"name":"order_direction","type":"string","required":false,"description":"Sort direction (asc/desc)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - backup files list with pagination (file paths, sizes, optimization dates)"}},{"name":"get_running","description":"Get running image optimizer process.","method":"GET","path_template":"/servers/websites/{website}/image-optimizer/running","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object or empty array"}},{"name":"create_optimize","description":"Optimize images.","method":"POST","path_template":"/servers/websites/{website}/image-optimizer/optimize","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"paths","type":"array","required":false,"description":"Specific image paths to optimize"},{"name":"all_images","type":"boolean","required":false,"description":"Optimize all images"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object"}},{"name":"restore","description":"DESTRUCTIVE - Restore original images from backup.","method":"POST","path_template":"/servers/websites/{website}/image-optimizer/restore","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"paths","type":"array","required":false,"description":"Specific image paths to restore"},{"name":"all_images","type":"boolean","required":false,"description":"Restore all images"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object"}},{"name":"cleanup","description":"DESTRUCTIVE - Clean up image optimizer backups.","method":"POST","path_template":"/servers/websites/{website}/image-optimizer/cleanup","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"confirm","type":"boolean","required":false,"description":"Confirm cleanup"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object"}}]},{"name":"websites_logs","scope":"websites:logs","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"list_php_errors","description":"Get PHP error logs for a website.","method":"GET","path_template":"/servers/websites/{website}/logs/php-errors","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"offset","type":"integer","required":false,"description":"Byte offset to start reading from"},{"name":"cursor","type":"string","required":false,"description":"Cursor for pagination"},{"name":"lines","type":"integer","required":false,"description":"Number of lines to return"},{"name":"search","type":"string","required":false,"description":"Search term to filter logs"},{"name":"level","type":"string","required":false,"description":"Error level filter: 'fatal', 'parse', 'warning', 'notice', 'strict', 'deprecated', or 'unknown' (or 'all' / omit for no filter)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object"}},{"name":"delete_php_errors","description":"DESTRUCTIVE - Clear PHP error logs for a website.","method":"DELETE","path_template":"/servers/websites/{website}/logs/php-errors","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"list_web_errors","description":"Get the web server (nginx) error log for a website - upstream/gateway errors such as a 502 'upstream sent too big header'. Covers the most recent (current-hour) entries.","method":"GET","path_template":"/servers/websites/{website}/logs/web-errors","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"offset","type":"integer","required":false,"description":"Byte offset to start reading from"},{"name":"cursor","type":"string","required":false,"description":"Cursor for pagination"},{"name":"lines","type":"integer","required":false,"description":"Number of lines to return"},{"name":"search","type":"string","required":false,"description":"Search term to filter logs"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object"}},{"name":"get_redis_cache","description":"Get Redis cache logs for a website.","method":"GET","path_template":"/servers/websites/{website}/logs/redis-cache","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"offset","type":"integer","required":false,"description":"Byte offset to start reading from"},{"name":"cursor","type":"string","required":false,"description":"Cursor for pagination"},{"name":"lines","type":"integer","required":false,"description":"Number of lines to return"},{"name":"search","type":"string","required":false,"description":"Search term to filter logs"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object"}},{"name":"delete_redis_cache","description":"DESTRUCTIVE - Clear Redis cache logs for a website.","method":"DELETE","path_template":"/servers/websites/{website}/logs/redis-cache","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_php","description":"DESTRUCTIVE - Clear PHP logs for a website.","method":"DELETE","path_template":"/servers/websites/{website}/logs/php","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_parse","description":"Parse and filter website logs by type with pagination.","method":"GET","path_template":"/servers/websites/{website}/logs/parse","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"type","type":"string","required":false,"description":"Log type: 'access', 'mail', 'ftp', 'crons', 'waf', 'challenge' (default: 'access')"},{"name":"day","type":"string","required":false,"description":"Day filter: 'today', 'yesterday', or 'YYYY-MM-DD' (within last 30 days)"},{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page (max 200)"},{"name":"search","type":"string","required":false,"description":"Search term"},{"name":"hour_start","type":"integer","required":false,"description":"Start hour filter (0-23)"},{"name":"hour_end","type":"integer","required":false,"description":"End hour filter (0-24)"},{"name":"timezone_offset","type":"integer","required":false,"description":"Timezone offset in minutes from UTC"},{"name":"scope","type":"string","required":false,"description":"Scope filter: 'all', 'human', 'bot', 'ai'"},{"name":"filters","type":"object","required":false,"description":"Additional log-specific filters"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","meta.current_page":"integer","meta.per_page":"integer","meta.total":"integer"}}]},{"name":"websites_mail","scope":"websites:mail","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"get_details","description":"Get email server details.","method":"GET","path_template":"/servers/websites/mails/details/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":{"smtp_host":"string","smtp_port":"integer","imap_host":"string","imap_port":"integer","pop3_host":"string","pop3_port":"integer"}}},{"name":"get_forwarders","description":"Get list of email forwarders.","method":"GET","path_template":"/servers/websites/mails/forwarders/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":[{"id":"string","user":"string","destinations":"array (or the string blackhole when mail is discarded)"}],"meta":"object"}},{"name":"create_forwarders","description":"Create an email forwarder.","method":"POST","path_template":"/servers/websites/mails/forwarders/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"user","type":"string","required":true,"description":"Forwarder local part (the text before the @), for example info. Up to 64 characters."},{"name":"blackhole","type":"boolean","required":true,"description":"When true, matching mail is discarded instead of forwarded. When false, you must provide destinations."},{"name":"destinations","type":"array","required":false,"description":"Array of destination email addresses to forward to. Required when blackhole is false. Each item must be a valid email address."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":{"id":"string","user":"string","destinations":"array (or the string blackhole when mail is discarded)"}}},{"name":"update_forwarders","description":"Update an email forwarder.","method":"PUT","path_template":"/servers/websites/mails/forwarders/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"id","type":"string","required":true,"description":"Identifier of the forwarder to update. Use the id from the forwarders list (the forwarder local part)."},{"name":"blackhole","type":"boolean","required":true,"description":"When true, matching mail is discarded instead of forwarded. When false, you must provide destinations."},{"name":"destinations","type":"array","required":false,"description":"Array of destination email addresses to forward to. Required when blackhole is false. Each item must be a valid email address."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":{"id":"string","user":"string","destinations":"array (or the string blackhole when mail is discarded)"}}},{"name":"delete_forwarders","description":"DESTRUCTIVE - Delete email forwarders.","method":"DELETE","path_template":"/servers/websites/mails/forwarders/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"ids","type":"array","required":true,"description":"Array of forwarder IDs to delete. Each id is the forwarder local part string (the id value from the forwarders list). Maximum 500 per call, because each one is applied on the server individually."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_filters","description":"Get list of mail filters. Filters are per-mailbox: pass a mailbox to scope the list, or omit it to list every mailbox's filters.","method":"GET","path_template":"/servers/websites/mails/filters/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"mailbox","type":"string","required":false,"description":"Mailbox local part to scope the list to"},{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Results per page (max 100)"},{"name":"search","type":"string","required":false,"description":"Search filters by name or mailbox"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":[{"id":"string","mailbox":"string","name":"string","combine":"string","conditions":"array","actions":"array","active":"boolean","position":"integer"}],"meta":{"current_page":"integer","last_page":"integer","per_page":"integer","total":"integer"}}},{"name":"create_filters","description":"Create a mail filter for a mailbox.","method":"POST","path_template":"/servers/websites/mails/filters/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"mailbox","type":"string","required":true,"description":"Mailbox local part the filter belongs to"},{"name":"name","type":"string","required":true,"description":"Filter name (unique within the mailbox)"},{"name":"combine","type":"string","required":true,"description":"Match mode: all or any"},{"name":"conditions","type":"array","required":true,"description":"Match conditions, each an object {field (from/to/subject/cc), operator (contains/is/begins-with/ends-with), value}"},{"name":"actions","type":"array","required":true,"description":"Actions, each an object {type (fileinto/mark_read/mark_flagged/stop), folder (for fileinto)}"},{"name":"active","type":"boolean","required":false,"description":"Whether the filter is active (default true)"},{"name":"position","type":"integer","required":false,"description":"Initial sort position (lower numbers run earlier). Optional, defaults to 0."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":{"id":"string","mailbox":"string","name":"string"}}},{"name":"update_filters","description":"Update a mail filter. Identified by mailbox plus name. Replaces its conditions, actions, match mode and active flag.","method":"PUT","path_template":"/servers/websites/mails/filters/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"mailbox","type":"string","required":true,"description":"Mailbox local part the filter belongs to"},{"name":"name","type":"string","required":true,"description":"Filter name to update"},{"name":"combine","type":"string","required":true,"description":"Match mode: all or any"},{"name":"conditions","type":"array","required":true,"description":"Match conditions (see create)"},{"name":"actions","type":"array","required":true,"description":"Actions (see create)"},{"name":"active","type":"boolean","required":false,"description":"Whether the filter is active. Optional, defaults to true. If the filter is currently disabled and you omit this field it will be re-enabled. Pass false to keep it disabled."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":{"id":"string","mailbox":"string","name":"string"}}},{"name":"delete_filters","description":"DESTRUCTIVE - Delete mail filters.","method":"DELETE","path_template":"/servers/websites/mails/filters/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"ids","type":"array","required":true,"description":"Filter ids to delete, each the composite \"mailbox:name\" key from the list response. Maximum 500 per call, because each one is applied on the server individually."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_filter_settings","description":"Get a mailbox's filter settings (whether detected spam is moved to the Junk folder).","method":"GET","path_template":"/servers/websites/mails/filter-settings/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"mailbox","type":"string","required":true,"description":"Mailbox local part the settings belong to"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":{"spam_to_junk":"boolean","filters_available":"boolean"}}},{"name":"create_filter_settings","description":"Set a mailbox's filter settings (turn move-detected-spam-to-Junk on or off).","method":"POST","path_template":"/servers/websites/mails/filter-settings/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"mailbox","type":"string","required":true,"description":"Mailbox local part the settings belong to"},{"name":"spam_to_junk","type":"boolean","required":true,"description":"Whether detected spam is filed into the Junk folder. On by default. Set false to leave detected spam in the inbox."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_dns_settings","description":"Get email DNS settings (SPF, DKIM, DMARC).","method":"GET","path_template":"/servers/websites/mails/dns-settings/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":{"spf":{"record":"string","valid":"boolean"},"dkim":{"record":"string","valid":"boolean"},"dmarc":{"record":"string","valid":"boolean"}}}},{"name":"list_mailboxes","description":"Get mailboxes list. Supports pagination.","method":"GET","path_template":"/servers/websites/{website}/mails/mailboxes","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array (each mailbox: { id, user, newsletter (boolean), vacation, autoresponder, size })","meta":"object"}},{"name":"create_mailboxes","description":"Create a new mailbox.","method":"POST","path_template":"/servers/websites/{website}/mails/mailboxes","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"user","type":"string","required":true,"description":"Mailbox username (local part of the email)"},{"name":"newsletter","type":"boolean","required":false,"description":"If true, mark this mailbox as a newsletter sender (more lenient bounce-rate threshold in mail-guard)."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object"}},{"name":"update_mailboxes","description":"Update mailbox autoresponder settings.","method":"PUT","path_template":"/servers/websites/{website}/mails/mailboxes/{mailbox}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"mailbox","type":"string","required":true,"description":"Mailbox username (URL parameter)"}],"body_params":[{"name":"active","type":"boolean","required":true,"description":"Whether the autoresponder is active"},{"name":"message","type":"string","required":true,"description":"Autoresponder message body"},{"name":"subject_prefix","type":"string","required":true,"description":"Subject line prefix (max 30 chars)"},{"name":"frequency","type":"string","required":true,"description":"Reply frequency in seconds (86400-604800)"},{"name":"cc_emails","type":"array","required":false,"description":"Array of CC email addresses"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"update_password","description":"Change mailbox password. Returns the new password.","method":"PUT","path_template":"/servers/websites/{website}/mails/mailboxes/{mailbox}/password","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"mailbox","type":"string","required":true,"description":"Mailbox username (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data.password":"string"}},{"name":"get_webmail","description":"Get a one-time webmail (Roundcube) SSO login URL for a mailbox. The URL logs straight into the mailbox with no password and is valid for about 30 seconds. Available only for mailboxes hosted on the local mail server (not when the site uses an external mail provider).","method":"GET","path_template":"/servers/websites/{website}/mails/mailboxes/{mailbox}/webmail","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"mailbox","type":"string","required":true,"description":"Mailbox username (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.url":"string","data.expires":"string"}},{"name":"update_newsletter","description":"Set the newsletter flag on a mailbox. True enables the more lenient bounce-rate threshold used by mail-guard for newsletter senders.","method":"PUT","path_template":"/servers/websites/{website}/mails/mailboxes/{mailbox}/newsletter","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"mailbox","type":"string","required":true,"description":"Mailbox username (URL parameter)"}],"body_params":[{"name":"newsletter","type":"boolean","required":true,"description":"True to mark as newsletter mailbox, false to unmark."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data.mailbox":"string","data.newsletter":"boolean"}},{"name":"delete_mailboxes","description":"DESTRUCTIVE - Delete one or more mailboxes.","method":"DELETE","path_template":"/servers/websites/{website}/mails/mailboxes","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"ids","type":"array","required":true,"description":"Array of mailbox usernames to delete. Maximum 500 per call, because each one is applied on the server individually."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_autoresponder","description":"Get autoresponder settings for a mailbox.","method":"GET","path_template":"/servers/websites/{website}/mails/autoresponder","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"user","type":"string","required":true,"description":"Mailbox username (query parameter)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.user":"string","data.autoresponder":"object|null"}},{"name":"get_vacation","description":"Get vacation settings for a mailbox.","method":"GET","path_template":"/servers/websites/{website}/mails/vacation","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"user","type":"string","required":true,"description":"Mailbox username (query parameter)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.user":"string","data.vacation":"object|null"}},{"name":"create_vacation","description":"Set vacation (out-of-office) auto-reply settings for a mailbox.","method":"POST","path_template":"/servers/websites/{website}/mails/vacation","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"user","type":"string","required":true,"description":"Mailbox username"},{"name":"active","type":"boolean","required":true,"description":"Enable or disable vacation auto-reply"},{"name":"message","type":"string","required":true,"description":"Auto-reply message body"},{"name":"subject_prefix","type":"string","required":false,"description":"Subject line prefix for auto-reply"},{"name":"vacation_start","type":"string","required":false,"description":"Start date (YYYY-MM-DD)"},{"name":"vacation_end","type":"string","required":false,"description":"End date (YYYY-MM-DD), null for no end date"},{"name":"frequency","type":"integer","required":false,"description":"Auto-reply frequency in hours (how often to re-send to same sender)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object"}},{"name":"list_external_providers","description":"List all available external email providers (plural). Use the singular /mails/external-provider endpoint to retrieve the CURRENTLY active provider for this website.","method":"GET","path_template":"/servers/websites/{website}/mails/external-providers","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array"}},{"name":"get_external_provider","description":"Get the currently active external email provider configuration for the website. Singular - returns the active provider (or empty object if none). Use /mails/external-providers (plural) to list all available provider options.","method":"GET","path_template":"/servers/websites/{website}/mails/external-provider","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object"}},{"name":"create_external_providers","description":"Set an external email provider for the website.","method":"POST","path_template":"/servers/websites/{website}/mails/external-providers","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"provider","type":"string","required":true,"description":"Provider name (e.g., google, microsoft)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object"}},{"name":"delete_external_providers","description":"DESTRUCTIVE - Remove external email provider (reset to local).","method":"DELETE","path_template":"/servers/websites/{website}/mails/external-providers","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_spam_whitelist","description":"Get spam whitelist entries. Supports pagination.","method":"GET","path_template":"/servers/websites/{website}/mails/spam-whitelist","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","meta":"object"}},{"name":"create_spam_whitelist","description":"Add an entry to the spam whitelist.","method":"POST","path_template":"/servers/websites/{website}/mails/spam-whitelist","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"entry_type","type":"string","required":true,"description":"Type of entry: email or domain"},{"name":"entry_value","type":"string","required":true,"description":"Email address or domain name"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_spam_whitelist","description":"DESTRUCTIVE - Delete spam whitelist entries.","method":"DELETE","path_template":"/servers/websites/{website}/mails/spam-whitelist","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"ids","type":"array","required":true,"description":"Array of whitelist entry IDs to delete. Maximum 500 per call, because each one is applied on the server individually."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_catch_all","description":"Get catch-all email configuration.","method":"GET","path_template":"/servers/websites/{website}/mails/catch-all","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object"}},{"name":"create_catch_all","description":"Set catch-all email address.","method":"POST","path_template":"/servers/websites/{website}/mails/catch-all","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"email","type":"string","required":true,"description":"Catch-all email address"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"update_catch_all","description":"Update catch-all email address.","method":"PUT","path_template":"/servers/websites/{website}/mails/catch-all","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"email","type":"string","required":true,"description":"New catch-all email address"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_catch_all","description":"DESTRUCTIVE - Remove catch-all email configuration.","method":"DELETE","path_template":"/servers/websites/{website}/mails/catch-all","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_send_mail","description":"Send a test email from the website. Requires the websites:mail scope, not websites:devtools - this endpoint sends email. Throttled to 3 requests per minute and 20 per hour, counted against ONE shared budget with the panel's own send-mail form and with Proper, so all three draw on the same allowance. Over the limit returns 429 with a Retry-After header.","method":"POST","path_template":"/servers/websites/{website}/dev-tools/send-mail","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"from_email","type":"string","required":true,"description":"The address the message is sent from. Its domain must be one this account owns, otherwise the send is refused."},{"name":"to_email","type":"string","required":true,"description":"The address to send the message to."},{"name":"subject","type":"string","required":true,"description":"The subject line. Up to 255 characters."},{"name":"body","type":"string","required":true,"description":"The message text. Up to 100000 characters."},{"name":"content_type","type":"string","required":false,"description":"Send \"text/html\" to write the body as HTML, or \"text/plain\" for plain text."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object"}},{"name":"dev_tools_list_mailboxes","description":"Get mailboxes for the website. Requires the websites:mail scope, not websites:devtools - this endpoint reads the account's mail surface.","method":"GET","path_template":"/servers/websites/{website}/dev-tools/mailboxes","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of mailboxes"}}]},{"name":"websites_mail-guard","scope":"websites:mail-guard","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"get_history","description":"Get mail guard history.","method":"GET","path_template":"/servers/websites/{website}/mail-guard/history","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"period","type":"string","required":false,"description":"Period filter: today, 7, 30"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of history entries","meta":"object"}},{"name":"get_pending","description":"Get pending/delayed mails.","method":"GET","path_template":"/servers/websites/{website}/mail-guard/pending","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of pending mails","meta":"object"}},{"name":"list_stats","description":"Get mail guard statistics.","method":"GET","path_template":"/servers/websites/{website}/mail-guard/stats","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"period","type":"string","required":false,"description":"Period filter: today, 7, 30"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - mail guard statistics (sent, delayed, rejected, banned counts for the specified period)"}},{"name":"create_release","description":"Release a delayed mail for sending.","method":"POST","path_template":"/servers/websites/{website}/mail-guard/release","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"mail_id","type":"string","required":true,"description":"Mail ID to release"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_reject","description":"DESTRUCTIVE - Reject a delayed mail.","method":"POST","path_template":"/servers/websites/{website}/mail-guard/reject","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"mail_id","type":"string","required":true,"description":"Mail ID to reject"},{"name":"reason","type":"string","required":false,"description":"Rejection reason"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_bulk_release","description":"Bulk release multiple pending mails.","method":"POST","path_template":"/servers/websites/{website}/mail-guard/bulk-release","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"mail_ids","type":"array","required":true,"description":"Array of mail IDs to release"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_bulk_reject","description":"DESTRUCTIVE - Bulk reject multiple pending mails.","method":"POST","path_template":"/servers/websites/{website}/mail-guard/bulk-reject","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"mail_ids","type":"array","required":true,"description":"Array of mail IDs to reject"},{"name":"reason","type":"string","required":false,"description":"Optional rejection reason recorded on the rejected mails. Defaults to a standard message when omitted."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_release_all","description":"Release all pending mails.","method":"POST","path_template":"/servers/websites/{website}/mail-guard/release-all","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_reject_all","description":"DESTRUCTIVE - Reject all pending mails.","method":"POST","path_template":"/servers/websites/{website}/mail-guard/reject-all","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"list_bans","description":"Get list of banned senders.","method":"GET","path_template":"/servers/websites/{website}/mail-guard/bans","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"active_only","type":"boolean","required":false,"description":"Only return active bans (default: true)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of bans"}},{"name":"create_unban","description":"Unban a sender.","method":"POST","path_template":"/servers/websites/{website}/mail-guard/unban","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"tracker","type":"string","required":true,"description":"Ban tracker ID"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_learn_time","description":"Get learn time status.","method":"GET","path_template":"/servers/websites/{website}/mail-guard/learn-time","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - learn time status (active boolean, days remaining, start/end dates)"}},{"name":"update_learn_time","description":"Set learn time.","method":"PUT","path_template":"/servers/websites/{website}/mail-guard/learn-time","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"days","type":"integer","required":true,"description":"Number of days for the learning period. One of: 3, 7, 14."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_learn_time","description":"DESTRUCTIVE - Cancel learn time.","method":"DELETE","path_template":"/servers/websites/{website}/mail-guard/learn-time","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"reset_learning","description":"DESTRUCTIVE - Reset all learned patterns.","method":"POST","path_template":"/servers/websites/{website}/mail-guard/reset-learning","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}}]},{"name":"websites_monitors","scope":"websites:monitors","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"list_monitors","description":"List monitors for a website with pagination.","method":"GET","path_template":"/servers/websites/{website}/monitors","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of monitor objects","meta":"pagination object","meta.incomplete":"boolean, present only when at least one monitor could not be read","meta.unreadable_count":"integer, present only alongside meta.incomplete. How many monitors were dropped from data because they could not be read"}},{"name":"create_monitors","description":"Add a monitor for a website.","method":"POST","path_template":"/servers/websites/{website}/monitors","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"url","type":"string","required":true,"description":"Full URL to monitor (e.g., https://domain.com/path). It must start with https:// followed by this website's own domain, and then a path. An address on any other host is refused with 422, because the monitor can only watch this website. The host is matched without case. The part after the domain must be a plain path. A second address inside it (//other-host/x), a .. step, a backslash or a line break are all refused with 422, and so are their percent-encoded spellings such as %2f for a slash or %2e%2e for a .. step. A query string and a fragment are allowed. The whole value is limited to 255 characters."},{"name":"type","type":"string","required":true,"description":"Monitor type. One of: status, keyword."},{"name":"keyword","type":"string","required":false,"description":"Keyword to check for (required when type is keyword)"},{"name":"emails","type":"array","required":true,"description":"Email addresses for notifications. At least one is required, and at most twenty. Each must be a valid address, and duplicates are rejected."},{"name":"active","type":"boolean","required":true,"description":"Whether the monitor is active."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"update_monitors","description":"Update a monitor.","method":"PUT","path_template":"/servers/websites/{website}/monitors/{monitor}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"monitor","type":"string","required":true,"description":"URL-safe base64 monitor ID (URL parameter). Use the id string exactly as the monitors list returned it. It uses - and _ in place of + and /, so it is always a single path segment and needs no extra escaping. IDs issued earlier in the plain base64 alphabet are still accepted."}],"body_params":[{"name":"keyword","type":"string","required":false,"description":"Updated keyword (for keyword type monitors)"},{"name":"emails","type":"array","required":true,"description":"Updated notification email addresses. At least one is required, and at most twenty. Each must be a valid address, and duplicates are rejected."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_monitors","description":"DESTRUCTIVE - Remove monitors.","method":"DELETE","path_template":"/servers/websites/{website}/monitors","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"ids","type":"array","required":true,"description":"Array of monitor IDs to remove, each taken exactly as the monitors list returned it. Maximum 500 per call, because each one is applied on the server individually."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_enable","description":"Enable a monitor. Resuming a paused monitor is the counterpart of disable, and it is the right call after planned maintenance.","method":"POST","path_template":"/servers/websites/{website}/monitors/{monitor}/enable","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"monitor","type":"string","required":true,"description":"URL-safe base64 monitor ID (URL parameter). Use the id string exactly as the monitors list returned it. It uses - and _ in place of + and /, so it is always a single path segment and needs no extra escaping. IDs issued earlier in the plain base64 alphabet are still accepted."}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_disable","description":"Disable a monitor.","method":"POST","path_template":"/servers/websites/{website}/monitors/{monitor}/disable","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"monitor","type":"string","required":true,"description":"URL-safe base64 monitor ID (URL parameter). Use the id string exactly as the monitors list returned it. It uses - and _ in place of + and /, so it is always a single path segment and needs no extra escaping. IDs issued earlier in the plain base64 alphabet are still accepted."}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"list_events","description":"Get events for a specific monitor with pagination.","method":"GET","path_template":"/servers/websites/{website}/monitors/{monitor}/events","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"monitor","type":"string","required":true,"description":"URL-safe base64 monitor ID (URL parameter). Use the id string exactly as the monitors list returned it. It uses - and _ in place of + and /, so it is always a single path segment and needs no extra escaping. IDs issued earlier in the plain base64 alphabet are still accepted."}],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of event objects","meta":"pagination object","code":"string (CONDITIONAL, 404 only) - one of two values. monitor_not_exists: the monitor no longer exists and its uptime history is gone. server_not_exists: the website's server is not set up for monitoring, so it has no uptime history at all - contact support. Branch on both. Neither one means the site was down"}}]},{"name":"websites_nodejs","scope":"websites:nodejs","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"get_default_version","description":"Get default Node.js version.","method":"GET","path_template":"/servers/websites/{website}/nodejs/default-version","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"string (version)"}},{"name":"update_default_version","description":"Set default Node.js version.","method":"PUT","path_template":"/servers/websites/{website}/nodejs/default-version","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"version","type":"string","required":true,"description":"Node.js version"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"list_versions","description":"Get installed and supported Node.js versions.","method":"GET","path_template":"/servers/websites/{website}/nodejs/versions","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":{"installed":"array of installed versions","supported":"array of supported versions"}}},{"name":"create_versions","description":"Install a Node.js version.","method":"POST","path_template":"/servers/websites/{website}/nodejs/versions","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"version","type":"string","required":true,"description":"Node.js version to install"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_versions","description":"DESTRUCTIVE - Uninstall a Node.js version.","method":"DELETE","path_template":"/servers/websites/{website}/nodejs/versions","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"version","type":"string","required":true,"description":"Node.js version to uninstall"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"list_applications","description":"Get Node.js applications list with pagination.","method":"GET","path_template":"/servers/websites/{website}/nodejs/applications","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of application objects","meta":"pagination object"}},{"name":"create_applications","description":"Create a Node.js application.","method":"POST","path_template":"/servers/websites/{website}/nodejs/applications","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"name","type":"string","required":true,"description":"Application name"},{"name":"node_version","type":"string","required":true,"description":"Node.js version. Send the major line, for example 20. A full version like 20.11.1 is still accepted, and it is saved as its major line, so later reads of this application return 20 rather than the value you sent. The website keeps one release of each major line, so the major is what actually decides which one runs."},{"name":"runner","type":"string","required":true,"description":"How the app starts: node_file (run an entry file), npm_script, or yarn_script"},{"name":"entry_file","type":"string","required":false,"description":"Entry file relative to the app folder, no leading slash or .. segments (required when runner is node_file), e.g. app.js or dist/server.js"},{"name":"script","type":"string","required":false,"description":"Package script name to run (required when runner is npm_script or yarn_script), e.g. start"},{"name":"arguments","type":"string","required":false,"description":"Extra arguments passed to the application process"},{"name":"working_directory","type":"string","required":false,"description":"App folder relative to the site root (ignored for a main app, which is pinned to the site root)"},{"name":"access_type","type":"string","required":true,"description":"How the app is served: port or path"},{"name":"proxy_path","type":"string","required":false,"description":"Subpath prefix when access_type is path (required for a non-main path app), e.g. /api/"},{"name":"ip_whitelist","type":"array","required":false,"description":"IP addresses allowed to reach a path-served app, up to 256 of them. An address range may be written as 203.0.113.0/24. Setting one on an app served on its own port is refused. Leave the field out to create the app with no address restriction."},{"name":"auto_start","type":"boolean","required":false,"description":"Start the app automatically after a server reboot. Defaults to true."},{"name":"is_main","type":"boolean","required":false,"description":"Serve this app at the site root (only one main app per site across the Python and Node.js tracks)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":{"id":"string","website_id":"integer","name":"string","slug":"string","node_version":"string","runner":"string","entry_file":"string|null","script":"string|null","arguments":"array","working_directory":"string|null","port":"integer|null","access_type":"string|null","proxy_path":"string|null","ip_whitelist":"array","auto_start":"boolean","is_main":"boolean","service_id":"string|null","service_unit":"string|null","status":"string|null","last_status_synced_at":"string (ISO 8601)|null","created_at":"string (ISO 8601)|null","updated_at":"string (ISO 8601)|null"}}},{"name":"update_applications","description":"Update a Node.js application.","method":"PUT","path_template":"/servers/websites/{website}/nodejs/applications/{application}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"application","type":"string","required":true,"description":"Application ID (URL parameter)"}],"body_params":[{"name":"name","type":"string","required":true,"description":"Application name"},{"name":"node_version","type":"string","required":true,"description":"Node.js version. Send the major line, for example 20. A full version like 20.11.1 is still accepted, and it is saved as its major line, so later reads of this application return 20 rather than the value you sent. The website keeps one release of each major line, so the major is what actually decides which one runs. One exception on update. If you send exactly the value this application already has, it is kept exactly as it is, so an older application still holding a full version like 24.15.0 keeps that value when you re-send it unchanged. That is on purpose, because changing it here without rebuilding the application would leave the saved value and the running one disagreeing. To move such an application onto its major line, send the major itself, for example 24."},{"name":"runner","type":"string","required":true,"description":"How the app starts: node_file, npm_script, or yarn_script"},{"name":"entry_file","type":"string","required":false,"description":"Entry file relative to the app folder (required when runner is node_file)"},{"name":"script","type":"string","required":false,"description":"Package script name (required when runner is npm_script or yarn_script)"},{"name":"arguments","type":"string","required":false,"description":"Extra arguments passed to the application process"},{"name":"working_directory","type":"string","required":false,"description":"App folder relative to the site root"},{"name":"access_type","type":"string","required":true,"description":"How the app is served: port or path"},{"name":"proxy_path","type":"string","required":false,"description":"Subpath prefix when access_type is path"},{"name":"ip_whitelist","type":"array","required":false,"description":"IP addresses allowed to reach a path-served app, up to 256 of them. An address range may be written as 203.0.113.0/24. Setting one on an app served on its own port is refused, and a list saved on such an app before that rule existed is kept but not applied while it stays on its own port. If that app is later served on a path instead, the saved list starts applying, so check it before switching. Leave the field out to keep the addresses already saved, or send an empty array to clear them - except when this website could not show the saved list at all and returned it as an empty array, where the empty send is ignored on purpose and a valid list has to be sent first. When the read gave you the addresses, the empty send clears them."},{"name":"auto_start","type":"boolean","required":false,"description":"Start the app automatically after a server reboot. This is one of the two fields that does not follow the replace-the-whole-definition rule. Leaving it out keeps the application's current setting rather than resetting it to on, so an application you deliberately stopped stays stopped through an unrelated edit such as a rename. Send it explicitly to change it."},{"name":"is_main","type":"boolean","required":false,"description":"Serve this app at the site root. This call replaces the whole definition, so leaving this field out is read as false rather than as no change. If the app is currently the main one and you want it to stay that way, send it as true with the rest of the body."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":{"id":"string","website_id":"integer","name":"string","slug":"string","node_version":"string","runner":"string","entry_file":"string|null","script":"string|null","arguments":"array","working_directory":"string|null","port":"integer|null","access_type":"string|null","proxy_path":"string|null","ip_whitelist":"array","auto_start":"boolean","is_main":"boolean","service_id":"string|null","service_unit":"string|null","status":"string|null","last_status_synced_at":"string (ISO 8601)|null","created_at":"string (ISO 8601)|null","updated_at":"string (ISO 8601)|null"}}},{"name":"delete_applications","description":"DESTRUCTIVE - Delete Node.js applications.","method":"DELETE","path_template":"/servers/websites/{website}/nodejs/applications","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"ids","type":"array","required":true,"description":"Array of application IDs to delete"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_start","description":"Start a Node.js application.","method":"POST","path_template":"/servers/websites/{website}/nodejs/applications/{application}/start","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"application","type":"string","required":true,"description":"Application ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_stop","description":"Stop a Node.js application.","method":"POST","path_template":"/servers/websites/{website}/nodejs/applications/{application}/stop","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"application","type":"string","required":true,"description":"Application ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"restart","description":"DESTRUCTIVE - Restart a Node.js application.","method":"POST","path_template":"/servers/websites/{website}/nodejs/applications/{application}/restart","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"application","type":"string","required":true,"description":"Application ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"update_auto_start","description":"Set auto-start for a Node.js application.","method":"PUT","path_template":"/servers/websites/{website}/nodejs/applications/{application}/auto-start","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"application","type":"string","required":true,"description":"Application ID (URL parameter)"}],"body_params":[{"name":"auto_start","type":"boolean","required":true,"description":"Enable or disable auto-start"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"list_logs","description":"Get Node.js application logs.","method":"GET","path_template":"/servers/websites/{website}/nodejs/applications/{application}/logs","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"application","type":"string","required":true,"description":"Application ID (URL parameter)"}],"body_params":[{"name":"lines","type":"integer","required":false,"description":"Number of log lines (10-1000, default 200)"},{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"search","type":"string","required":false,"description":"Search filter"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - log lines array and pagination info"}},{"name":"get_env","description":"Get a Node.js application's environment variables. Requires full website permission. Values are masked - each value is replaced by a fixed token and only the names are returned.","method":"GET","path_template":"/servers/websites/{website}/nodejs/applications/{application}/env","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"application","type":"string","required":true,"description":"Application ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":{"env":"object - map of variable name to a masked value token"}}},{"name":"update_env","description":"Replace a Node.js application's environment variables. Requires full website permission. Sends the whole map, so any name not included is removed. Reserved names (PORT, NODE_ENV, and any name starting with PROGINTER_) cannot be overridden.","method":"PUT","path_template":"/servers/websites/{website}/nodejs/applications/{application}/env","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"application","type":"string","required":true,"description":"Application ID (URL parameter)"}],"body_params":[{"name":"env","type":"object","required":true,"description":"Map of variable name to string value. Replaces the entire set. Names use letters, numbers and underscores and do not start with a number. Up to 200 variables, each value up to 4096 characters."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}}]},{"name":"websites_notifications","scope":"websites:notifications","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"list_notifications","description":"Get website notifications.","method":"GET","path_template":"/servers/websites/{website}/notifications","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"read_status","type":"string","required":false,"description":"Filter: all, read, unread"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","data[].id":"integer","data[].type":"string","data[].title":"string","data[].text":"string","data[].is_read":"boolean","data[].read_at":"string|null","data[].website_id":"integer","data[].created_at":"integer","data[].source":"string","data[].stage_domain":"string|null","meta":"object","meta.current_page":"integer","meta.per_page":"integer","meta.total":"integer"}},{"name":"get_unread_count","description":"Get unread notification count.","method":"GET","path_template":"/servers/websites/{website}/notifications/unread-count","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":{"count":"integer"}}},{"name":"update_mark_read","description":"Mark notifications as read.","method":"PUT","path_template":"/servers/websites/{website}/notifications/mark-read","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"ids","type":"array","required":false,"description":"Specific notification IDs. An EMPTY array, an explicit null, and an omitted field all mean MARK EVERY NOTIFICATION on this website (and on its stages when no source is named) - they do not mean \"mark nothing\". The reply reports how many rows the request actually matched, so an ID belonging to another website counts as zero. Every ID must be a whole number above zero. A 0 or a negative refuses the WHOLE request with 422 and marks nothing, rather than being dropped from the list. Any other unusable entry is dropped rather than refused, and the reply's message text says how many were ignored, so do not assume every ID you sent was acted on. That count is only in the English message on this endpoint, not a separate field."},{"name":"source","type":"string","required":false,"description":"Which list the IDs come from: website or stage. Leaving it out means website. When you send NO ids, this also decides which list is cleared: name one and only that list is touched, leave it out and both are. Any other value is refused with 422 and nothing is changed."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_notifications","description":"DESTRUCTIVE - Delete notifications.","method":"DELETE","path_template":"/servers/websites/{website}/notifications","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"ids","type":"array","required":false,"description":"Specific notification IDs. An EMPTY array, an explicit null, and an omitted field all mean DELETE EVERY NOTIFICATION on this website (and on its stages when no source is named) - they do not mean \"delete nothing\". A client building this list from a user selection must not call the endpoint at all when the selection is empty. The reply reports how many rows were actually deleted, so an ID belonging to another website counts as zero. Every ID must be a whole number above zero. A 0 or a negative refuses the WHOLE request with 422 and deletes nothing, rather than being dropped from the list. Any other unusable entry is dropped rather than refused, and the reply's message text says how many were ignored, so do not assume every ID you sent was deleted. That count is only in the English message on this endpoint, not a separate field."},{"name":"source","type":"string","required":false,"description":"Which list the IDs come from: website or stage. Leaving it out means website. When you send NO ids, this also decides which list is cleared: name one and only that list is touched, leave it out and both are. Any other value is refused with 422 and nothing is changed."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}}]},{"name":"websites_page-speed","scope":"websites:page-speed","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"list_reports","description":"Get paginated list of PageSpeed reports.","method":"GET","path_template":"/servers/websites/{website}/page-speed/reports","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of reports","meta":{"current_page":"integer","last_page":"integer","per_page":"integer","total":"integer","remaining_scans":"integer","max_scans":"integer"}}},{"name":"get_page_speed","description":"Get a single PageSpeed report with full data.","method":"GET","path_template":"/servers/websites/{website}/page-speed/{report}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"report","type":"integer","required":true,"description":"Report ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.scores.performance":"integer","data.scores.accessibility":"integer","data.scores.best_practices":"integer","data.scores.seo":"integer","data.audits":"array","data.metadata":"object"}},{"name":"create_scan","description":"Run a new PageSpeed scan.","method":"POST","path_template":"/servers/websites/{website}/page-speed/scan","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":{"id":"integer","status":"string"}}}]},{"name":"websites_php","scope":"websites:php","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"get_version","description":"Get current PHP version for website.","method":"GET","path_template":"/servers/websites/php/{website}/version","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.version":"float"}},{"name":"update_version","description":"Change PHP version for website.","method":"PUT","path_template":"/servers/websites/php/{website}/version","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"version","type":"string","required":true,"description":"PHP version (e.g., 8.2)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object","data.version":"float|string"}},{"name":"list_details","description":"Get PHP configuration details.","method":"GET","path_template":"/servers/websites/php/{website}/details","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.version":"float","data.settings":"array","data.modules":"array","data.time":"string"}},{"name":"update_settings","description":"Update a PHP setting.","method":"PUT","path_template":"/servers/websites/php/{website}/settings","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"key","type":"string","required":true,"description":"Setting key"},{"name":"value","type":"string","required":true,"description":"Setting value"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object","data.key":"string","data.value":"string"}},{"name":"list_processes","description":"Get running PHP processes.","method":"GET","path_template":"/servers/websites/php/{website}/processes","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":[{"pid":"integer","script":"string","memory":"integer","cpu":"float","started_at":"string (datetime)"}]}},{"name":"delete_processes","description":"DESTRUCTIVE - Kill PHP processes.","method":"DELETE","path_template":"/servers/websites/php/{website}/processes","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"ids","type":"array","required":true,"description":"Process IDs to kill"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}}]},{"name":"websites_presets","scope":"websites:presets","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"list_wp_presets","description":"Get WordPress presets list with pagination.","method":"GET","path_template":"/servers/websites/{website}/wp-presets","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of preset objects","meta":"pagination object"}},{"name":"list_admins","description":"Get WordPress admin users for preset creation.","method":"GET","path_template":"/servers/websites/{website}/wp-presets/admins","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of admin user objects"}},{"name":"list_content_types","description":"Get WordPress content types for preset creation.","method":"GET","path_template":"/servers/websites/{website}/wp-presets/content-types","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of content type objects"}},{"name":"list_plugins","description":"Get WordPress plugins for preset creation.","method":"GET","path_template":"/servers/websites/{website}/wp-presets/plugins","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of plugin objects"}},{"name":"list_themes","description":"Get WordPress themes for preset creation.","method":"GET","path_template":"/servers/websites/{website}/wp-presets/themes","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of theme objects"}},{"name":"create_wp_presets","description":"Save a WordPress preset from the current website.","method":"POST","path_template":"/servers/websites/{website}/wp-presets","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"name","type":"string","required":true,"description":"Preset name"},{"name":"description","type":"string","required":false,"description":"Preset description"},{"name":"admin_user_ids","type":"array","required":true,"description":"Admin user IDs to include"},{"name":"included_content_types","type":"array","required":false,"description":"Content types to include"},{"name":"selected_plugins","type":"array","required":false,"description":"Plugins to include"},{"name":"selected_themes","type":"array","required":false,"description":"Themes to include"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":{"id":"integer","name":"string","status":"string"}}},{"name":"get_wp_presets","description":"Get preset details.","method":"GET","path_template":"/servers/websites/{website}/wp-presets/{preset}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"preset","type":"integer","required":true,"description":"Preset ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":{"id":"integer","name":"string","description":"string|null","metadata":"object","source_website":"string","source_domain":"string","archive_size":"integer|null","created_at":"string (ISO 8601)"}}},{"name":"create_apply","description":"DESTRUCTIVE - Apply a preset to the current website.","method":"POST","path_template":"/servers/websites/{website}/wp-presets/{preset}/apply","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"preset","type":"integer","required":true,"description":"Preset ID (URL parameter)"}],"body_params":[{"name":"categories","type":"array","required":true,"description":"Categories to apply from the preset. At least one required. Each item one of: plugins, theme, admin_users, settings, content, products_orders, optimizer_cache."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_wp_presets","description":"DESTRUCTIVE - Delete a WordPress preset.","method":"DELETE","path_template":"/servers/websites/{website}/wp-presets/{preset}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"preset","type":"integer","required":true,"description":"Preset ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}}]},{"name":"websites_python","scope":"websites:python","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"get_default_version","description":"Get default Python version.","method":"GET","path_template":"/servers/websites/{website}/python/default-version","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"string (version)"}},{"name":"update_default_version","description":"Set default Python version.","method":"PUT","path_template":"/servers/websites/{website}/python/default-version","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"version","type":"string","required":true,"description":"Python version (major.minor, e.g. 3.12)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"list_versions","description":"Get installed and supported Python versions.","method":"GET","path_template":"/servers/websites/{website}/python/versions","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":{"installed":"array of installed versions","supported":"array of supported versions"}}},{"name":"create_versions","description":"Install a Python version.","method":"POST","path_template":"/servers/websites/{website}/python/versions","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"version","type":"string","required":true,"description":"Python version to install (major.minor, e.g. 3.12)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_versions","description":"DESTRUCTIVE - Uninstall a Python version.","method":"DELETE","path_template":"/servers/websites/{website}/python/versions","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"version","type":"string","required":true,"description":"Python version to uninstall (major.minor, e.g. 3.12)"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"list_applications","description":"Get Python applications list with pagination.","method":"GET","path_template":"/servers/websites/{website}/python/applications","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of application objects","meta":"pagination object"}},{"name":"create_applications","description":"Create a Python application. The web framework (Django/Flask/FastAPI) and app server (gunicorn/uvicorn) are auto-detected from the app folder. The customer never chooses them.","method":"POST","path_template":"/servers/websites/{website}/python/applications","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"name","type":"string","required":true,"description":"Application name"},{"name":"python_version","type":"string","required":true,"description":"Python version (major.minor, e.g. 3.12)"},{"name":"access_type","type":"string","required":true,"description":"How the app is served: port or path"},{"name":"working_directory","type":"string","required":false,"description":"App folder relative to the site root (required for a non-main app, ignored for a main app, which is pinned to the site root)"},{"name":"proxy_path","type":"string","required":false,"description":"Subpath prefix when access_type is path (required for a non-main path app), e.g. /api/"},{"name":"arguments","type":"string","required":false,"description":"Extra gunicorn/uvicorn arguments, e.g. --workers 2 --timeout 60"},{"name":"ip_whitelist","type":"array","required":false,"description":"IP addresses allowed to reach a path-served app, up to 256 of them. An address range may be written as 203.0.113.0/24. Setting one on an app served on its own port is refused. Leave the field out to create the app with no address restriction."},{"name":"auto_start","type":"boolean","required":false,"description":"Start the app automatically after a server reboot. Defaults to true."},{"name":"is_main","type":"boolean","required":false,"description":"Serve this app at the site root (only one main app per site across the Python and Node.js tracks)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":{"id":"string","website_id":"integer","name":"string","slug":"string","python_version":"string","framework":"string|null (auto-detected)","app_type":"string|null (wsgi or asgi, auto-detected)","entrypoint":"string|null (auto-detected)","requirements_source":"string|null (auto-detected dependency source)","arguments":"string|null","working_directory":"string|null","port":"integer|null","access_type":"string|null","proxy_path":"string|null","ip_whitelist":"array","auto_start":"boolean","is_main":"boolean","service_id":"string|null","service_unit":"string|null","status":"string|null","last_status_synced_at":"string (ISO 8601)|null","created_at":"string (ISO 8601)|null","updated_at":"string (ISO 8601)|null"}}},{"name":"update_applications","description":"Update a Python application.","method":"PUT","path_template":"/servers/websites/{website}/python/applications/{application}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"application","type":"string","required":true,"description":"Application ID (URL parameter)"}],"body_params":[{"name":"name","type":"string","required":true,"description":"Application name"},{"name":"python_version","type":"string","required":true,"description":"Python version (major.minor, e.g. 3.12)"},{"name":"access_type","type":"string","required":true,"description":"How the app is served: port or path"},{"name":"working_directory","type":"string","required":false,"description":"App folder relative to the site root"},{"name":"proxy_path","type":"string","required":false,"description":"Subpath prefix when access_type is path"},{"name":"arguments","type":"string","required":false,"description":"Extra gunicorn/uvicorn arguments"},{"name":"ip_whitelist","type":"array","required":false,"description":"IP addresses allowed to reach a path-served app, up to 256 of them. An address range may be written as 203.0.113.0/24. Setting one on an app served on its own port is refused, and a list saved on such an app before that rule existed is kept but not applied while it stays on its own port. If that app is later served on a path instead, the saved list starts applying, so check it before switching. Leave the field out to keep the addresses already saved, or send an empty array to clear them - except when this website could not show the saved list at all and returned it as an empty array, where the empty send is ignored on purpose and a valid list has to be sent first. When the read gave you the addresses, the empty send clears them."},{"name":"auto_start","type":"boolean","required":false,"description":"Start the app automatically after a server reboot. This is one of the two fields that does not follow the replace-the-whole-definition rule. Leaving it out keeps the application's current setting rather than resetting it to on, so an application you deliberately stopped stays stopped through an unrelated edit such as a rename. Send it explicitly to change it."},{"name":"is_main","type":"boolean","required":false,"description":"Serve this app at the site root. This call replaces the whole definition, so leaving this field out is read as false rather than as no change. If the app is currently the main one and you want it to stay that way, send it as true with the rest of the body."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":{"id":"string","website_id":"integer","name":"string","slug":"string","python_version":"string","framework":"string|null (auto-detected)","app_type":"string|null (wsgi or asgi, auto-detected)","entrypoint":"string|null (auto-detected)","requirements_source":"string|null (auto-detected dependency source)","arguments":"string|null","working_directory":"string|null","port":"integer|null","access_type":"string|null","proxy_path":"string|null","ip_whitelist":"array","auto_start":"boolean","is_main":"boolean","service_id":"string|null","service_unit":"string|null","status":"string|null","last_status_synced_at":"string (ISO 8601)|null","created_at":"string (ISO 8601)|null","updated_at":"string (ISO 8601)|null"}}},{"name":"delete_applications","description":"DESTRUCTIVE - Delete Python applications.","method":"DELETE","path_template":"/servers/websites/{website}/python/applications","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"ids","type":"array","required":true,"description":"Array of application IDs to delete"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_start","description":"Start a Python application.","method":"POST","path_template":"/servers/websites/{website}/python/applications/{application}/start","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"application","type":"string","required":true,"description":"Application ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_stop","description":"Stop a Python application.","method":"POST","path_template":"/servers/websites/{website}/python/applications/{application}/stop","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"application","type":"string","required":true,"description":"Application ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"restart","description":"DESTRUCTIVE - Restart a Python application.","method":"POST","path_template":"/servers/websites/{website}/python/applications/{application}/restart","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"application","type":"string","required":true,"description":"Application ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"update_auto_start","description":"Set auto-start for a Python application.","method":"PUT","path_template":"/servers/websites/{website}/python/applications/{application}/auto-start","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"application","type":"string","required":true,"description":"Application ID (URL parameter)"}],"body_params":[{"name":"auto_start","type":"boolean","required":true,"description":"Enable or disable auto-start"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"list_logs","description":"Get Python application logs.","method":"GET","path_template":"/servers/websites/{website}/python/applications/{application}/logs","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"application","type":"string","required":true,"description":"Application ID (URL parameter)"}],"body_params":[{"name":"lines","type":"integer","required":false,"description":"Number of log lines (10-1000, default 200)"},{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"search","type":"string","required":false,"description":"Search filter"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - log lines array and pagination info"}},{"name":"get_env","description":"Get a Python application's environment variables. Requires full website permission. Values are masked - each value is replaced by a fixed token and only the names are returned.","method":"GET","path_template":"/servers/websites/{website}/python/applications/{application}/env","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"application","type":"string","required":true,"description":"Application ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":{"env":"object - map of variable name to a masked value token"}}},{"name":"update_env","description":"Replace a Python application's environment variables. Requires full website permission. Sends the whole map, so any name not included is removed. Reserved names (PORT, PYTHONUNBUFFERED, and any name starting with PROGINTER_) cannot be overridden.","method":"PUT","path_template":"/servers/websites/{website}/python/applications/{application}/env","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"application","type":"string","required":true,"description":"Application ID (URL parameter)"}],"body_params":[{"name":"env","type":"object","required":true,"description":"Map of variable name to string value. Replaces the entire set. Names use letters, numbers and underscores and do not start with a number. Up to 200 variables, each value up to 4096 characters."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}}]},{"name":"websites_redis","scope":"websites:redis","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"list_redis","description":"Get Redis details including connection info, status, and configuration.","method":"GET","path_template":"/servers/websites/{website}/redis","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":{"active":"boolean","redis_version":"string|null","php_redis_version":"string|null","socket_path":"string|null","port":"integer|null","remote_host":"string|null","remote_enabled":"boolean","global_remote_allowed":"boolean","require_username":"boolean","memory_usage_percent":"number|null","config":{"maxmemory_policy":"string","maxmemory_bytes":"integer|null","appendonly":"string"},"runtime":{"used_memory_human":"string","used_memory_bytes":"integer|null"}}}},{"name":"get_password","description":"Get Redis password.","method":"GET","path_template":"/servers/websites/{website}/redis/password","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"string (password)"}},{"name":"create_password","description":"Generate a new Redis password.","method":"POST","path_template":"/servers/websites/{website}/redis/password","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"string (new password)"}},{"name":"create_activate","description":"Activate or deactivate Redis for a website.","method":"POST","path_template":"/servers/websites/{website}/redis/activate","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"active","type":"boolean","required":true,"description":"Enable (true) or disable (false) Redis"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"list_settings","description":"Get Redis settings (maxmemory policy, appendonly, etc.).","method":"GET","path_template":"/servers/websites/{website}/redis/settings","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"same structure as GET /servers/websites/{website}/redis - returns full Redis details including config"}},{"name":"update_settings","description":"Update Redis configuration (maxmemory eviction policy and/or AOF appendonly persistence). At least one of the two fields must be supplied - omitted fields remain unchanged. Requires full permission on the website.","method":"PUT","path_template":"/servers/websites/{website}/redis/settings","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"maxmemory_policy","type":"string","required":false,"description":"One of: allkeys-lru, allkeys-lfu, allkeys-random, volatile-lru, volatile-lfu, volatile-random, volatile-ttl, noeviction"},{"name":"appendonly","type":"boolean","required":false,"description":"Enable or disable AOF (appendonly) persistence"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_require_username","description":"Toggle Redis username requirement.","method":"POST","path_template":"/servers/websites/{website}/redis/require-username","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"require_username","type":"boolean","required":true,"description":"Require username for Redis authentication"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_remote_access","description":"Toggle Redis remote access.","method":"POST","path_template":"/servers/websites/{website}/redis/remote-access","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"enable","type":"boolean","required":true,"description":"Enable (true) or disable (false) remote access"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"purge_redis","description":"DESTRUCTIVE - Purge all Redis data for a website.","method":"POST","path_template":"/servers/websites/{website}/redis/purge","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}}]},{"name":"websites_resources","scope":"websites:resources","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"get_realtime","description":"Get realtime resource usage.","method":"GET","path_template":"/servers/websites/resources/realtime/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.cpu":"number - total CPU cores allocated (effective limit, equals server cores when no limit configured)","data.memory":"number - total memory allocated (GB, effective limit, equals server memory when no limit configured)","data.cpu_configured":"boolean - whether a CPU limit has been explicitly configured (false means data.cpu is the server-wide capacity, not a per-website limit)","data.memory_configured":"boolean - whether a memory limit has been explicitly configured (false means data.memory is the server-wide capacity, not a per-website limit)","data.current":"object","data.current.cpu":"number - current CPU usage (percentage)","data.current.memory":"number - current memory usage (GB)","data.current.io_read":"number - current I/O read","data.current.io_write":"number - current I/O write","data.current.iops_read":"number - current IOPS read","data.current.iops_write":"number - current IOPS write","data.current.entry_processes":"number - current entry processes","data.current.processes":"number - current processes","data.entry_processes":"number - entry processes limit","data.processes":"number - processes limit"}},{"name":"get_history","description":"Get resource usage history.","method":"GET","path_template":"/servers/websites/resources/history/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"period","type":"string","required":false,"description":"Time period: 'hour', 'day', 'week', 'month' (default: 'hour')"},{"name":"type","type":"string","required":false,"description":"Aggregation type: 'avg' or 'high' (default: 'avg')"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array - time-series resource usage data points"}},{"name":"get_limits","description":"Get resource limits.","method":"GET","path_template":"/servers/websites/resources/limits/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"type","type":"string","required":false,"description":"Limit type to retrieve (e.g., 'cpu', 'memory')"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.cpu":"number - CPU cores limit (0 = unlimited)","data.memory":"number - memory limit in GB (0 = unlimited)","data.disk":"number - disk limit in GB"}},{"name":"update_limits","description":"Set resource limits.","method":"PUT","path_template":"/servers/websites/resources/limits/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"type","type":"string","required":true,"description":"Limit type: 'cpu', 'memory'"},{"name":"value","type":"number","required":false,"description":"Limit value (CPU in cores, memory in GB)"},{"name":"no_limit","type":"boolean","required":false,"description":"Set to true to remove the limit"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object","data.type":"string - limit type that was set","data.value":"number - new limit value","data.no_limit":"boolean - whether the limit is removed"}}]},{"name":"websites_rules","scope":"websites:rules","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"list_ip_ranges","description":"Get available IP ranges grouped by category for use in rules.","method":"GET","path_template":"/servers/websites/{website}/rules/ip-ranges","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":{"categories":"array of category objects with key, label, ranges"}}},{"name":"list_rules","description":"Get rules list for a section (waf, ddos, cache, gdpr_lang) with pagination.","method":"GET","path_template":"/servers/websites/{website}/rules","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"section","type":"string","required":false,"description":"Section: waf, ddos, cache, gdpr_lang (default: waf). gdpr_lang rules pick the cookie-banner language and use the set_language action with params {\"language\":\"<declared banner-language code>\"}."},{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of rule objects","meta":"pagination object"}},{"name":"create_rules","description":"Create a new rule.","method":"POST","path_template":"/servers/websites/{website}/rules","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"section","type":"string","required":false,"description":"Section: waf, ddos, cache, gdpr_lang (default: waf). gdpr_lang rules pick the cookie-banner language and use the set_language action with params {\"language\":\"<declared banner-language code>\"}."},{"name":"name","type":"string","required":true,"description":"Rule name"},{"name":"description","type":"string","required":false,"description":"Rule description"},{"name":"conditions","type":"object","required":true,"description":"Rule conditions tree - a JSON OBJECT, not an array. Shape: {\"logic\":\"and|or\",\"groups\":[{\"logic\":\"and|or\",\"conditions\":[{\"field\":\"<field>\",\"operator\":\"<operator>\",\"value\":\"<value>\",\"key\":\"<name, keyed fields only>\"}],\"groups\":[]}]}. Required: top-level logic (and|or) and groups (an array with at least 1 group). Each group carries its own logic (and|or), plus optional conditions and nested groups arrays. Each condition needs field (one of: ip, country, uri, uri_path, user_agent, referer, method, host, query_string, cookie, cookie_name, cookie_value, request_header, scheme, is_ajax, is_static), operator (one of: equals, not_equals, contains, not_contains, matches, not_matches, in_cidr, not_in_cidr, in_list, not_in_list, in_range, not_in_range), and value (for field=country use 2-letter country codes in capitals, e.g. US, IL, DE). key is required only when field is cookie_value or request_header (the cookie or header name)."},{"name":"action","type":"object","required":true,"description":"Rule action: object with type and optional params. For rate_limit (waf section only): {\"type\":\"rate_limit\",\"params\":{\"requests_per_minute\":N OR \"requests_per_second\":N (one required), \"on_exceed\":\"block_request|delay|exit_http_status\" (only these three values), \"burst\":N (optional), \"scope\":\"ip\" (default, per source IP) OR \"rule\" (one shared bucket across all matching IPs, the only way to bound distributed crawlers like Facebook). If on_exceed=delay also include delay_seconds (1-30). If on_exceed=exit_http_status also include status_code (400/401/402/403/404/405/429/444/500/502/503).}}. Activation note: a scope=rule limit is saved immediately, but it can keep counting per IP address for a while before the shared count starts. Do not treat the success response as proof that the shared limit is active. Check the actual behavior before you rely on it. Other action.type values per section: WAF accepts block_request, bypass, log, exit_http_status, rate_limit, delay (a bypass action may include params {\"targets\":[...]} = a non-empty list of one or more distinct values from \"proxy\", \"xmlrpc\", \"owasp\", skipping ONLY those protections, a granular bypass, e.g. proxy-only releases a datacenter or VPN IP block while keeping XML-RPC and OWASP on. Omit params.targets for a full WAF bypass. Prefer the granular target, and REQUIRE the source IP to match: the source IP is the only non-forgeable request attribute you can pin narrowly (a country match is also non-forgeable but nation-wide), so a bypass matched only on a forgeable one like User-Agent, path, or a header lets any forger lift the WAF - match the source IP (use in_cidr, a list of CIDRs, /32 for a single IPv4 host or /128 for a single IPv6 host) with 'and' logic and keep it as narrow as the case allows. This has no exception for a bypass rule: an OWASP rule misfiring on request SHAPE across many legitimate visitors, such as OWASP CRS 920450 on WooCommerce checkout, is not a bypass-rule case, so turn off that specific rule via PUT /servers/websites/{website}/waf/modsecurity-rules/toggle with {rule_id, enabled:false} instead (one atomic call that turns off just that one rule and keeps all other OWASP protection on), never a server-tier bypass, which would lift OWASP on that path for every tenant). DDoS accepts bypass and challenge (challenge is one action that always serves the strong automatic checking page. The legacy names invisible_challenge, interstitial_challenge, and captcha_challenge are still accepted on existing rules, but use challenge for any new rule). Cache accepts bypass_cache, purge, ignore. purge REQUIRES params: scope (matched_url or all) and device (both, desktop or mobile) - a matching request clears that page, or the whole site, from the Proginter page cache and itself skips the cache. matched_url clears the address the request asked for, so a webhook or a save that should clear a different page needs scope all. The website's own caching plugin is not cleared. Only the address the visitor used is cleared, plus its www form. If the website uses wildcard subdomains, a rule does not clear subdomain addresses, only the addresses set up on the website. gdpr_lang accepts set_language."},{"name":"enabled","type":"boolean","required":false,"description":"Enable rule (default: true)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"string - UUID of the newly created rule (use for update/delete/toggle endpoints)"}},{"name":"update_rules","description":"Update an existing rule.","method":"PUT","path_template":"/servers/websites/{website}/rules/{rule}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"rule","type":"string","required":true,"description":"Rule ID (URL parameter)"}],"body_params":[{"name":"section","type":"string","required":false,"description":"Section: waf, ddos, cache, gdpr_lang (default: waf). gdpr_lang rules pick the cookie-banner language and use the set_language action with params {\"language\":\"<declared banner-language code>\"}."},{"name":"rule_id","type":"string","required":true,"description":"The rule's UUID (max 100 chars), required in the request body. Use the same value as the {rule} URL segment."},{"name":"name","type":"string","required":true,"description":"Rule name"},{"name":"description","type":"string","required":false,"description":"Rule description"},{"name":"conditions","type":"object","required":true,"description":"Rule conditions tree - a JSON OBJECT, not an array. Update replaces the whole rule, so conditions is required. Shape: {\"logic\":\"and|or\",\"groups\":[{\"logic\":\"and|or\",\"conditions\":[{\"field\":\"<field>\",\"operator\":\"<operator>\",\"value\":\"<value>\",\"key\":\"<name, keyed fields only>\"}],\"groups\":[]}]}. Required: top-level logic (and|or) and groups (an array with at least 1 group). Each group carries its own logic (and|or), plus optional conditions and nested groups arrays. Each condition needs field (one of: ip, country, uri, uri_path, user_agent, referer, method, host, query_string, cookie, cookie_name, cookie_value, request_header, scheme, is_ajax, is_static), operator (one of: equals, not_equals, contains, not_contains, matches, not_matches, in_cidr, not_in_cidr, in_list, not_in_list, in_range, not_in_range), and value (for field=country use 2-letter country codes in capitals, e.g. US, IL, DE). key is required only when field is cookie_value or request_header (the cookie or header name)."},{"name":"action","type":"object","required":true,"description":"Rule action (same shape as POST endpoint): object with type and optional params. For rate_limit (waf section only): {\"type\":\"rate_limit\",\"params\":{\"requests_per_minute\":N OR \"requests_per_second\":N (one required), \"on_exceed\":\"block_request|delay|exit_http_status\" (only these three values), \"burst\":N (optional), \"scope\":\"ip\" (default) OR \"rule\" (aggregate across all matching IPs). If on_exceed=delay also include delay_seconds (1-30). If on_exceed=exit_http_status also include status_code.}}. Activation note: a scope=rule limit is saved immediately, but the shared count can take a while to start. Check the actual behavior before you rely on it. Other action.type values per section: WAF accepts block_request, bypass, log, exit_http_status, rate_limit, delay (a bypass action may include params {\"targets\":[...]} = a non-empty list of one or more distinct values from \"proxy\", \"xmlrpc\", \"owasp\", skipping ONLY those protections, a granular bypass, e.g. proxy-only releases a datacenter or VPN IP block while keeping XML-RPC and OWASP on. Omit params.targets for a full WAF bypass. Prefer the granular target, and REQUIRE the source IP to match: the source IP is the only non-forgeable request attribute you can pin narrowly (a country match is also non-forgeable but nation-wide), so a bypass matched only on a forgeable one like User-Agent, path, or a header lets any forger lift the WAF - match the source IP (use in_cidr, a list of CIDRs, /32 for a single IPv4 host or /128 for a single IPv6 host) with 'and' logic and keep it as narrow as the case allows. This has no exception for a bypass rule: an OWASP rule misfiring on request SHAPE across many legitimate visitors, such as OWASP CRS 920450 on WooCommerce checkout, is not a bypass-rule case, so turn off that specific rule via PUT /servers/websites/{website}/waf/modsecurity-rules/toggle with {rule_id, enabled:false} instead (one atomic call that turns off just that one rule and keeps all other OWASP protection on), never a server-tier bypass, which would lift OWASP on that path for every tenant). DDoS accepts bypass and challenge (challenge is one action that always serves the strong automatic checking page. The legacy names invisible_challenge, interstitial_challenge, and captcha_challenge are still accepted on existing rules, but use challenge for any new rule). Cache accepts bypass_cache, purge, ignore. purge REQUIRES params: scope (matched_url or all) and device (both, desktop or mobile) - a matching request clears that page, or the whole site, from the Proginter page cache and itself skips the cache. matched_url clears the address the request asked for, so a webhook or a save that should clear a different page needs scope all. The website's own caching plugin is not cleared. Only the address the visitor used is cleared, plus its www form. If the website uses wildcard subdomains, a rule does not clear subdomain addresses, only the addresses set up on the website. gdpr_lang accepts set_language."},{"name":"enabled","type":"boolean","required":false,"description":"Enable/disable rule"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_rules","description":"DESTRUCTIVE - Delete rules. The reply states how many rule deletions were REQUESTED, not how many rules were removed: the whole id list is sent in one call, so an id that matches no rule is counted the same as one that does. Read the rules list back to confirm what is gone.","method":"DELETE","path_template":"/servers/websites/{website}/rules","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"section","type":"string","required":false,"description":"Section: waf, ddos, cache, gdpr_lang (default: waf). gdpr_lang rules pick the cookie-banner language and use the set_language action with params {\"language\":\"<declared banner-language code>\"}."},{"name":"ids","type":"array","required":true,"description":"Array of rule IDs to delete"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"update_reorder","description":"Reorder rules within a section.","method":"PUT","path_template":"/servers/websites/{website}/rules/reorder","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"section","type":"string","required":false,"description":"Section: waf, ddos, cache, gdpr_lang (default: waf). gdpr_lang rules pick the cookie-banner language and use the set_language action with params {\"language\":\"<declared banner-language code>\"}."},{"name":"order","type":"array","required":true,"description":"Array of rule IDs in desired order"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"update_toggle","description":"Toggle a rule's enabled state.","method":"PUT","path_template":"/servers/websites/{website}/rules/{rule}/toggle","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"rule","type":"string","required":true,"description":"Rule ID (URL parameter)"}],"body_params":[{"name":"section","type":"string","required":false,"description":"Section: waf, ddos, cache, gdpr_lang (default: waf). gdpr_lang rules pick the cookie-banner language and use the set_language action with params {\"language\":\"<declared banner-language code>\"}."},{"name":"rule_id","type":"string","required":true,"description":"The rule's UUID (max 100 chars), required in the request body. Use the same value as the {rule} URL segment."},{"name":"enabled","type":"boolean","required":true,"description":"Enable (true) or disable (false)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}}]},{"name":"websites_settings","scope":"websites:settings","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"list_settings","description":"Get website settings including public directory, maintenance mode, SEO, security headers, password protection, and accessibility.","method":"GET","path_template":"/servers/websites/{website}/settings","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object","data.public_directory":"string|null (custom public folder path)","data.maintenance_enable":"boolean","data.maintenance_exclude_ips":"array of strings","data.redis":"boolean","data.seo_block":"boolean","data.never_suspend":"boolean (only for non-shared, non-staging websites)","data.wp_rest_api":"boolean","data.wp_allow_js_uploads":"boolean","data.wp_allow_css_uploads":"boolean","data.wp_vulnerability_alerts_disabled":"boolean","data.requests_allow_purge_from_local_only":"boolean","data.requests_allowed_methods":"array of strings","data.requests_header_x_content_type_options":"boolean","data.requests_header_referrer_policy":"string|false","data.requests_content_security_policy":"string|false","data.requests_permissions_policy":"string|false","data.requests_allow_frame_from":"array|string|false","data.requests_allow_cors_from":"array|string|false","data.requests_allow_cors_methods":"array|string|false","data.password_protection_enable":"boolean","data.password_protection_username":"string","data.accessibility_enable":"boolean","data.accessibility_langs":"array of strings","data.accessibility_default_lang":"string","data.accessibility_mode":"string","data.accessibility_location_vertical":"string","data.accessibility_location_horizontal":"string","data.accessibility_statement_link":"string|null","data.accessibility_statement_title":"string","data.accessibility_statement_body":"string","data.accessibility_exclude_uri":"array of strings"}},{"name":"update_settings","description":"Update website settings. Supports partial updates - only provided fields are changed.","method":"PUT","path_template":"/servers/websites/{website}/settings","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"custom_public_directory","type":"boolean","required":false,"description":"Enable custom public directory"},{"name":"public_directory","type":"string","required":false,"description":"Custom public directory path"},{"name":"maintenance_enable","type":"boolean","required":false,"description":"Enable maintenance mode"},{"name":"seo_block","type":"boolean","required":false,"description":"Block search engine indexing"},{"name":"never_suspend","type":"boolean","required":false,"description":"Prevent automatic suspension (owner only)"},{"name":"password_protection_enable","type":"boolean","required":false,"description":"Enable password protection"},{"name":"password_protection_username","type":"string","required":false,"description":"Password protection username"},{"name":"password_protection_password","type":"string","required":false,"description":"Password protection password"},{"name":"maintenance_exclude_ips","type":"array","required":false,"description":"Public IP addresses or public IP ranges allowed to bypass maintenance mode. Each item is a single public IP or a public CIDR range. Private and reserved ranges are not accepted."},{"name":"requests_allowed_methods","type":"array","required":false,"description":"Allowed HTTP request methods for the site. Each item is one of GET, POST, HEAD, PUT, DELETE, OPTIONS, PURGE. The list must include GET, POST and HEAD."},{"name":"requests_header_x_content_type_options","type":"boolean","required":false,"description":"Send the X-Content-Type-Options nosniff response header."},{"name":"requests_header_referrer_policy","type":"string","required":false,"description":"Referrer-Policy response header value. One of no_policy, no-referrer, no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url. Use no_policy to send no Referrer-Policy header."},{"name":"requests_content_security_policy_toggle","type":"boolean","required":false,"description":"Enable a custom Content-Security-Policy header. When true, provide requests_content_security_policy. When false, any custom policy is cleared."},{"name":"requests_content_security_policy","type":"string","required":false,"description":"Custom Content-Security-Policy header value, up to 5000 characters. Required when requests_content_security_policy_toggle is true. The literal word \"false\" is not accepted as a value. To turn the header off, set requests_content_security_policy_toggle to false."},{"name":"requests_permissions_policy_toggle","type":"boolean","required":false,"description":"Enable a custom Permissions-Policy header. When true, provide requests_permissions_policy. When false, any custom policy is cleared."},{"name":"requests_permissions_policy","type":"string","required":false,"description":"Custom Permissions-Policy header value, up to 5000 characters. Required when requests_permissions_policy_toggle is true. The literal word \"false\" is not accepted as a value. To turn the header off, set requests_permissions_policy_toggle to false."},{"name":"requests_allow_frame_from_toggle","type":"boolean","required":false,"description":"Enable iframe-embedding control, which builds the CSP frame-ancestors directive. When true, provide requests_allow_frame_from. When false, iframe embedding control is turned off."},{"name":"requests_allow_frame_from","type":"array","required":false,"description":"Origins allowed to embed this site in an iframe. Each item is self, none (deny all embedding), a full origin like https://app.example.com, a bare domain like example.com, a wildcard subdomain like *.example.com or https://*.example.com, or a hostname like localhost. Up to 2048 characters per item."},{"name":"requests_allow_cors_from_toggle","type":"boolean","required":false,"description":"Enable CORS. When true, provide requests_allow_cors_from and requests_allow_cors_methods. When false, CORS is turned off."},{"name":"requests_allow_cors_from","type":"array","required":false,"description":"Origins allowed to make cross-origin requests. Each item is a full origin like https://app.example.com, a bare domain like example.com, a hostname like localhost, or * for all origins. A wildcard subdomain is not accepted for CORS. Up to 255 characters per item."},{"name":"requests_allow_cors_methods","type":"array","required":false,"description":"HTTP methods allowed for cross-origin requests. Each item is one of GET, POST, DELETE, PUT, PATCH, OPTIONS, HEAD."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_maintenance_page","description":"Get the custom maintenance page HTML.","method":"GET","path_template":"/servers/websites/{website}/settings/maintenance-page","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"string (HTML content)"}},{"name":"update_maintenance_page","description":"Set custom maintenance page HTML.","method":"PUT","path_template":"/servers/websites/{website}/settings/maintenance-page","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"code","type":"string","required":true,"description":"HTML content for the maintenance page"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_maintenance_page","description":"DESTRUCTIVE - Restore maintenance page to default.","method":"DELETE","path_template":"/servers/websites/{website}/settings/maintenance-page","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_suspended_page","description":"Get the custom suspended page HTML. Not available for shared websites.","method":"GET","path_template":"/servers/websites/{website}/settings/suspended-page","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"string (HTML content)"}},{"name":"update_suspended_page","description":"Set custom suspended page HTML. Not available for shared websites.","method":"PUT","path_template":"/servers/websites/{website}/settings/suspended-page","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"code","type":"string","required":true,"description":"HTML content for the suspended page"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_suspended_page","description":"DESTRUCTIVE - Restore suspended page to default. Not available for shared websites.","method":"DELETE","path_template":"/servers/websites/{website}/settings/suspended-page","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"list_error_pages","description":"Get custom error page HTML for a specific HTTP error code.","method":"GET","path_template":"/servers/websites/{website}/settings/error-pages","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"code","type":"integer","required":true,"description":"HTTP error code (400-405, 408, 409, 413, 419, 429, 500-505)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"string (HTML content)"}},{"name":"update_error_pages","description":"Set custom error page HTML for a specific HTTP error code.","method":"PUT","path_template":"/servers/websites/{website}/settings/error-pages","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"code","type":"integer","required":true,"description":"HTTP error code (400-405, 408, 409, 413, 419, 429, 500-505)"},{"name":"html","type":"string","required":true,"description":"HTML content for the error page"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_error_pages","description":"DESTRUCTIVE - Restore error page to default for a specific HTTP error code.","method":"DELETE","path_template":"/servers/websites/{website}/settings/error-pages","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"code","type":"integer","required":true,"description":"HTTP error code (400-405, 408, 409, 413, 419, 429, 500-505)"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"list_routes","description":"Get URL rewrite routes list with pagination.","method":"GET","path_template":"/servers/websites/{website}/settings/routes","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of route objects","meta":"pagination object"}},{"name":"create_routes","description":"Create a URL rewrite route.","method":"POST","path_template":"/servers/websites/{website}/settings/routes","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"pattern","type":"string","required":true,"description":"Regex pattern to match"},{"name":"replacement","type":"string","required":true,"description":"Replacement URL"},{"name":"flags","type":"string","required":true,"description":"Rewrite flags (e.g., last, permanent, redirect)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"update_routes","description":"Update a URL rewrite route.","method":"PUT","path_template":"/servers/websites/{website}/settings/routes/{route}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"route","type":"string","required":true,"description":"Current pattern of the route (URL parameter, URL-encoded)"}],"body_params":[{"name":"new_pattern","type":"string","required":true,"description":"New regex pattern"},{"name":"replacement","type":"string","required":true,"description":"Replacement URL"},{"name":"flags","type":"string","required":true,"description":"Rewrite flags"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_routes","description":"DESTRUCTIVE - Delete URL rewrite routes.","method":"DELETE","path_template":"/servers/websites/{website}/settings/routes","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"ids","type":"array","required":true,"description":"Array of route patterns to delete. Maximum 500 per call, because each one is applied on the server individually."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_move","description":"Move a route to a new position in the order.","method":"POST","path_template":"/servers/websites/{website}/settings/routes/move","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"source_pattern","type":"string","required":true,"description":"Pattern of the route to move"},{"name":"after_pattern","type":"string","required":false,"description":"Pattern to place after (null for first position)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_open_basedir","description":"Get open_basedir users list. Not available for staging websites.","method":"GET","path_template":"/servers/websites/{website}/settings/open-basedir","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of user objects with id, name, domain","meta":"pagination object"}},{"name":"create_open_basedir","description":"Add a user to open_basedir. Not available for staging websites. Both websites must be on the same server (400 otherwise), and the caller needs full permission on the target website being added, not only on the website being changed (403 otherwise).","method":"POST","path_template":"/servers/websites/{website}/settings/open-basedir","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"user","type":"integer","required":true,"description":"Website ID of the user to add"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_open_basedir","description":"DESTRUCTIVE - Remove users from open_basedir. Not available for staging websites.","method":"DELETE","path_template":"/servers/websites/{website}/settings/open-basedir","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"ids","type":"array","required":true,"description":"Array of usernames to remove. Maximum 500 per call, because each one is applied on the server individually."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}}]},{"name":"websites_ssh","scope":"websites:ssh","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"list_details","description":"Get SSH connection details for a website.","method":"GET","path_template":"/servers/websites/{website}/ssh/details","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object"}},{"name":"get_ip_restriction","description":"Get SSH IP restriction status.","method":"GET","path_template":"/servers/websites/{website}/ssh/ip-restriction","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.ip_restriction_enabled":"boolean"}},{"name":"update_ip_restriction","description":"Enable or disable SSH IP restriction (whitelist). Requires full permission on the website.","method":"PUT","path_template":"/servers/websites/{website}/ssh/ip-restriction","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"enabled","type":"boolean","required":true,"description":"Whether IP restriction is active"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object","data.enabled":"boolean"}},{"name":"list_keys","description":"Get SSH keys list. Supports pagination.","method":"GET","path_template":"/servers/websites/{website}/ssh/keys","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","meta":"object"}},{"name":"create_keys","description":"Add a new SSH key.","method":"POST","path_template":"/servers/websites/{website}/ssh/keys","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"owner_name","type":"string","required":true,"description":"Display name for the SSH key"},{"name":"public_key","type":"string","required":true,"description":"SSH public key content"},{"name":"permanent","type":"boolean","required":true,"description":"Whether the key is permanent"},{"name":"expire_at","type":"integer","required":false,"description":"Unix timestamp for expiry (required when permanent is false)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_generate","description":"Generate a new ed25519 SSH keypair. Adds the public key to the account and returns the private key once - it is never stored, so save it from this response.","method":"POST","path_template":"/servers/websites/{website}/ssh/keys/generate","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"owner_name","type":"string","required":true,"description":"Display name for the generated SSH key"},{"name":"permanent","type":"boolean","required":true,"description":"Whether the key is permanent"},{"name":"expire_at","type":"integer","required":false,"description":"Unix timestamp for expiry (required when permanent is false)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":{"public_key":"string","private_key":"string"}}},{"name":"update_keys","description":"Update an SSH key.","method":"PUT","path_template":"/servers/websites/{website}/ssh/keys/{key}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"key","type":"string","required":true,"description":"SSH public key identifier (URL parameter)"}],"body_params":[{"name":"owner_name","type":"string","required":true,"description":"Display name for the SSH key"},{"name":"permanent","type":"boolean","required":true,"description":"Whether the key is permanent"},{"name":"expire_at","type":"integer","required":false,"description":"Unix timestamp for expiry (required when permanent is false)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_keys","description":"DESTRUCTIVE - Delete one or more SSH keys.","method":"DELETE","path_template":"/servers/websites/{website}/ssh/keys","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"ids","type":"array","required":true,"description":"Array of SSH public key identifiers to delete. Maximum 500 per call, because each one is applied on the server individually."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"list_sessions","description":"Get SSH sessions list. Supports pagination.","method":"GET","path_template":"/servers/websites/{website}/ssh/sessions","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","meta":"object"}},{"name":"delete_sessions","description":"DESTRUCTIVE - Kill SSH sessions. Provide either a session PID in \"ids\", or set \"all\": true to kill all sessions.","method":"DELETE","path_template":"/servers/websites/{website}/ssh/sessions","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"all","type":"boolean","required":false,"description":"Set to true to kill all sessions. When true, \"ids\" is ignored."},{"name":"ids","type":"array","required":false,"description":"Array containing a single session PID to kill. Required unless \"all\" is true."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_whitelist","description":"Get SSH whitelist IPs. Requires public_key query parameter.","method":"GET","path_template":"/servers/websites/{website}/ssh/whitelist","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"public_key","type":"string","required":true,"description":"SSH public key identifier (query parameter)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array","meta":"object"}},{"name":"create_whitelist","description":"Add an IP to the SSH whitelist.","method":"POST","path_template":"/servers/websites/{website}/ssh/whitelist","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"public_key","type":"string","required":true,"description":"SSH public key identifier"},{"name":"ip","type":"string","required":true,"description":"IP address or CIDR range"},{"name":"permanent","type":"boolean","required":true,"description":"Whether the whitelist entry is permanent"},{"name":"expire_at","type":"integer","required":false,"description":"Unix timestamp for expiry (required when permanent is false)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"update_whitelist","description":"Update an SSH whitelist entry.","method":"PUT","path_template":"/servers/websites/{website}/ssh/whitelist/{entry}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"entry","type":"string","required":true,"description":"IP address (URL parameter)"}],"body_params":[{"name":"public_key","type":"string","required":true,"description":"SSH public key identifier"},{"name":"permanent","type":"boolean","required":true,"description":"Whether the whitelist entry is permanent"},{"name":"expire_at","type":"integer","required":false,"description":"Unix timestamp for expiry (required when permanent is false)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_whitelist","description":"DESTRUCTIVE - Delete SSH whitelist entries. A text entry that is not a valid IP address or CIDR range is IGNORED rather than rejected, and the call still answers 200 with success true, so read the message rather than the status: it states how many removals were REQUESTED, not how many entries were actually deleted, and it names any entries it ignored. The node call returns nothing, so an entry that was not in the whitelist is counted the same as one that was. Read the whitelist back to confirm. A count of 0 means nothing was acted on. An entry that is not text is refused with 422 naming the field, so a mixed batch is rejected whole rather than partly ignored.","method":"DELETE","path_template":"/servers/websites/{website}/ssh/whitelist","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"public_key","type":"string","required":true,"description":"SSH public key identifier"},{"name":"ids","type":"array","required":true,"description":"Array of IP addresses to remove. Maximum 500 per call, because each one is applied on the server individually."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}}]},{"name":"websites_ssl","scope":"websites:ssl","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"get_ssl","description":"Get SSL details for a website including per-domain certificate status. Each entry in data.domains carries: name (the website address - the key is name, not domain), status, type, certificate_source, regenerate_pending, regenerate_pending_source, regenerate_pending_operator_note, ssl_folder_unreadable, certificates (fullchain and crt metadata), docroot, pointer_directory, last_issued_at, last_attempt_at, last_error, last_error_severity and last_error_code. regenerate_pending_operator_note is always null on this API. It carries a short warning that only the manager panel shows its own staff, and it is listed here so the key is not a surprise. Read regenerate_pending_source instead, which says the same thing and is always present. Read certificate_source BEFORE calling regenerate. It is one of own (this address has its own certificate), shared (the address is already covered by the platform's shared certificate and needs nothing, so regenerating it spends the allowance every free site shares), default (the server's stand-in certificate, which does not match the address, so visitors see a warning), or null when the server has not reported it yet. regenerate_pending is true when a new certificate has already been REQUESTED, and false otherwise. It is a record that the request exists, not a promise about timing: a request waits behind the ten-minute per-address brake, the hour-long brake on an address behind a proxy, and the six-hour allowance a platform apex shares, and it stays true through all of them by design. Do not tell anyone a certificate is arriving shortly on the strength of this field. It never comes back null. It is separate from status on purpose, because an address can have working HTTPS and a queued regeneration at the same time. Beside it, regenerate_pending_source is a sentence naming where that flag comes from, and it is worth reading once: on a server that has not yet taken the current SSL tooling the flag is read partly from a file inside the customer's own account, which this panel cannot verify. So do not decline a regeneration on the strength of regenerate_pending alone. That marker is temporary and goes away once every server carries the new tooling. last_error is one sentence chosen by this panel, never the server's raw text, and last_error_severity says how to read it: wait means a timer is running and the situation resolves by itself, action means the customer has something to do and nothing improves until they do it, failed means the last attempt failed and we retry automatically. Only action asks anything of the customer. Do not read failed as permanent, because the sentence beside it says we try again. ssl_folder_unreadable true means the ssl folder inside the customer's own website files is not a folder the panel can safely write into, so no certificate can be issued for that address until the customer renames it. Regenerating does not help and this is the one SSL failure that never resolves by waiting. last_error_code is the stable identifier for the SAME situation last_error describes in words, and it is what you branch on: several different codes all carry the severity wait, so severity alone cannot tell you WHICH wait it is. Use it to tell a rate-limited address (rate_limit_wait) from one waiting on the shared allowance (shared_allowance_wait) or on a web-server reload (nginx_reload_pending). A null in certificate_source, ssl_folder_unreadable or last_error_code means not known here, never no. A null last_error_severity means there is no message at all, which for a healthy address genuinely means no.","method":"GET","path_template":"/servers/websites/{website}/ssl","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data.summary":"object","data.domains":"array"}},{"name":"create_regenerate","description":"Regenerate SSL certificates for specified domains. Three refusals are normal answers rather than errors to retry past. A website whose account is still being set up is refused (account_not_ready) and nothing is queued, because a certificate cannot be requested until setup finishes. That one is not a wait you poll: the certificate is issued automatically when setup completes, so stop rather than retrying, and if it persists the account's setup has stalled and needs an operator. A domain carrying a custom-purchased certificate is refused (ssl_is_not_free), because regenerating would replace it with a free certificate and destroy the only copy of its private key. A domain on a shared platform address is refused while the certificate authority is rate-limiting it (ssl_cooldown_active), and the message names the addresses and roughly how many hours remain. That allowance is shared with every other free site, so the wait is the protection. THE CALL IS ONLY REFUSED WHEN EVERY ADDRESS YOU NAMED IS BRAKED. If you name several addresses and only some are braked, this call succeeds for the rest and answers 200 with no per-address detail, so a 200 does not mean every address was queued. After a successful call, re-read GET /servers/websites/{website}/ssl and check each row's last_error_code and last_error_severity to see which addresses are waiting. On the customer's own registered domain the same brake exists but regeneration is still allowed, because the only account a retry can affect is theirs. Read certificate_source from GET /servers/websites/{website}/ssl before calling: a value of shared means the address is already covered and needs nothing. You do not have to filter those out yourself any more. An address the shared platform certificate already covers is dropped from the request before it reaches the hosting server, and every address dropped that way is named in not_queued on the response, so you can tell what was acted on without diffing your request against a later read. TWO MORE ANSWERS EXIST AND NEITHER IS THE ORDINARY 200. If every address you named was already covered, nothing is sent to the hosting server at all: you get 200 with success true, every address you named listed in not_queued, and a message saying no regeneration was requested. There is no queued field on any answer this endpoint gives, so branch on not_queued. Do not read that as a queued order and do not poll after it. And if this panel cannot read the website's current certificate state, nothing is ordered and you get 503 with success false. That one is a real refusal rather than a result, so retry it shortly rather than treating it as a completed request. A 409 means the website's hosting account does not exist on its server, so there is nothing to request a certificate for. Do not retry a 409 on a timer. The account is created when the website finishes being set up or finishes moving to another server, so read GET /servers/websites/{website}/ssl and act on what it reports. A website whose account exists but has no domains attached is NOT this answer. That case comes back as the 503 above, because the hosting server reports an unreadable configuration and a genuinely empty one the same way, and treating it as final would stop you retrying a fault that clears.","method":"POST","path_template":"/servers/websites/{website}/ssl/regenerate","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"domains","type":"array","required":true,"description":"Array of domain names to regenerate SSL for. Up to 100 addresses in one call. Each address must be sent exactly as GET /ssl reports it. An address whose meaning would change on the way through is refused with 422 rather than silently rewritten: a dot or an underscore at the start or the end, a wildcard star, or an inner space. An underscore INSIDE the name is accepted, because the hosting server leaves it exactly as sent. Surrounding whitespace is accepted and trimmed. Letter case does not matter."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","not_queued":"array of strings. The addresses dropped from the request because the shared platform certificate already covers them. Always present, and empty when nothing was dropped. This is not the same as a braked address: a braked one WAS sent and is reported through last_error_code on GET /ssl instead."}},{"name":"create_csr","description":"DESTRUCTIVE - Create a Certificate Signing Request (CSR) for one address on this website. DESTRUCTIVE: it REPLACES any certificate request already saved for that address, and the private key that goes with it. A certificate already ordered against the old request can no longer be installed, because installation matches the certificate against the key we hold. Ask before calling it on an address that already has a request.","method":"POST","path_template":"/servers/websites/{website}/ssl/csr","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"domain","type":"string","required":true,"description":"One address this website serves - its own domain or one of its pointers. An address the website does not serve is refused with 422, because the request and its private key are written under that name in the account's own certificate folder."},{"name":"algo","type":"string","required":true,"description":"Signing algorithm. One of: RSA, ECDSA, EdDSA. Letter case does not matter, so rsa and RSA both work, and the panel stores the canonical spelling."},{"name":"bits","type":"string","required":true,"description":"Key size, and it must match the algorithm. RSA: 2048, 3072 or 4096. ECDSA: ec-256, ec-384 or ec-521. EdDSA: ed25519. Letter case does not matter here either, so EC-256 and ec-256 both work. A size belonging to a different algorithm is refused with 422 - the pair is checked, not each field on its own. GET /servers/{server}/ssl-algos returns the valid pairs for a server as a map of algorithm to sizes."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"object"}},{"name":"update_ssl","description":"DESTRUCTIVE - Install a custom SSL certificate for specified domains. A 200 means the certificate is installed and is being served. It does not mean everything was recorded. After a 200, re-read GET /servers/websites/{website}/ssl and check last_error_severity on every address you sent. A severity of action with last_error_code ssl_personal_marker_refused means we could not record that this address now uses a purchased certificate, so an automatic free certificate can replace it at the next renewal. The website owner clears whatever is sitting in their ssl folder and uploads the certificate again. This endpoint answers success and message only. It returns no per-address data, so read GET /servers/websites/{website}/ssl for the per-address result. If any address you named fails the checks before installing, nothing is installed and you get an error rather than a 200. If an address fails during the install itself, you also get an error, and any address installed before that point keeps its new certificate. In that case re-read GET /servers/websites/{website}/ssl to see which addresses ended up with it.","method":"PUT","path_template":"/servers/websites/{website}/ssl","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"domains","type":"array","required":true,"description":"Array of domain names to install onto. Each one must already have its own saved certificate request, created by POST /servers/websites/{website}/ssl/csr, and the certificate must match the private key saved with that request. A certificate whose SAN list covers several addresses does not install onto all of them, because each address holds a different private key. Nothing is changed when any named address fails a check. Up to 100 addresses in one call. Each address must be sent exactly as GET /ssl reports it. An address whose meaning would change on the way through is refused with 422 rather than silently rewritten: a dot or an underscore at the start or the end, a wildcard star, or an inner space. An underscore INSIDE the name is accepted, because the hosting server leaves it exactly as sent. Surrounding whitespace is accepted and trimmed. Letter case does not matter."},{"name":"crt","type":"string","required":true,"description":"Certificate content (PEM format), up to 65535 characters. Longer text is refused with 422 rather than reaching the server. WHILE THE HOSTING SERVERS ARE BEING UPDATED, some still accept only about 10000 characters for this field and refuse a longer value themselves, with a short technical message naming an argument rather than this field. That refusal is a limit on that server and not a malformed request."},{"name":"fullchain","type":"string","required":true,"description":"Full certificate chain content (PEM format), up to 65535 characters. Longer text is refused with 422 rather than reaching the server. WHILE THE HOSTING SERVERS ARE BEING UPDATED, some still accept only about 10000 characters for this field and refuse a longer value themselves, with a short technical message naming an argument rather than this field. A leaf plus two intermediates plus a cross-signed root can reach that size, so this is worth expecting on a real chain. The chain is NOT checked for completeness: a value holding only the leaf certificate is accepted and installed, and then fails on clients that do not already hold the issuer. Send the site certificate followed by every intermediate above it."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_ssl","description":"DESTRUCTIVE - Delete SSL certificates for specified domains.","method":"DELETE","path_template":"/servers/websites/{website}/ssl","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"domains","type":"array","required":true,"description":"Array of domain names to delete SSL for. Up to 100 addresses in one call. Each address must be sent exactly as GET /ssl reports it. An address whose meaning would change on the way through is refused with 422 rather than silently rewritten: a dot or an underscore at the start or the end, a wildcard star, or an inner space. An underscore INSIDE the name is accepted, because the hosting server leaves it exactly as sent. Surrounding whitespace is accepted and trimmed. Letter case does not matter."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}}]},{"name":"websites_staging","scope":"websites:staging","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"get_staging","description":"Get a paginated list of staging websites for a parent website.","method":"GET","path_template":"/servers/websites/{website}/staging","path_params":[{"name":"website","type":"integer","required":true,"description":"Parent website ID (URL parameter)"}],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page (max 200)"},{"name":"search","type":"string","required":false,"description":"Search term"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of StageResource","meta.current_page":"integer","meta.per_page":"integer","meta.total":"integer"}},{"name":"create_staging","description":"Create a new staging website. Optionally clone from the parent or install WordPress.","method":"POST","path_template":"/servers/websites/{website}/staging","path_params":[{"name":"website","type":"integer","required":true,"description":"Parent website ID (URL parameter)"}],"body_params":[{"name":"name","type":"string","required":true,"description":"Name for the staging website"},{"name":"free_subdomain","type":"boolean","required":true,"description":"Use a free auto-generated subdomain"},{"name":"domain","type":"string","required":false,"description":"Domain for the stage (required when free_subdomain is false)"},{"name":"clone","type":"boolean","required":true,"description":"Clone from the parent website"},{"name":"install_wordpress","type":"boolean","required":false,"description":"Install WordPress (when clone is false)"},{"name":"wp_name","type":"string","required":false,"description":"WordPress site name (when install_wordpress is true)"},{"name":"wp_email","type":"string","required":false,"description":"WordPress admin email (when install_wordpress is true)"},{"name":"wp_language","type":"string","required":false,"description":"WordPress locale (when install_wordpress is true)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data.id":"integer","data.domain":"string","data.name":"string","warnings":"array|null"}},{"name":"delete_staging","description":"DESTRUCTIVE - Delete a staging website.","method":"DELETE","path_template":"/servers/websites/{website}/staging/{stage}","path_params":[{"name":"website","type":"integer","required":true,"description":"Parent website ID (URL parameter)"},{"name":"stage","type":"integer","required":true,"description":"Stage website ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_clone","description":"Clone a staging website to a new stage under a target parent website.","method":"POST","path_template":"/servers/websites/{website}/staging/{stage}/clone","path_params":[{"name":"website","type":"integer","required":true,"description":"Parent website ID (URL parameter)"},{"name":"stage","type":"integer","required":true,"description":"Source stage ID (URL parameter)"}],"body_params":[{"name":"name","type":"string","required":true,"description":"Name for the cloned stage"},{"name":"free_subdomain","type":"boolean","required":true,"description":"Use a free auto-generated subdomain"},{"name":"domain","type":"string","required":false,"description":"Domain for the clone (required when free_subdomain is false)"},{"name":"target_website_id","type":"integer","required":true,"description":"Target parent website ID (body param, defaults to current parent)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","warnings":"array|null"}},{"name":"create_copy","description":"DESTRUCTIVE - Copy a stage to a production website.","method":"POST","path_template":"/servers/websites/{website}/staging/{stage}/copy","path_params":[{"name":"website","type":"integer","required":true,"description":"Parent website ID (URL parameter)"},{"name":"stage","type":"integer","required":true,"description":"Stage ID (URL parameter)"}],"body_params":[{"name":"target_website_id","type":"integer","required":false,"description":"Target production website ID (body param, defaults to parent)"},{"name":"maintenance","type":"boolean","required":false,"description":"Enable maintenance mode during copy (default: false)"},{"name":"copy_anything","type":"boolean","required":false,"description":"Copy all items (default: true)"},{"name":"copy_files","type":"boolean","required":false,"description":"Copy files (when copy_anything is false)"},{"name":"copy_databases","type":"boolean","required":false,"description":"Copy databases (when copy_anything is false)"},{"name":"copy_cronjobs","type":"boolean","required":false,"description":"Copy cronjobs (when copy_anything is false)"},{"name":"sync_php_version","type":"boolean","required":false,"description":"Sync PHP version (default: true)"},{"name":"preserve_production_data","type":"boolean","required":false,"description":"Preserve production WordPress data (default: false)"},{"name":"include_paths","type":"array","required":false,"description":"Specific file paths to include"},{"name":"databases_config","type":"string","required":false,"description":"JSON array selecting which databases and tables to copy. Each item is an object with a database (the database name) and tables (either the string all, or a list of specific table names). Leave empty to copy all databases and their tables of both engines. PostgreSQL databases are named through the reserved entry <username>__postgres: an item with database \"<username>__postgres\" and tables all copies every PostgreSQL database, and one whose tables list holds PostgreSQL database names (for example [\"shop\"]) copies just those - each is always copied whole. A selection that names no PostgreSQL database leaves the target's PostgreSQL databases untouched. Only used when copy_databases is true."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","warnings":"array|null"}},{"name":"create_swap","description":"DESTRUCTIVE - Swap a staging website with its production parent.","method":"POST","path_template":"/servers/websites/{website}/staging/{stage}/swap","path_params":[{"name":"website","type":"integer","required":true,"description":"Parent website ID (URL parameter)"},{"name":"stage","type":"integer","required":true,"description":"Stage ID (URL parameter)"}],"body_params":[{"name":"maintenance","type":"boolean","required":false,"description":"Enable maintenance mode during swap (default: false)"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","warnings":"array|null"}},{"name":"create_refresh","description":"DESTRUCTIVE - Refresh a staging website from its production parent.","method":"POST","path_template":"/servers/websites/{website}/staging/{stage}/refresh","path_params":[{"name":"website","type":"integer","required":true,"description":"Parent website ID (URL parameter)"},{"name":"stage","type":"integer","required":true,"description":"Stage ID (URL parameter)"}],"body_params":[{"name":"full_refresh","type":"boolean","required":false,"description":"Full refresh (default: true)"},{"name":"refresh_files","type":"boolean","required":false,"description":"Refresh files only (when full_refresh is false)"},{"name":"refresh_databases","type":"boolean","required":false,"description":"Refresh databases only (when full_refresh is false)"},{"name":"refresh_cronjobs","type":"boolean","required":false,"description":"Refresh cronjobs only (when full_refresh is false)"},{"name":"include_paths","type":"array","required":false,"description":"Specific file paths to include"},{"name":"databases_config","type":"string","required":false,"description":"JSON array selecting which databases and tables to refresh. Each item is an object with a database (the database name) and tables (either the string all, or a list of specific table names). Leave empty to refresh all databases and their tables of both engines. PostgreSQL databases are named through the reserved entry <username>__postgres: an item with database \"<username>__postgres\" and tables all refreshes every PostgreSQL database, and one whose tables list holds PostgreSQL database names (for example [\"shop\"]) refreshes just those - each is always refreshed whole. Only used when refresh_databases is true."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","warnings":"array|null"}},{"name":"create_migrate","description":"DESTRUCTIVE - Migrate a staging website to a different parent website.","method":"POST","path_template":"/servers/websites/{website}/staging/{stage}/migrate","path_params":[{"name":"website","type":"integer","required":true,"description":"Current parent website ID (URL parameter)"},{"name":"stage","type":"integer","required":true,"description":"Stage ID (URL parameter)"}],"body_params":[{"name":"target_website_id","type":"integer","required":true,"description":"Target parent website ID"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","warnings":"array|null"}},{"name":"create_move_to_production","description":"DESTRUCTIVE - Move a staging website to production (promotes it to a standalone website). If you send server_id to promote onto a different server, and your API key was limited to particular resources on either axis, that target has to be one the key covers, or the call is refused with 403. Promoting in place is not affected, and neither is a key its owner never limited to any resource.","method":"POST","path_template":"/servers/websites/{website}/staging/{stage}/move-to-production","path_params":[{"name":"website","type":"integer","required":true,"description":"Parent website ID (URL parameter)"},{"name":"stage","type":"integer","required":true,"description":"Stage ID (URL parameter)"}],"body_params":[{"name":"server_id","type":"integer","required":false,"description":"Target server ID (defaults to same server)"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","warnings":"array|null"}}]},{"name":"websites_waf","scope":"websites:waf","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"get_status","description":"Get WAF (Web Application Firewall) status and settings.","method":"GET","path_template":"/servers/websites/waf/status/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":{"enable":"boolean","xmlrpc":"string","blocked_proxies_level":"string"}}},{"name":"get_blocks_statistics","description":"Get WAF block statistics over time, including request breakdowns by clean vs blocked traffic.","method":"GET","path_template":"/servers/websites/waf/blocks-statistics/{website}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"timeframe","type":"string|integer","required":false,"description":"Time period: 'today', 'this_month', or integer 1-30 for number of days (defaults to 'today')"},{"name":"tz","type":"string","required":false,"description":"Timezone for bucketing (e.g., 'America/New_York'). Defaults to server cluster timezone"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":{"total":"object","total.requests":"integer","total.clean":"integer","total.blocked":"integer","timestamps":"object","timestamps.requests":"object","timestamps.clean":"object","timestamps.blocked":"object","blocked":"object","blocked.ips":"object","blocked.countries":"object","countries":"object"}}},{"name":"list_settings","description":"Get WAF settings: the safe-path bypass toggles, log-only mode, XML-RPC handling and the proxy-blocking level.","method":"GET","path_template":"/servers/websites/{website}/waf/settings","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":{"enable":"boolean","xmlrpc":"string (all|block|jetpack)","log_only":"boolean","bypass_known_uri":"boolean","bypass_known_query_strings":"boolean","blocked_proxies_level":"string (off|low|medium|high)"}}},{"name":"update_settings","description":"Update the WAF safe-path bypass toggles. The two bypass toggles are required on every save. The settings GET also returns xmlrpc and blocked_proxies_level, but this endpoint does not change them: xmlrpc is set with PUT /servers/websites/{website}/waf/xmlrpc, blocked_proxies_level with PUT /servers/websites/{website}/waf/proxy-blocking, and any value for them in this body is ignored. Saving settings always turns the WAF on (whatever enable the GET returned) and regenerates its protection rules. A save that turns the WAF on also turns OWASP inspection back on when owasp_enabled was off. A save on an already enabled website keeps owasp_enabled as it is. To turn the WAF off, use the WAF mode endpoint. The former whitelist_ip_ranges, exclude_ip_ranges and whitelist_all_ip_ranges fields were removed on 2026-09-06 because no server code ever read them. Sending them has no effect.","method":"PUT","path_template":"/servers/websites/{website}/waf/settings","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"bypass_known_uri","type":"boolean","required":true,"description":"Bypass the WAF for known-good URIs. Required - send both toggles on every save (pass the current value to keep it)."},{"name":"bypass_known_query_strings","type":"boolean","required":true,"description":"Bypass the WAF for known-good query strings. Required - send both toggles on every save (pass the current value to keep it)."},{"name":"log_only","type":"boolean","required":false,"description":"Enable log-only mode"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_page","description":"Get WAF blocked page HTML.","method":"GET","path_template":"/servers/websites/{website}/waf/page","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"string (HTML content)"}},{"name":"update_page","description":"Set custom WAF blocked page HTML.","method":"PUT","path_template":"/servers/websites/{website}/waf/page","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"code","type":"string","required":true,"description":"HTML content for the WAF page"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_page","description":"DESTRUCTIVE - Restore WAF blocked page to default.","method":"DELETE","path_template":"/servers/websites/{website}/waf/page","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"list_modsecurity_rules","description":"Get available OWASP rules list.","method":"GET","path_template":"/servers/websites/{website}/waf/modsecurity-rules","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of rule objects"}},{"name":"update_toggle","description":"Turn one OWASP rule on or off for this website. This is an atomic single-rule toggle: it changes only the rule you name and leaves every other OWASP setting untouched, so you do not read and rewrite the whole disabled_rules list. Use it to silence one rule that misfires on legitimate traffic (for example OWASP CRS 920450 on WooCommerce checkout) while keeping all other OWASP protection on.","method":"PUT","path_template":"/servers/websites/{website}/waf/modsecurity-rules/toggle","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"rule_id","type":"string","required":true,"description":"The OWASP rule ID to turn on or off, a 3-7 digit numeric string (e.g. \"942100\")."},{"name":"enabled","type":"boolean","required":true,"description":"Set false to turn the rule off (disable it) or true to turn it back on (enable it)."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"list_modsecurity_settings","description":"Get OWASP settings including the enabled state, sensitivity, and disabled rules.","method":"GET","path_template":"/servers/websites/{website}/waf/modsecurity-settings","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":{"owasp_enabled":"boolean","sensitivity":"string","disabled_categories":"array","disabled_rules":"array","custom_rules":"array"}}},{"name":"update_modsecurity_settings","description":"Update OWASP settings. owasp_enabled false is kept across WAF settings saves and category_overrides saves on an enabled website. A preset WAF mode save (balanced or strict) always turns it back on. A WAF settings save, or a mode save with category_overrides, turns it back on only when it turns the WAF on.","method":"PUT","path_template":"/servers/websites/{website}/waf/modsecurity-settings","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"owasp_enabled","type":"boolean","required":false,"description":"Enable OWASP rules"},{"name":"sensitivity","type":"string","required":false,"description":"OWASP CRS paranoia / sensitivity level. One of: off, low, medium, high, paranoid."},{"name":"disabled_categories","type":"array","required":false,"description":"Array of OWASP category names to disable. Each must be one of: Method Enforcement, Scanner Detection, Protocol Enforcement, Protocol Attack, Multipart Attack, LFI, RFI, RCE, PHP Injection, Generic Attack, XSS, SQLi, Session Fixation, Java Attack, Blocking Evaluation, Data Leakages, Data Leakages SQL, Data Leakages Java, Data Leakages PHP, Data Leakages IIS, Web Shells, Anomaly Scoring."},{"name":"disabled_rules","type":"array","required":false,"description":"Array of OWASP rule IDs to disable, each a 3-7 digit numeric string (e.g. \"942100\")."},{"name":"custom_rules","type":"array","required":false,"description":"Array of custom OWASP exclusion / whitelist rules (max 50). Each item is an object: {type (required, one of: exclude_uri, exclude_uri_rule, exclude_param, exclude_cookie, exclude_header, exclude_body, exclude_param_by_tag, exclude_cookie_by_tag, exclude_header_by_tag, exclude_body_by_tag, allow_content_type, whitelist_ip), enabled (required boolean)}, plus the fields the chosen type needs: uri (path), rule_id (1-7 digit numeric string), param, cookie, header, tag (one of: attack-sqli, attack-xss, attack-rfi, attack-lfi, attack-rce, attack-generic, attack-php, attack-java, attack-session-fixation), content_type, ip."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"update_xmlrpc","description":"Update XML-RPC blocking setting.","method":"PUT","path_template":"/servers/websites/{website}/waf/xmlrpc","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"xmlrpc","type":"string","required":true,"description":"XML-RPC handling. One of: all (allow all XML-RPC), block (block all XML-RPC), jetpack (allow only Jetpack / WordPress.com)."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"update_proxy_blocking","description":"Update proxy blocking level.","method":"PUT","path_template":"/servers/websites/{website}/waf/proxy-blocking","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"blocked_proxies_level","type":"string","required":true,"description":"Proxy / VPN blocking level. One of: off, low, medium, high."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"update_log_only","description":"Toggle WAF log-only mode.","method":"PUT","path_template":"/servers/websites/{website}/waf/log-only","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"log_only","type":"boolean","required":true,"description":"Enable log-only mode"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"update_mode","description":"Set WAF mode (off, balanced, strict). Sending category_overrides with any mode other than off turns the WAF on for the website and regenerates its protection rules from the overrides merged over the site's current settings (categories you omit keep their current value) instead of applying the mode preset. A preset mode (balanced or strict) always turns OWASP inspection back on when owasp_enabled was off, whether or not the WAF was already on. category_overrides turn it back on only on a website whose WAF was off. On an already enabled website they keep owasp_enabled as it is. Sending mode off always turns the WAF fully off.","method":"PUT","path_template":"/servers/websites/{website}/waf/mode","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"mode","type":"string","required":true,"description":"WAF mode: off, balanced, strict"},{"name":"category_overrides","type":"object","required":false,"description":"Per-category on/off overrides (boolean values). Any category you omit keeps its current value."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}}]},{"name":"websites_wordpress","scope":"websites:wordpress","description":"Manage your websites including domains, DNS records, databases, PHP settings, mail forwarders, cache, DDoS/WAF protection, and resource monitoring.","actions":[{"name":"list_stats","description":"Get WordPress statistics.","method":"GET","path_template":"/servers/websites/{website}/wordpress/stats","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - WordPress statistics (plugins count, themes count, users count, posts count, pages count, comments count, etc.)"}},{"name":"list_roles","description":"Get WordPress user roles.","method":"GET","path_template":"/servers/websites/{website}/wordpress/roles","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of role objects"}},{"name":"get_version","description":"Get WordPress version info.","method":"GET","path_template":"/servers/websites/{website}/wordpress/version","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - current WordPress version, latest available version, and update availability"}},{"name":"list_optimizer_settings","description":"Get WordPress optimizer settings.","method":"GET","path_template":"/servers/websites/{website}/wordpress/optimizer-settings","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - WordPress optimizer settings (key-value pairs of optimization toggles and configuration)"}},{"name":"update_optimizer_settings","description":"Update WordPress optimizer settings.","method":"PUT","path_template":"/servers/websites/{website}/wordpress/optimizer-settings","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"settings","type":"object","required":true,"description":"Settings key-value pairs to update"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"update_wordpress_core","description":"Update WordPress core to the latest version.","method":"POST","path_template":"/servers/websites/{website}/wordpress/update-core","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"backup","type":"boolean","required":false,"description":"Create backup before update"},{"name":"backup_name","type":"string","required":false,"description":"Backup name"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"update_auto_update","description":"Set WordPress auto-update preference.","method":"PUT","path_template":"/servers/websites/{website}/wordpress/auto-update","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"auto_update","type":"string","required":true,"description":"Auto-update setting. One of: off, minor, major, on."}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_drafts","description":"DESTRUCTIVE - Clear all WordPress drafts.","method":"DELETE","path_template":"/servers/websites/{website}/wordpress/drafts","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_revisions","description":"DESTRUCTIVE - Clear all WordPress revisions.","method":"DELETE","path_template":"/servers/websites/{website}/wordpress/revisions","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"list_admins","description":"Get WordPress admin users.","method":"GET","path_template":"/servers/websites/{website}/wordpress/admins","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of admin user objects"}},{"name":"list_post_types","description":"Get WordPress post types.","method":"GET","path_template":"/servers/websites/{website}/wordpress/post-types","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of post type objects"}},{"name":"create_profiling","description":"Start WordPress profiling.","method":"POST","path_template":"/servers/websites/{website}/wordpress/profiling","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"path","type":"string","required":false,"description":"Path to profile"},{"name":"user_id","type":"integer","required":false,"description":"WordPress user ID for profiling"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - WordPress profiling results (timing breakdowns, slow queries, plugin performance metrics)"}},{"name":"list_check_issues","description":"Check WordPress for issues.","method":"GET","path_template":"/servers/websites/{website}/wordpress/check-issues","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - WordPress issue check results (security issues, outdated components, configuration problems)"}},{"name":"get_db_connection","description":"Test whether the site's own wp-config database credentials actually authenticate (a fresh live database login using the site's own DB host, user, and password). Proves the credentials are correct or returns the real failure reason. Unlike listing databases or users, this verifies the site's actual login rather than only that the database exists.","method":"GET","path_template":"/servers/websites/{website}/wordpress/db-connection","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - { connected: boolean, summary: string, error: string present only when the connection failed, carrying the database error text }"}},{"name":"reset_wordpress","description":"DESTRUCTIVE - Reset selected WordPress components (core, plugins, theme).","method":"POST","path_template":"/servers/websites/{website}/wordpress/reset","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"types","type":"array","required":true,"description":"Reset types. Each item one of: core, plugins, theme."},{"name":"backup","type":"boolean","required":true,"description":"Create backup before reset"},{"name":"backup_name","type":"string","required":false,"description":"Backup name"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string","data":"array of reset results"}},{"name":"install_wordpress","description":"DESTRUCTIVE - Install WordPress on the website. This overwrites any existing site at this path.","method":"POST","path_template":"/servers/websites/{website}/wordpress/install","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"name","type":"string","required":true,"description":"Site title"},{"name":"email","type":"string","required":true,"description":"Admin email"},{"name":"language","type":"string","required":true,"description":"WordPress locale"},{"name":"backup","type":"boolean","required":true,"description":"Create backup before install"},{"name":"backup_name","type":"string","required":false,"description":"Backup name"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"login_as","description":"DESTRUCTIVE - Get magic login URL for a WordPress user.","method":"POST","path_template":"/servers/websites/{website}/wordpress/users/login-as","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"user_id","type":"integer","required":true,"description":"WordPress user ID"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - one-time WordPress admin login URL"}},{"name":"list_users","description":"Get WordPress users list with pagination.","method":"GET","path_template":"/servers/websites/{website}/wordpress/users","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page (max 200)"},{"name":"search","type":"string","required":false,"description":"Search filter"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of user objects","meta":"pagination object"}},{"name":"list_invisible_admins","description":"List WordPress 'invisible admin' accounts - admin-level users that exist in the database but do not show up in the standard wp-admin user list. Hiding an admin account is a known malware persistence trick, so treat every row as suspicious until the site owner confirms it. Proginter does not create accounts of its own here: panel wp-admin access goes through a one-time SSO file for an existing user, so nothing on this list is ours. The WP `ID` key is renamed to `id` for API consistency.","method":"GET","path_template":"/servers/websites/{website}/wordpress/users/invisible-admins","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of user objects (same shape as /wordpress/users items)","meta":"object","meta.total":"integer"}},{"name":"create_users","description":"Create a WordPress user.","method":"POST","path_template":"/servers/websites/{website}/wordpress/users","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"display_name","type":"string","required":true,"description":"Display name"},{"name":"user_login","type":"string","required":true,"description":"Username"},{"name":"user_email","type":"string","required":true,"description":"Email address"},{"name":"user_pass","type":"string","required":true,"description":"Password"},{"name":"role","type":"string","required":true,"description":"User role"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"update_users","description":"Update a WordPress user.","method":"PUT","path_template":"/servers/websites/{website}/wordpress/users/{user}","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"user","type":"integer","required":true,"description":"WordPress user ID (URL parameter)"}],"body_params":[{"name":"display_name","type":"string","required":false,"description":"Display name"},{"name":"user_email","type":"string","required":false,"description":"Email address"},{"name":"role","type":"string","required":false,"description":"User role"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"update_password","description":"Change a WordPress user's password.","method":"PUT","path_template":"/servers/websites/{website}/wordpress/users/{user}/password","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"},{"name":"user","type":"integer","required":true,"description":"WordPress user ID (URL parameter)"}],"body_params":[{"name":"user_pass","type":"string","required":true,"description":"New password"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"reset_admin_passwords","description":"DESTRUCTIVE - Reset all admin passwords.","method":"POST","path_template":"/servers/websites/{website}/wordpress/users/reset-admin-passwords","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"send_email","type":"boolean","required":false,"description":"Send email notification to admins"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_users","description":"DESTRUCTIVE - Delete WordPress users. By default each removed user's content (posts, pages, media) is reassigned to an administrator. Set delete_content to true to permanently delete that content instead. Each user is deleted independently and the response reports a per-user result.","method":"DELETE","path_template":"/servers/websites/{website}/wordpress/users","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"ids","type":"array","required":true,"description":"Array of WordPress user IDs to delete. Maximum 500 per call, because each one is removed on the server individually."},{"name":"delete_content","type":"boolean","required":false,"description":"When true, the removed users' content (posts, pages, media) is permanently deleted. When false or omitted (the default), their content is reassigned to an administrator instead."},{"name":"reassign_to","type":"integer","required":false,"description":"Administrator user ID to receive the removed users' content on the reassign path. Must be an existing administrator and not one of the users being deleted. When omitted (and delete_content is false), the site's main administrator (lowest-id admin, excluding the deleted users) is used automatically."}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean (true only when every requested user was deleted)","message":"string","deleted_count":"integer","failed_count":"integer","results":"array of { id: integer, status: 'deleted' | 'failed', message: string (present on failures) } - one entry per requested user. HTTP status is 200 when all succeed and 207 (Multi-Status) when any fail."}},{"name":"list_plugins","description":"Get WordPress plugins list with pagination.","method":"GET","path_template":"/servers/websites/{website}/wordpress/plugins","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of plugin objects","meta":"pagination object"}},{"name":"get_invisible","description":"List WordPress 'invisible' plugins - plugins present in the site's plugins folder that do not show up on the WordPress plugins screen. Hiding a plugin is a known malware persistence trick, so treat every row as suspicious until the site owner confirms it. These are not system plugins, and nothing Proginter installs can appear here: the check reads the site's own plugins folder only, never must-use plugins or drop-ins.","method":"GET","path_template":"/servers/websites/{website}/wordpress/plugins/invisible","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of plugin objects"}},{"name":"create_upload","description":"Upload and install a plugin from a zip file (multipart/form-data). Note: this endpoint takes a multipart file upload (plugins[]) that cannot be sent over MCP - run the upload via the panel or a direct External API multipart call.","method":"POST","path_template":"/servers/websites/{website}/wordpress/plugins/upload","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"activate","type":"boolean","required":true,"description":"Activate plugin after install (pass false to install without activating)"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_install","description":"Install a plugin from the WordPress repository.","method":"POST","path_template":"/servers/websites/{website}/wordpress/plugins/install","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"id","type":"string","required":true,"description":"Plugin slug from the WordPress.org repository. URL-based installation is rejected - to install from a ZIP file use the plugins/upload endpoint (multipart) instead"},{"name":"activate","type":"boolean","required":false,"description":"Activate plugin after install"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_update","description":"Update plugins to their latest versions.","method":"POST","path_template":"/servers/websites/{website}/wordpress/plugins/update","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"plugins","type":"array","required":false,"description":"Array of plugin_file IDs to update (all if empty)"},{"name":"backup","type":"boolean","required":true,"description":"Create backup before update"},{"name":"backup_name","type":"string","required":false,"description":"Backup name"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of update result objects"}},{"name":"create_enable","description":"Enable (activate) plugins.","method":"POST","path_template":"/servers/websites/{website}/wordpress/plugins/enable","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"ids","type":"array","required":true,"description":"Array of plugin_file IDs to enable"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"enable_all","description":"Enable all plugins.","method":"POST","path_template":"/servers/websites/{website}/wordpress/plugins/enable-all","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_disable","description":"Disable (deactivate) plugins.","method":"POST","path_template":"/servers/websites/{website}/wordpress/plugins/disable","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"ids","type":"array","required":true,"description":"Array of plugin_file IDs to disable"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"disable_all","description":"DESTRUCTIVE - Disable all plugins.","method":"POST","path_template":"/servers/websites/{website}/wordpress/plugins/disable-all","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_auto_update","description":"Set auto-update for a specific plugin.","method":"POST","path_template":"/servers/websites/{website}/wordpress/plugins/auto-update","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"id","type":"string","required":true,"description":"Plugin file ID"},{"name":"auto_update","type":"boolean","required":true,"description":"Enable or disable auto-update"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_auto_update_all","description":"Set auto-update for all plugins.","method":"POST","path_template":"/servers/websites/{website}/wordpress/plugins/auto-update-all","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"auto_update","type":"boolean","required":true,"description":"Enable or disable auto-update for all"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_plugins","description":"DESTRUCTIVE - Delete WordPress plugins.","method":"DELETE","path_template":"/servers/websites/{website}/wordpress/plugins","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"ids","type":"array","required":true,"description":"Array of plugin_file IDs to delete"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"list_themes","description":"Get WordPress themes list with pagination.","method":"GET","path_template":"/servers/websites/{website}/wordpress/themes","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"page","type":"integer","required":false,"description":"Page number"},{"name":"per_page","type":"integer","required":false,"description":"Items per page"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of theme objects","meta":"pagination object"}},{"name":"themes_create_upload","description":"Upload and install a theme from a zip file (multipart/form-data). Note: this endpoint takes a multipart file upload (themes[]) that cannot be sent over MCP - run the upload via the panel or a direct External API multipart call.","method":"POST","path_template":"/servers/websites/{website}/wordpress/themes/upload","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"activate","type":"boolean","required":false,"description":"Activate theme after install"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"themes_create_install","description":"Install a theme from the WordPress repository.","method":"POST","path_template":"/servers/websites/{website}/wordpress/themes/install","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"id","type":"string","required":true,"description":"Theme slug from the WordPress.org repository. URL-based installation is rejected - to install from a ZIP file use the themes/upload endpoint (multipart) instead"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"themes_create_update","description":"Update themes to their latest versions.","method":"POST","path_template":"/servers/websites/{website}/wordpress/themes/update","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"themes","type":"array","required":false,"description":"Array of theme slugs to update (all if empty)"},{"name":"backup","type":"boolean","required":true,"description":"Create backup before update"},{"name":"backup_name","type":"string","required":false,"description":"Backup name"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"array of update result objects"}},{"name":"themes_create_auto_update","description":"Set auto-update for a specific theme.","method":"POST","path_template":"/servers/websites/{website}/wordpress/themes/auto-update","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"id","type":"string","required":true,"description":"Theme slug"},{"name":"auto_update","type":"boolean","required":true,"description":"Enable or disable auto-update"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"themes_create_auto_update_all","description":"Set auto-update for all themes.","method":"POST","path_template":"/servers/websites/{website}/wordpress/themes/auto-update-all","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"auto_update","type":"boolean","required":true,"description":"Enable or disable auto-update for all"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"delete_themes","description":"DESTRUCTIVE - Delete WordPress themes.","method":"DELETE","path_template":"/servers/websites/{website}/wordpress/themes","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"ids","type":"array","required":true,"description":"Array of theme slugs to delete"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_activate","description":"Activate a WordPress theme.","method":"POST","path_template":"/servers/websites/{website}/wordpress/themes/activate","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"id","type":"string","required":true,"description":"Theme slug to activate"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"create_child","description":"Create a child theme.","method":"POST","path_template":"/servers/websites/{website}/wordpress/themes/child","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"parent","type":"string","required":true,"description":"Parent theme slug"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"get_object_cache","description":"Get WordPress object cache status.","method":"GET","path_template":"/servers/websites/{website}/wordpress/object-cache","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - object cache status (enabled, type, status details)"}},{"name":"object_cache_create_enable","description":"Enable WordPress object cache.","method":"POST","path_template":"/servers/websites/{website}/wordpress/object-cache/enable","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"object_cache_create_disable","description":"Disable WordPress object cache.","method":"POST","path_template":"/servers/websites/{website}/wordpress/object-cache/disable","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"purge_object_cache","description":"DESTRUCTIVE - Purge (flush) WordPress object cache.","method":"POST","path_template":"/servers/websites/{website}/wordpress/object-cache/purge","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"list_analytics","description":"Object cache hit-rate and request analytics over a time window. Panel default is 'hour'.","method":"GET","path_template":"/servers/websites/{website}/wordpress/object-cache/analytics","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"period","type":"string","required":false,"description":"Aggregation window (default 'hour')"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object"}},{"name":"update_db_config","description":"Update WordPress database configuration (wp-config.php).","method":"PUT","path_template":"/servers/websites/{website}/wordpress/db-config","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"db_name","type":"string","required":false,"description":"Database name"},{"name":"db_user","type":"string","required":false,"description":"Database user"},{"name":"db_password","type":"string","required":false,"description":"Database password"},{"name":"host","type":"string","required":false,"description":"Database host"}],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}},{"name":"list_rucss","description":"Get RUCSS (Remove Unused CSS) status.","method":"GET","path_template":"/servers/websites/{website}/wordpress/rucss","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[],"requires_confirmation":false,"prerequisites":[],"response_structure":{"success":"boolean","data":"object - RUCSS status (enabled, optimization state)"}},{"name":"purge_rucss","description":"DESTRUCTIVE - Purge/regenerate RUCSS.","method":"POST","path_template":"/servers/websites/{website}/wordpress/rucss/purge","path_params":[{"name":"website","type":"integer","required":true,"description":"Website ID (URL parameter)"}],"body_params":[{"name":"page_types","type":"array","required":true,"description":"Page types to regenerate RUCSS for"}],"requires_confirmation":true,"prerequisites":[],"response_structure":{"success":"boolean","message":"string"}}]}]}