# JetHost MCP API — full reference Endpoint: https://mcp.jethost.com/mcp Transport: JSON-RPC 2.0 `tools/call` over streamable HTTP Auth: OAuth 2.1 bearer token (DCR + PKCE) Discovery: https://mcp.jethost.com/.well-known/oauth-protected-resource -> https://mcp.jethost.com/.well-known/oauth-authorization-server Registration (DCR): https://jethost.com/app/index.php?m=jethost_mcp_oauth&action=register Authorize: https://jethost.com/app/index.php?m=jethost_mcp_oauth&action=authorize | Token: https://jethost.com/app/index.php?m=jethost_mcp_oauth&action=token | JWKS: https://jethost.com/app/index.php?m=jethost_mcp_oauth&action=jwks # Advanced Token Manager Accounts with the opt-in Advanced Token Manager (Bulgarian: Разширен Токен Мениджър) refuse generic DCR clients with an `Advanced Token Manager enabled` error; access is granted through owner-created credentials instead. A credential connects two ways: OAuth with only its client_id (session-less mint, PKCE, no consent screen — the client_id is a capability secret) or an opaque 50-character `jh_pat_…` Bearer token (shown once, stored hashed, introspected server-side, fails closed). Grants are per account/service/tool/cPanel module, read live on every request, and expire with the credential (default 30 days, max 90). See https://mcp.jethost.com/docs/security.html. ## list_services Scope: `read:services` | Annotations: read-only, idempotent List the caller's active and suspended hosting services. Requires the read:services scope. Input schema: ```json { "type": "object", "properties": {} } ``` Output schema: ```json { "type": "object", "properties": { "services": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "integer" }, "domain": { "type": "string" }, "product": { "type": "string" }, "status": { "type": "string", "enum": [ "Active", "Suspended", "Terminated", "Pending", "Cancelled", "Fraud", "Completed" ] }, "service_kind": { "type": "string", "enum": [ "shared", "vps", "managed_vps" ], "description": "shared = shared hosting; vps / managed_vps = a VPS (use list_vps_operations)." }, "next_due_date": { "type": [ "string", "null" ] }, "recurring_amount": { "type": "object", "properties": { "value": { "type": "string" }, "currency": { "type": "string" } }, "required": [ "value", "currency" ] }, "first_payment_amount": { "type": "object", "properties": { "value": { "type": "string" }, "currency": { "type": "string" } }, "required": [ "value", "currency" ] } }, "required": [ "id", "domain", "product", "status", "service_kind", "next_due_date", "recurring_amount", "first_payment_amount" ] } } }, "required": [ "services" ] } ``` ## get_service Scope: `read:services` | Annotations: read-only, idempotent Get a single hosting service by ID, including status, billing cycle, disk/bandwidth usage, server, and default nameservers. Requires the read:services scope. Input schema: ```json { "type": "object", "properties": { "id": { "type": "integer" } }, "required": [ "id" ] } ``` Output schema: ```json { "type": "object", "properties": { "service": { "type": "object", "properties": { "id": { "type": "integer" }, "domain": { "type": "string" }, "product": { "type": "string" }, "status": { "type": "string", "enum": [ "Active", "Suspended", "Terminated", "Pending", "Cancelled", "Fraud", "Completed" ] }, "service_kind": { "type": "string", "enum": [ "shared", "vps", "managed_vps" ], "description": "shared = shared hosting; vps / managed_vps = a VPS (use list_vps_operations)." }, "registration_date": { "type": [ "string", "null" ] }, "next_due_date": { "type": [ "string", "null" ] }, "billing_cycle": { "type": "string" }, "recurring_amount": { "type": "object", "properties": { "value": { "type": "string" }, "currency": { "type": "string" } }, "required": [ "value", "currency" ] }, "first_payment_amount": { "type": "object", "properties": { "value": { "type": "string" }, "currency": { "type": "string" } }, "required": [ "value", "currency" ] }, "disk_usage_mb": { "type": "integer" }, "disk_limit_mb": { "type": [ "integer", "null" ] }, "bandwidth_usage_mb": { "type": "integer" }, "bandwidth_limit_mb": { "type": [ "integer", "null" ] }, "suspension_reason": { "type": [ "string", "null" ] }, "server_name": { "type": [ "string", "null" ] }, "dedicated_ip": { "type": [ "string", "null" ] }, "nameservers": { "type": "array", "items": { "type": "string" } } }, "required": [ "id", "domain", "product", "status", "service_kind", "registration_date", "next_due_date", "billing_cycle", "recurring_amount", "first_payment_amount", "suspension_reason", "dedicated_ip" ] }, "error": { "type": "string" } } } ``` Errors: `not_found` ## get_service_usage Scope: `read:services` | Annotations: read-only, idempotent Get CPU usage for one of the caller's hosting services by ID: current, today, and month-to-date CPU-minutes (UTC). Pass month=YYYY-MM to also get a per-day series. Data may be up to ~60s stale. Requires the read:services scope. Input schema: ```json { "type": "object", "properties": { "service_id": { "type": "integer", "description": "The hosting service ID." }, "month": { "type": [ "null", "string" ], "description": "Optional 'YYYY-MM' to include a per-day CPU series.", "default": null } }, "required": [ "service_id" ] } ``` Output schema: ```json { "type": "object", "properties": { "usage": { "type": "object", "properties": { "service_id": { "type": "integer" }, "has_data": { "type": "boolean" }, "as_of_utc": { "type": [ "string", "null" ] }, "cpu_minutes_current": { "type": [ "number", "null" ] }, "cpu_minutes_today": { "type": [ "number", "null" ] }, "cpu_minutes_month": { "type": [ "number", "null" ] }, "month": { "type": "string" }, "series": { "type": "array", "items": { "type": "object", "properties": { "day": { "type": "integer" }, "minutes": { "type": "number" } }, "required": [ "day", "minutes" ] } } }, "required": [ "service_id", "has_data", "as_of_utc", "cpu_minutes_current", "cpu_minutes_today", "cpu_minutes_month" ] }, "error": { "type": "string" } } } ``` Errors: `invalid_month`, `not_found` ## list_invoices Scope: `read:invoices` | Annotations: read-only, idempotent List the caller's invoices, optionally filtered by status (Unpaid, Paid, Cancelled, Refunded, Collections, Draft, Payment Pending, Overdue). Requires the read:invoices scope. Input schema: ```json { "type": "object", "properties": { "status": { "type": "string", "default": "" } } } ``` Output schema: ```json { "type": "object", "properties": { "invoices": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "integer" }, "invoice_number": { "type": "string" }, "status": { "type": "string", "enum": [ "Unpaid", "Paid", "Cancelled", "Refunded", "Collections", "Draft", "Payment Pending", "Overdue" ] }, "total": { "type": "object", "properties": { "value": { "type": "string" }, "currency": { "type": "string" } }, "required": [ "value", "currency" ] }, "due_date": { "type": [ "string", "null" ] }, "date": { "type": [ "string", "null" ] } }, "required": [ "id", "invoice_number", "status", "total", "due_date", "date" ] } } }, "required": [ "invoices" ] } ``` ## get_invoice Scope: `read:invoices` | Annotations: read-only, idempotent Get a single invoice by ID, including line items. Requires the read:invoices scope. Input schema: ```json { "type": "object", "properties": { "id": { "type": "integer" } }, "required": [ "id" ] } ``` Output schema: ```json { "type": "object", "properties": { "invoice": { "type": "object", "properties": { "id": { "type": "integer" }, "invoice_number": { "type": "string" }, "status": { "type": "string", "enum": [ "Unpaid", "Paid", "Cancelled", "Refunded", "Collections", "Draft", "Payment Pending", "Overdue" ] }, "total": { "type": "object", "properties": { "value": { "type": "string" }, "currency": { "type": "string" } }, "required": [ "value", "currency" ] }, "due_date": { "type": [ "string", "null" ] }, "date": { "type": [ "string", "null" ] }, "line_items": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "integer" }, "description": { "type": "string" }, "amount": { "type": "object", "properties": { "value": { "type": "string" }, "currency": { "type": "string" } }, "required": [ "value", "currency" ] }, "type": { "type": "string" } }, "required": [ "id", "description", "amount", "type" ] } } }, "required": [ "id", "invoice_number", "status", "total", "due_date", "date", "line_items" ] }, "error": { "type": "string" } } } ``` Errors: `not_found` ## search_invoices Scope: `read:invoices` | Annotations: read-only, idempotent Search the caller's invoices for line items whose description matches a substring (case-insensitive). Returns up to 50 matching invoices, newest first. Requires the read:invoices scope. Input schema: ```json { "type": "object", "properties": { "query": { "type": "string", "description": "Substring to match against item descriptions." } }, "required": [ "query" ] } ``` Output schema: ```json { "type": "object", "properties": { "invoices": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "integer" }, "invoice_number": { "type": "string" }, "status": { "type": "string", "enum": [ "Unpaid", "Paid", "Cancelled", "Refunded", "Collections", "Draft", "Payment Pending", "Overdue" ] }, "total": { "type": "object", "properties": { "value": { "type": "string" }, "currency": { "type": "string" } }, "required": [ "value", "currency" ] }, "due_date": { "type": [ "string", "null" ] }, "date": { "type": [ "string", "null" ] } }, "required": [ "id", "invoice_number", "status", "total", "due_date", "date" ] } }, "truncated": { "type": "boolean" }, "limit": { "type": "integer" }, "error": { "type": "string" } } } ``` Errors: `query_too_short` ## find_invoices_by_relation Scope: `read:invoices` | Annotations: read-only, idempotent Find the caller's invoices that include a charge for a specific hosting service (rel_type=Hosting) or domain (rel_type=Domain). rel_id is the integer service id or domain id. Returns up to 50 matching invoices, newest first. Requires the read:invoices scope. Input schema: ```json { "type": "object", "properties": { "rel_type": { "type": "string", "description": "Must be exactly \"Hosting\" or \"Domain\"." }, "rel_id": { "type": "integer", "description": "Hosting id (for Hosting) or domain id (for Domain)." } }, "required": [ "rel_type", "rel_id" ] } ``` Output schema: ```json { "type": "object", "properties": { "invoices": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "integer" }, "invoice_number": { "type": "string" }, "status": { "type": "string", "enum": [ "Unpaid", "Paid", "Cancelled", "Refunded", "Collections", "Draft", "Payment Pending", "Overdue" ] }, "total": { "type": "object", "properties": { "value": { "type": "string" }, "currency": { "type": "string" } }, "required": [ "value", "currency" ] }, "due_date": { "type": [ "string", "null" ] }, "date": { "type": [ "string", "null" ] } }, "required": [ "id", "invoice_number", "status", "total", "due_date", "date" ] } }, "truncated": { "type": "boolean" }, "limit": { "type": "integer" }, "error": { "type": "string" } } } ``` Errors: `invalid_rel_type`, `invalid_rel_id` ## list_domains Scope: `read:domains` | Annotations: read-only, idempotent List the caller's registered domains with expiry dates. Requires the read:domains scope. Input schema: ```json { "type": "object", "properties": {} } ``` Output schema: ```json { "type": "object", "properties": { "domains": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "integer" }, "domain_name": { "type": "string" }, "status": { "type": "string", "enum": [ "Active", "Expired", "Pending", "Pending Registration", "Pending Transfer", "Transferred Away", "Cancelled", "Grace", "Fraud" ] }, "expiry_date": { "type": [ "string", "null" ] }, "recurring_amount": { "type": "object", "properties": { "value": { "type": "string" }, "currency": { "type": "string" } }, "required": [ "value", "currency" ] }, "first_payment_amount": { "type": "object", "properties": { "value": { "type": "string" }, "currency": { "type": "string" } }, "required": [ "value", "currency" ] } }, "required": [ "id", "domain_name", "status", "expiry_date", "recurring_amount", "first_payment_amount" ] } } }, "required": [ "domains" ] } ``` ## get_domain Scope: `read:domains` | Annotations: read-only, idempotent Get a single domain by ID, including status and expiry date. Requires the read:domains scope. Input schema: ```json { "type": "object", "properties": { "id": { "type": "integer" } }, "required": [ "id" ] } ``` Output schema: ```json { "type": "object", "properties": { "domain": { "type": "object", "properties": { "id": { "type": "integer" }, "domain_name": { "type": "string" }, "status": { "type": "string", "enum": [ "Active", "Expired", "Pending", "Pending Registration", "Pending Transfer", "Transferred Away", "Cancelled", "Grace", "Fraud" ] }, "registration_date": { "type": [ "string", "null" ] }, "expiry_date": { "type": [ "string", "null" ] }, "next_due_date": { "type": [ "string", "null" ] }, "recurring_amount": { "type": "object", "properties": { "value": { "type": "string" }, "currency": { "type": "string" } }, "required": [ "value", "currency" ] }, "first_payment_amount": { "type": "object", "properties": { "value": { "type": "string" }, "currency": { "type": "string" } }, "required": [ "value", "currency" ] }, "auto_renew": { "type": "boolean" }, "id_protection": { "type": "boolean" }, "dns_management": { "type": "boolean" } }, "required": [ "id", "domain_name", "status", "registration_date", "expiry_date", "next_due_date", "recurring_amount", "first_payment_amount", "auto_renew", "id_protection", "dns_management" ] }, "error": { "type": "string" } } } ``` Errors: `not_found` ## get_domain_nameservers Scope: `read:domains` | Annotations: read-only, idempotent, open-world Get the registrar nameservers currently set for one of the caller's domains, by domain ID. Performs a live lookup at the registrar. Requires the read:domains scope. Input schema: ```json { "type": "object", "properties": { "id": { "type": "integer" } }, "required": [ "id" ] } ``` Output schema: ```json { "type": "object", "properties": { "domain_id": { "type": "integer" }, "nameservers": { "type": "array", "items": { "type": "string" } }, "error": { "type": "string" }, "message": { "type": "string" } } } ``` Errors: `lookup_failed`, `not_found` ## get_domain_whois Scope: `read:domains` | Annotations: read-only, idempotent, open-world Get the WHOIS contact records (registrant, admin, tech) for one of the caller's domains, by domain ID. Contains personal contact details; performs a live lookup at the registrar. Requires the read:domains scope. Input schema: ```json { "type": "object", "properties": { "id": { "type": "integer" } }, "required": [ "id" ] } ``` Output schema: ```json { "type": "object", "properties": { "domain_id": { "type": "integer" }, "registrant": { "type": "object", "additionalProperties": { "type": [ "string", "null" ] } }, "admin": { "type": "object", "additionalProperties": { "type": [ "string", "null" ] } }, "tech": { "type": "object", "additionalProperties": { "type": [ "string", "null" ] } }, "error": { "type": "string" }, "message": { "type": "string" } } } ``` Errors: `lookup_failed`, `not_found` ## list_tickets Scope: `read:tickets` | Annotations: read-only, idempotent List the caller's support tickets, optionally filtered by status. Requires the read:tickets scope. Input schema: ```json { "type": "object", "properties": { "status": { "type": [ "null", "string" ], "default": null } } } ``` Output schema: ```json { "type": "object", "properties": { "tickets": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "integer" }, "subject": { "type": "string" }, "status": { "type": "string", "enum": [ "Open", "Answered", "Customer-Reply", "Awaiting reply", "Closed", "On Hold", "In Progress" ] }, "priority": { "type": "string" }, "created_at": { "type": [ "string", "null" ] }, "updated_at": { "type": [ "string", "null" ] } }, "required": [ "id", "subject", "status", "priority", "created_at", "updated_at" ] } } }, "required": [ "tickets" ] } ``` ## get_ticket Scope: `read:tickets` | Annotations: read-only, idempotent Get a single support ticket by ID, including customer-visible replies only. Requires the read:tickets scope. Input schema: ```json { "type": "object", "properties": { "id": { "type": "integer" } }, "required": [ "id" ] } ``` Output schema: ```json { "type": "object", "properties": { "ticket": { "type": "object", "properties": { "id": { "type": "integer" }, "subject": { "type": "string" }, "status": { "type": "string", "enum": [ "Open", "Answered", "Customer-Reply", "Awaiting reply", "Closed", "On Hold", "In Progress" ] }, "priority": { "type": "string" }, "created_at": { "type": [ "string", "null" ] }, "updated_at": { "type": [ "string", "null" ] }, "replies": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "integer" }, "message": { "type": "string" }, "created_at": { "type": [ "string", "null" ] } }, "required": [ "id", "message", "created_at" ] } } }, "required": [ "id", "subject", "status", "priority", "created_at", "updated_at", "replies" ] }, "error": { "type": "string" } } } ``` Errors: `not_found` ## check_domain_availability Scope: `read:domain_catalog` | Annotations: read-only, idempotent, open-world Check availability and USD pricing for one or more explicit domain names (up to 10). Each result gives status, 1-year register and transfer prices, and free_with_hosting / free_with_products — whether the TLD is free when ordered with a JetHost hosting plan and which plans (and billing cycles) qualify. When a domain is available and sellable, follow up with list_hosting_plans to show the customer which plans to pair it with; free_with_products already names the plans that include that TLD as a free domain. Data may be up to 60 seconds stale. Requires the read:domain_catalog scope. Input schema: ```json { "type": "object", "properties": { "domains": { "type": "array", "description": "Fully-qualified domain names to check.", "items": { "type": "string" } } }, "required": [ "domains" ] } ``` Output schema: ```json { "type": "object", "properties": { "domains": { "type": "array", "items": { "type": "object", "properties": { "domain_name": { "type": "string" }, "sld": { "type": "string" }, "tld": { "type": "string" }, "status": { "type": "string", "enum": [ "available", "registered", "reserved", "unknown" ] }, "register_price": { "type": [ "string", "null" ] }, "transfer_price": { "type": [ "string", "null" ] }, "is_premium": { "type": "boolean" }, "sellable": { "type": "boolean" }, "currency": { "type": "string" }, "free_with_hosting": { "type": "boolean", "description": "True when this TLD can be registered free of charge if ordered together with a qualifying JetHost hosting plan. See free_with_products for which plans and billing cycles qualify." }, "free_with_products": { "type": "array", "description": "Hosting plans that include this domain free when ordered together. Empty when the domain is not free with any plan.", "items": { "type": "object", "properties": { "product_id": { "type": "integer", "description": "WHMCS product id of the qualifying hosting plan." }, "product_name": { "type": "string", "description": "Display name of the qualifying hosting plan." }, "billing_cycles": { "type": "array", "description": "Billing cycles on which the free domain applies (e.g. \"annually\", \"biennially\", \"triennially\"). The customer must pick one of these terms for the domain to be free.", "items": { "type": "string" } } }, "required": [ "product_id", "product_name", "billing_cycles" ] } } }, "required": [ "domain_name", "sld", "tld", "status", "register_price", "transfer_price", "is_premium", "sellable", "currency", "free_with_hosting", "free_with_products" ] } }, "error": { "type": "string" } }, "required": [ "domains" ] } ``` Errors: `domains_required` ## suggest_domains Scope: `read:domain_catalog` | Annotations: read-only, idempotent, open-world Suggest available domains from a keyword stem. Pass the keyword in the "stem" argument (e.g. stem="bakery"). Returns up to 15 JetHost-sellable suggestions, each with USD pricing and free_with_hosting / free_with_products — whether the TLD is free when ordered with a JetHost hosting plan and which plans (and billing cycles) qualify. Data may be up to 60 seconds stale. Requires the read:domain_catalog scope. Input schema: ```json { "type": "object", "properties": { "stem": { "type": "string", "description": "The keyword, brand, or business name to generate domain suggestions from — a single word or short phrase, e.g. \"bakery\" or \"handmade studio\". Pass the stem only: NOT a full domain name and NOT a TLD. This is the only argument; its name is \"stem\".", "minLength": 1 } }, "required": [ "stem" ] } ``` Output schema: ```json { "type": "object", "properties": { "domains": { "type": "array", "items": { "type": "object", "properties": { "domain_name": { "type": "string" }, "sld": { "type": "string" }, "tld": { "type": "string" }, "status": { "type": "string", "enum": [ "available", "registered", "reserved", "unknown" ] }, "register_price": { "type": [ "string", "null" ] }, "transfer_price": { "type": [ "string", "null" ] }, "is_premium": { "type": "boolean" }, "sellable": { "type": "boolean" }, "currency": { "type": "string" }, "free_with_hosting": { "type": "boolean", "description": "True when this TLD can be registered free of charge if ordered together with a qualifying JetHost hosting plan. See free_with_products for which plans and billing cycles qualify." }, "free_with_products": { "type": "array", "description": "Hosting plans that include this domain free when ordered together. Empty when the domain is not free with any plan.", "items": { "type": "object", "properties": { "product_id": { "type": "integer", "description": "WHMCS product id of the qualifying hosting plan." }, "product_name": { "type": "string", "description": "Display name of the qualifying hosting plan." }, "billing_cycles": { "type": "array", "description": "Billing cycles on which the free domain applies (e.g. \"annually\", \"biennially\", \"triennially\"). The customer must pick one of these terms for the domain to be free.", "items": { "type": "string" } } }, "required": [ "product_id", "product_name", "billing_cycles" ] } } }, "required": [ "domain_name", "sld", "tld", "status", "register_price", "transfer_price", "is_premium", "sellable", "currency", "free_with_hosting", "free_with_products" ] } }, "error": { "type": "string" } }, "required": [ "domains" ] } ``` Errors: `stem_required` ## list_hosting_plans Scope: `read:product_catalog` | Annotations: read-only, idempotent, open-world List all active JetHost hosting plans with USD pricing per billing cycle (enabled cycles only) and the discounted new-order price. Returns a lean list without config options — call get_hosting_plan(pid) to drill into a single plan's configurable options. Data may be up to 60 seconds stale. Requires the read:product_catalog scope. Input schema: ```json { "type": "object", "properties": {} } ``` Output schema: ```json { "type": "object", "properties": { "plans": { "type": "array", "items": { "type": "object", "properties": { "pid": { "type": "integer" }, "gid": { "type": "integer" }, "name": { "type": "string" }, "tagline": { "type": "string" }, "short_description": { "type": "string" }, "description": { "type": "string" }, "product_url": { "type": "string" }, "currency": { "type": "string" }, "pricing": { "type": "array", "items": { "type": "object", "properties": { "cycle": { "type": "string" }, "base_price": { "type": "string" }, "new_price": { "type": "string" }, "discount_pct": { "type": "number" }, "partner_price": { "type": "string", "description": "The recurring price the caller pays; present only when the caller has a partner price-list discount." }, "partner_discount_pct": { "type": "number", "description": "The partner discount percentage; present only alongside partner_price." } }, "required": [ "cycle", "base_price", "new_price", "discount_pct" ] } } }, "required": [ "pid", "gid", "name", "tagline", "short_description", "description", "product_url", "currency", "pricing" ] } } }, "required": [ "plans" ] } ``` ## get_hosting_plan Scope: `read:product_catalog` | Annotations: read-only, idempotent, open-world Get a single hosting plan by its WHMCS product id (pid), including its pricing and normalized configurable options (e.g. server location). Config-option prices are list price. Call list_hosting_plans first to find the pid. Data may be up to 60 seconds stale. Requires the read:product_catalog scope. Input schema: ```json { "type": "object", "properties": { "pid": { "type": "integer", "description": "The WHMCS product id (pid) of the hosting plan to retrieve. Obtain this from list_hosting_plans. Must be a positive integer.", "minimum": 1 } }, "required": [ "pid" ] } ``` Output schema: ```json { "type": "object", "properties": { "plan": { "type": "object", "properties": { "pid": { "type": "integer" }, "gid": { "type": "integer" }, "name": { "type": "string" }, "tagline": { "type": "string" }, "short_description": { "type": "string" }, "description": { "type": "string" }, "product_url": { "type": "string" }, "currency": { "type": "string" }, "pricing": { "type": "array", "items": { "type": "object", "properties": { "cycle": { "type": "string" }, "base_price": { "type": "string" }, "new_price": { "type": "string" }, "discount_pct": { "type": "number" }, "partner_price": { "type": "string", "description": "The recurring price the caller pays; present only when the caller has a partner price-list discount." }, "partner_discount_pct": { "type": "number", "description": "The partner discount percentage; present only alongside partner_price." } }, "required": [ "cycle", "base_price", "new_price", "discount_pct" ] } }, "config_options": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "integer" }, "name": { "type": "string" }, "type": { "type": "string" }, "required": { "type": "boolean" }, "options": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "integer" }, "name": { "type": "string" }, "price": { "type": "object" } }, "required": [ "id", "name", "price" ] } } }, "required": [ "id", "name", "type", "required", "options" ] } } }, "required": [ "pid", "gid", "name", "tagline", "short_description", "description", "product_url", "currency", "pricing", "config_options" ] }, "error": { "type": "string" } } } ``` Errors: `not_found` ## get_website_logs Scope: `read:logs` | Annotations: read-only, idempotent, open-world Get raw log lines for one domain on one of the caller's hosting services, within a time window: log_type=access for the HTTPS web-server access log (falls back to the plain HTTP log when the site has no SSL log), log_type=http_access for the plain HTTP access log specifically, log_type=php_error for PHP error_log files and WordPress debug.log. Results are tail-first (most recent matching lines first) and hard-capped at 500 lines / 256 KB — if truncated=true, narrow the time window or add a fixed-string search filter instead of paging. from/to must be ISO 8601 with an explicit offset (e.g. 2026-06-10T08:00:00+03:00 or ...Z); the effective window applied is echoed back in UTC. status=log_not_available means no such log file exists for that domain (do NOT report it as "no errors") — distinct from status=ok with zero lines, which means the log exists but nothing matched. status=unavailable_on_server means log access is not rolled out on that hosting server yet. Requires the read:logs scope. Input schema: ```json { "type": "object", "properties": { "service_id": { "type": "integer", "description": "The hosting service ID the domain belongs to. Obtain it via list_services.", "minimum": 1 }, "domain": { "type": "string", "description": "The domain whose logs to read. Must belong to the hosting service (main, addon, or subdomain).", "minLength": 1 }, "log_type": { "type": "string", "description": "Which log to read: \"access\" = the HTTPS web-server access log (the domain's SSL log), falling back to the plain HTTP log when the site has no SSL log; \"http_access\" = the plain HTTP access log specifically; \"php_error\" = PHP error_log files and WordPress debug.log found under the site's document root.", "enum": [ "access", "http_access", "php_error" ] }, "from": { "type": "string", "description": "Window start as ISO 8601 with a REQUIRED explicit offset, e.g. \"2026-06-10T08:00:00+03:00\" or \"2026-06-10T05:00:00Z\". Offset-less timestamps are rejected." }, "to": { "type": "string", "description": "Window end as ISO 8601 with a REQUIRED explicit offset. Must not be before \"from\"." }, "search": { "type": "string", "default": null, "description": "Optional fixed-string filter (substring match, NOT a regex) — e.g. an IP, a URL path, or \"PHP Fatal\"." }, "limit": { "type": "integer", "default": null, "description": "Maximum lines to return. Capped at 500 server-side regardless of the requested value; omit for the full 500.", "minimum": 1 } }, "required": [ "service_id", "domain", "log_type", "from", "to" ] } ``` Output schema: ```json { "type": "object", "properties": { "logs": { "type": "object", "properties": { "service_id": { "type": "integer" }, "domain": { "type": "string" }, "log_type": { "type": "string", "enum": [ "access", "http_access", "php_error" ] }, "status": { "type": "string", "enum": [ "ok", "log_not_available", "unavailable_on_server" ] }, "lines": { "type": "array", "items": { "type": "string" } }, "truncated": { "type": "boolean" }, "from": { "type": "string" }, "to": { "type": "string" }, "module_version": { "type": "string" }, "files_capped": { "type": "integer" }, "stopped_reason": { "type": "string" } }, "required": [ "service_id", "domain", "log_type", "status", "lines", "truncated", "from", "to", "module_version" ] }, "error": { "type": "string" } } } ``` Errors: `invalid_domain`, `invalid_timestamp`, `invalid_range`, `not_found` ## list_website_domains Scope: `read:websites` | Annotations: read-only, idempotent List the domains hosted on one of the caller's hosting services, by service ID: the main domain plus any addon domains, subdomains, and parked (alias) domains, each with its document root. The result is the complete set for that account. Use it to discover which domain to pass to get_website_logs or to map a site to its directory on the server. Requires the read:websites scope. Input schema: ```json { "type": "object", "properties": { "service_id": { "type": "integer", "description": "The hosting service ID whose hosted domains to list. Obtain it via list_services.", "minimum": 1 } }, "required": [ "service_id" ] } ``` Output schema: ```json { "type": "object", "properties": { "domains": { "type": "array", "items": { "type": "object", "properties": { "domain": { "type": "string" }, "type": { "type": "string", "enum": [ "main", "addon", "sub", "parked", "other" ] }, "document_root": { "type": "string" } }, "required": [ "domain", "type", "document_root" ] } }, "error": { "type": "string" } } } ``` Errors: `not_found` ## get_website_statistics Scope: `read:analytics` | Annotations: read-only, idempotent, open-world Get pre-aggregated AWStats traffic statistics for one domain on one of the caller's hosting services, for the CURRENT month. HTTP and HTTPS are reported as separate blocks (never summed). Each block has a status: ok (data parsed), statistics_not_available (no data file for that protocol this month — do NOT report as "no visitors"), or unavailable_on_server (the stats module is not rolled out on that hosting server yet). Sections cover summary, by_hour, by_day, top_pages, not_found_404, referrers, search_engines, search_keywords, origin, countries, os, browsers, robots, http_errors, file_types — pass "sections" to narrow the payload (omitted: list sections return a bounded top-N, small sections are complete). Bandwidth is raw bytes; individual visitor IPs are not exposed. Data is rebuilt nightly, so figures reflect through roughly yesterday and the current month is partial. Requires the read:analytics scope. Input schema: ```json { "type": "object", "properties": { "service_id": { "type": "integer", "description": "The hosting service ID the domain belongs to. Obtain it via list_services.", "minimum": 1 }, "domain": { "type": "string", "description": "The domain whose statistics to read. Must belong to the hosting service (main, addon, or subdomain).", "minLength": 1 }, "sections": { "type": "array", "default": null, "description": "Optional subset of sections to return. Omit for everything (list sections capped to a top-N). Allowed: summary, by_hour, by_day, top_pages, not_found_404, referrers, search_engines, search_keywords, origin, countries, os, browsers, robots, http_errors, file_types.", "items": { "type": "string", "enum": [ "summary", "by_hour", "by_day", "top_pages", "not_found_404", "referrers", "search_engines", "search_keywords", "origin", "countries", "os", "browsers", "robots", "http_errors", "file_types" ] } } }, "required": [ "service_id", "domain" ] } ``` Output schema: ```json { "type": "object", "properties": { "statistics": { "type": "object", "properties": { "service_id": { "type": "integer" }, "domain": { "type": "string" }, "period": { "type": "object", "properties": { "year": { "type": "integer" }, "month": { "type": "integer" } }, "required": [ "year", "month" ] }, "module_version": { "type": "string" }, "http": { "type": "object", "properties": { "status": { "type": "string", "enum": [ "ok", "statistics_not_available", "unavailable_on_server" ] }, "sections": { "type": "object" } }, "required": [ "status", "sections" ] }, "https": { "type": "object", "properties": { "status": { "type": "string", "enum": [ "ok", "statistics_not_available", "unavailable_on_server" ] }, "sections": { "type": "object" } }, "required": [ "status", "sections" ] } }, "required": [ "service_id", "domain", "period", "module_version", "http", "https" ] }, "error": { "type": "string" } } } ``` Errors: `invalid_domain`, `not_found` ## deploy_site_from_url Scope: `write:deploy` | Annotations: destructive, open-world Deploy a Git repository to one domain on one of the caller's hosting services. Source repositories are restricted to a server-side host allowlist. NO build step runs (no npm/composer install, no compilation) — the repository must contain ready-to-serve files. For Node.js apps this is the PREBUILT-deploy path: since nothing is built or installed on the server, the repository must already contain the built/bundled output (a bundled production build is self-contained, so `node_modules` does not need to be uploaded). Deploys are contained: the module refuses to overwrite a target that is not a clean scaffold (status=target_not_empty), so an established site is never clobbered. Rollback is by ref — re-deploy a previous ref from get_deploy_status history to restore an earlier state. status=ok means the deploy landed (commit_sha/file_count describe it); status=quota_exceeded means the account disk quota would be exceeded and nothing was deployed; status=target_not_empty is the containment refusal; status=unavailable_on_server means the deploy module is not rolled out on that hosting server yet. This is a state-changing, destructive, non-idempotent write — it requires the write:deploy scope. Input schema: ```json { "type": "object", "properties": { "service_id": { "type": "integer", "description": "The hosting service ID to deploy to. Obtain it via list_services.", "minimum": 1 }, "target_domain": { "type": "string", "description": "The hosted domain to deploy to. Must belong to the hosting service (discover it via list_website_domains).", "minLength": 1 }, "repo_url": { "type": "string", "description": "The Git repository URL to deploy. Only repositories on the server-side host allowlist are accepted; no build step runs, so the repository must contain ready-to-serve files.", "minLength": 1 }, "ref": { "type": "string", "default": null, "description": "Optional branch, tag, or commit to deploy. Omit for the repository default branch. To roll back, pass a ref from get_deploy_status history." }, "subdir": { "type": "string", "default": null, "description": "Optional document-root-relative subdirectory to deploy into. Omit to deploy into the target document root itself." } }, "required": [ "service_id", "target_domain", "repo_url" ] } ``` Output schema: ```json { "type": "object", "properties": { "deploy": { "type": "object", "properties": { "service_id": { "type": "integer" }, "target_domain": { "type": "string" }, "status": { "type": "string", "enum": [ "ok", "quota_exceeded", "target_not_empty", "unavailable_on_server" ] }, "repo_url": { "type": "string" }, "ref": { "type": "string" }, "commit_sha": { "type": "string" }, "target": { "type": "string" }, "file_count": { "type": "integer" }, "module_version": { "type": "string" } }, "required": [ "service_id", "target_domain", "status", "repo_url", "ref", "commit_sha", "target", "file_count", "module_version" ] }, "error": { "type": "string" } } } ``` Errors: `invalid_target_domain`, `invalid_repo_url`, `not_found` ## deploy_private_repo Scope: `write:deploy` | Annotations: destructive, open-world Deploy a private SSH Git repository to one domain on a caller's hosting service using a pre-installed per-repo deploy key. The repo MUST contain a .cpanel.yml at its root; without one, returns no_cpanel_yml. Call generate_deploy_key first (service_id — obtain via list_services) to provision the key, then add the returned public key to the repository's deploy keys on GitHub/GitLab/Bitbucket. Omitted branch deploys the repository default branch (HEAD). This destructive, non-idempotent write requires the write:deploy scope and is distinct from get_deploy_status (the URL-deploy lineage). Input schema: ```json { "type": "object", "properties": { "service_id": { "type": "integer", "description": "The hosting service ID to deploy to. Obtain it via list_services.", "minimum": 1 }, "repo_ssh_url": { "type": "string", "description": "SSH clone URL of the private repository (e.g. git@github.com:org/repo.git). A deploy key for this URL must have been generated via generate_deploy_key for this service first.", "minLength": 1 }, "target_domain": { "type": "string", "description": "The hosted domain to deploy to. Must belong to the hosting service (discover it via list_website_domains).", "minLength": 1 }, "branch": { "type": "string", "default": null, "description": "Optional branch, tag, or commit to deploy. Omit for the repository default branch (resolves to HEAD)." } }, "required": [ "service_id", "repo_ssh_url", "target_domain" ] } ``` Output schema: ```json { "type": "object", "properties": { "deploy": { "type": "object", "properties": { "status": { "type": "string" }, "commit_sha": { "type": "string" }, "ref": { "type": "string" }, "service_id": { "type": "integer" }, "target_domain": { "type": "string" }, "repository_root": { "type": "string" }, "deployment_id": { "type": "string" }, "module_version": { "type": "string" } }, "required": [ "status", "commit_sha", "ref", "service_id", "target_domain", "repository_root", "deployment_id", "module_version" ] }, "error": { "type": "string", "enum": [ "invalid_url", "not_found", "key_not_authorized", "no_cpanel_yml", "repo_unreachable", "cloning", "cpanel_error" ] }, "status": { "type": "string" }, "repository_root": { "type": "string" }, "message": { "type": "string" } } } ``` Errors: `invalid_target_domain`, `invalid_repo_url` ## get_deploy_status Scope: `read:deploy` | Annotations: read-only, idempotent, open-world Get the current deploy marker and deploy history for one domain on one of the caller's hosting services: current = the repo_url, ref, commit_sha, and deployed_at currently live at the target (null when nothing has been deployed there); history = past deploys, each with the same fields. Pass a ref from history back to deploy_site_from_url to roll back. Requires the read:deploy scope. Input schema: ```json { "type": "object", "properties": { "service_id": { "type": "integer", "description": "The hosting service ID the target domain belongs to. Obtain it via list_services.", "minimum": 1 }, "target_domain": { "type": "string", "description": "The hosted domain whose deploy state to read. Must belong to the hosting service.", "minLength": 1 } }, "required": [ "service_id", "target_domain" ] } ``` Output schema: ```json { "type": "object", "properties": { "deploy_status": { "type": "object", "properties": { "service_id": { "type": "integer" }, "target_domain": { "type": "string" }, "current": { "type": [ "object", "null" ], "properties": { "repo_url": { "type": "string" }, "ref": { "type": "string" }, "commit_sha": { "type": "string" }, "deployed_at": { "type": "string" } }, "required": [ "repo_url", "ref", "commit_sha", "deployed_at" ] }, "history": { "type": "array", "items": { "type": "object", "properties": { "repo_url": { "type": "string" }, "ref": { "type": "string" }, "commit_sha": { "type": "string" }, "deployed_at": { "type": "string" } }, "required": [ "repo_url", "ref", "commit_sha", "deployed_at" ] } }, "module_version": { "type": "string" } }, "required": [ "service_id", "target_domain", "current", "history", "module_version" ] }, "error": { "type": "string" } } } ``` Errors: `invalid_target_domain`, `not_found` ## get_private_repo_deploy_status Scope: `read:deploy` | Annotations: read-only, idempotent, open-world Get the current deployment status for a private-repo (.cpanel.yml-managed) deploy on one domain on one of the caller's hosting services. This is the read-status companion to deploy_private_repo (cPanel VersionControl lineage) — distinct from get_deploy_status, which covers the URL-deploy lineage. Returns repository_root, branch, current_ref, commit_sha, last_deploy_outcome, and last_deployed_at for the active deployment. Requires the read:deploy scope. Input schema: ```json { "type": "object", "properties": { "service_id": { "type": "integer", "description": "The hosting service ID the target domain belongs to. Obtain it via list_services.", "minimum": 1 }, "target_domain": { "type": "string", "description": "The hosted domain whose private-repo deploy state to read. Must belong to the hosting service.", "minLength": 1 } }, "required": [ "service_id", "target_domain" ] } ``` Output schema: ```json { "type": "object", "properties": { "private_repo_deploy_status": { "type": "object", "properties": { "service_id": { "type": "integer" }, "target_domain": { "type": "string" }, "repository_root": { "type": "string" }, "branch": { "type": "string" }, "current_ref": { "type": "string" }, "commit_sha": { "type": "string" }, "last_deploy_outcome": { "type": "string" }, "last_deployed_at": { "type": "string" }, "module_version": { "type": "string" } }, "required": [ "service_id", "target_domain", "repository_root", "branch", "current_ref", "commit_sha", "last_deploy_outcome", "last_deployed_at", "module_version" ] }, "error": { "type": "string", "enum": [ "not_found", "invalid_target_domain" ] } } } ``` Errors: `invalid_target_domain`, `not_found` ## generate_deploy_key Scope: `cpanel:uapi` | Annotations: idempotent, open-world Generate an ed25519 SSH deploy key (public key) for a Git repository on one of the caller's hosting services. BOTH inputs are REQUIRED: service_id (the hosting service the key is for — obtain it via list_services) and repo_url (the repository URL). Returns the public key and provider-specific instructions for pasting it as a deploy key in GitHub, GitLab, or Bitbucket. Generation is idempotent — re-calling for the same (service_id, repo_url) pair returns the existing public key without creating a duplicate. The private key is never returned; it is installed directly on the server. Requires the cpanel:uapi scope. TIP — add the returned public key to the repository's DEPLOY KEYS (not your account-wide SSH keys); a given deploy key is unique to ONE repo across the whole provider, so "key already in use" means it is attached elsewhere. If a later clone fails with "Permission denied (publickey)", the deploy may reference an SSH IdentityFile that does not match this installed key — verify ~/.ssh/config IdentityFile lines point at the key this tool installed. Input schema: ```json { "type": "object", "properties": { "service_id": { "type": "integer", "description": "The hosting service ID to generate the deploy key for. Obtain it via list_services.", "minimum": 1 }, "repo_url": { "type": "string", "description": "The Git repository URL to associate the deploy key with (e.g. https://github.com/org/repo.git).", "minLength": 1 } }, "required": [ "service_id", "repo_url" ] } ``` Output schema: ```json { "type": "object", "properties": { "deploy_key": { "type": "object", "properties": { "public_key": { "type": "string" }, "provider_instructions": { "type": "string" }, "repo_id": { "type": "string" }, "ssh_clone_alias": { "type": "string" }, "module_version": { "type": "string" } }, "required": [ "public_key", "provider_instructions", "repo_id", "ssh_clone_alias", "module_version" ] }, "error": { "type": "string" }, "details": { "type": "array", "items": { "type": "string" } } } } ``` Errors: `invalid_repo_url`, `cpanel_error`, `not_found` ## list_cpanel_operations Scope: `cpanel:uapi` | Annotations: read-only, idempotent, open-world List the cPanel MODULES the caller may currently use — a compact, category-grouped index (NOT the full operation catalog, which is too large to return at once). By default `listing` is a plain-text block: one category header (email, dns, files, databases, security, ssl, deploy, account, stats, misc) per line, then an indented "Name (count) — description" line per module. Pass `search` (e.g. "forwarder" or "ssl certificate") to instead get a token-matched result list of "Module::function(params) — summary" lines; a query naming something cPanel has no module for (e.g. "mailing list") explains that instead of returning nothing. The index reflects exactly which cPanel modules JetHost has enabled on the credential's role right now — toggling a module in the admin UI changes this on the next call, with no deploy. Use this first to find the right module, then call get_cpanel_module to fetch that module's functions and parameters, then cpanel_uapi to run one. discovery="live" means the index came from the role grants; "unavailable" means role introspection is temporarily unreachable. Requires the cpanel:uapi scope. Input schema: ```json { "type": "object", "properties": { "search": { "type": "string", "default": null, "description": "Optional case-insensitive keyword search over module name, function name, function summary, and module description, e.g. \"forwarder\" or \"ssl certificate\". Omit to get the full grouped module index instead." } } } ``` Output schema: ```json { "type": "object", "properties": { "discovery": { "type": "string", "enum": [ "live", "unavailable" ] }, "module_count": { "type": "integer" }, "listing": { "type": "string" }, "modules": { "type": "object" }, "errors": { "type": "array", "items": { "type": "string" } }, "warnings": { "type": "array", "items": { "type": "string" } } } } ``` ## list_cpanel_accounts Scope: `cpanel:uapi` | Annotations: read-only, idempotent, open-world List the cPanel accounts on one of the caller's Managed VPS (cPanel) services, by service ID: user, domain, plan, suspended flag, and disk used/limit. System accounts (root, nobody, the WHM user) are never listed. Pass a returned user as cpanel_user to cpanel_uapi. A service that is not an active managed cPanel VPS, or not the caller's, returns error="not_found". Requires the cpanel:uapi scope. Input schema: ```json { "type": "object", "properties": { "service_id": { "type": "integer", "description": "The Managed VPS (cPanel) service ID. Obtain it via list_services.", "minimum": 1 } }, "required": [ "service_id" ] } ``` Output schema: ```json { "type": "object", "properties": { "service_id": { "type": "integer" }, "accounts": { "type": "array", "items": { "type": "object", "properties": { "user": { "type": "string" }, "domain": { "type": "string" }, "plan": { "type": "string" }, "suspended": { "type": "boolean" }, "diskused": { "type": "string" }, "disklimit": { "type": "string" } }, "required": [ "user", "domain", "plan", "suspended", "diskused", "disklimit" ] } }, "error": { "type": "string", "enum": [ "not_found", "internal_error", "backend_error" ] }, "message": { "type": "string" } } } ``` Errors: `internal_error`, `not_found`, `backend_error` ## get_cpanel_module Scope: `cpanel:uapi` | Annotations: read-only, idempotent, open-world Describe ONE cPanel module. Pass module (e.g. "DNS", "Email", "Mysql") — call list_cpanel_operations first to see the available module names; the name is matched case-insensitively (an ambiguous case-only match, e.g. both "SSH" and "ssh", returns error="module_ambiguous" with the candidates). By default returns a compact `listing`: one line per function, "name(params) — summary", where a leading "!" marks a MUTATING function (confirm with the user before running one via cpanel_uapi) and a leading space marks a read-only one. Pass `function` (one name, or an array of names, e.g. "list_pops" or ["list_pops","add_pop"]) to instead get the full parameter schema (method, summary, description, params: name/type/required/description) for just those functions. If the module is not enabled, the result lists the available module names so you can correct it. Use this after list_cpanel_operations and before cpanel_uapi to discover a module's exact functions and parameters. Requires the cpanel:uapi scope. Input schema: ```json { "type": "object", "properties": { "module": { "type": "string", "description": "The cPanel UAPI module to describe, e.g. \"DNS\", \"Email\", or \"Mysql\". Call list_cpanel_operations first to see the available module names. Matched case-insensitively.", "minLength": 1 }, "function": { "default": null, "description": "Optional function name, or array of function names, within the module to fetch full parameter schemas for (e.g. \"list_pops\" or [\"list_pops\", \"add_pop\"]), instead of the default compact listing." } }, "required": [ "module" ] } ``` Output schema: ```json { "type": "object", "properties": { "discovery": { "type": "string", "enum": [ "live", "unavailable" ] }, "module": { "type": "string" }, "displayName": { "type": "string" }, "function_count": { "type": "integer" }, "legend": { "type": "string" }, "listing": { "type": "string" }, "functions": { "type": "object", "additionalProperties": { "type": "object", "properties": { "method": { "type": "string" }, "summary": { "type": "string" }, "description": { "type": "string" }, "params": { "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string" }, "type": { "type": "string" }, "required": { "type": "boolean" }, "description": { "type": "string" } } } } } } }, "unmatched_functions": { "type": "array", "items": { "type": "string" } }, "error": { "type": "string" }, "requested": { "type": "string" }, "available_modules": { "type": "array", "items": { "type": "string" } }, "candidates": { "type": "array", "items": { "type": "string" } } } } ``` Errors: `module_ambiguous`, `module_not_found` ## cpanel_uapi Scope: `cpanel:uapi` | Annotations: destructive, open-world Run one cPanel operation against the cPanel account behind one of the caller's hosting services and return a normalized result { ok, data, errors[], warnings[] }. Discovery flow: list_cpanel_operations (optionally with `search`) to find the module, then get_cpanel_module to see its functions and parameters, then this tool to execute one. module is a cPanel UAPI module (e.g. "Email"); function is an operation in that module (e.g. "list_pops"); params is a JSON object of that operation's parameters. Only modules JetHost has enabled are available — an unknown or disabled module returns an error. A module the caller's key does not grant returns error="module_not_granted" together with granted_modules, the modules that key MAY use on that service. get_cpanel_module's listing marks each function with a leading "!" for mutating (write/delete) functions or a leading space for read-only ones — check that marker and confirm with the user before running a mutating call. The cPanel account is resolved from the owned service; a service the caller does not own returns error=not_found. Managed VPS services host several cPanel accounts: call list_cpanel_accounts and pass the chosen `user` as cpanel_user (error="cpanel_user_required" otherwise); there a "!" function refuses with error="confirm_required" until you pass confirm:true after the customer agreed. Requires the cpanel:uapi scope. data is the UAPI payload; cPanel base64-encoded fields (e.g. DNS::parse_zone dname_b64/data_b64/text_b64) are decoded to readable values under the same key without the _b64 suffix. TIP — writing PHP source: a WAF on the request path rejects any params carrying the literal "