Partner REST API

A conventional REST API covering every partner administration operation — customers, AI Gateway keys, VPS deployments, credits, billing, agents, and more. Backed by the same tool catalog as the Partner MCP Gateway, the same pkt_ API keys, and the same scope model. Use this when you want deterministic calls from scripts, CI pipelines, n8n/Zapier nodes, or any non-AI client.

Auto-generated OpenAPI 3.1 135 operations, 15 scope domains pkt_ Bearer auth Same scope model as MCP

Live spec URL (regenerated per request): https://mcp.knotie-ai.pro/api/partner-rest/openapi.json — also served at /docs/openapi/partner-api.json on this host.

Base URLs. The partner gateway is https://mcp.knotie-ai.pro — use it for every /api/partner-rest/* and /api/partner-mcp call on this page. Main-app partner routes (/api/partner/…, including customer onboarding) stay on https://knotie-ai.pro or your own white-label domain — do not send those to the gateway host. A development gateway also runs at connecthub.kno2gether.com; use it only if you were explicitly given access.

What's new

  • customers_capabilities — ask the API what you can set instead of hard-coding it. Returns every feature key with its type, bounds and whether the partner master switch behind it is on for you, plus your experiences, plans and the app ids allowedApps accepts. Derived from the live schema and catalogs, so it tracks the product on its own (below). Scope customers:read.
  • customers_onboard — one call provisions a complete customer: account, generated credentials, branded welcome email, optional plan, optional experience binding, per-customer feature overrides and a starting AI-credit grant. Backed by the main app's POST /api/partner/customers/onboard (documented in full below). Scope customers:write.
  • customers_update — 23 additional per-customer fields, so the tool now covers every field the main app's customer PUT handler actually writes: businessPhone, autoEmbeddingEnabled, kbTier, showSocialPlanner, showPhoneNumbers, showAiGateway, enableAiMemory, showMcp, showMarketplaceSolutions, showMediaStudio, showWebsiteStudio, showAiAgents, selfServeAgentLimit, showVpsCatalogApps, vpsMarginPercent, enableTeamMembers, maxTeamMembers, lowCreditThreshold, aiCreditGracePeriodSeconds, restrictCustomerCreditPlanVisibility, customerInfraResellerMode, customerAgencyDashboardMode, storageAllocatedMB. It is a pure passthrough — clamping, master-gating and range validation stay in the main app, so the gateway can never drift into a second, weaker policy layer.
  • customer_credits_set_monthly — set or clear a customer's recurring monthly credit allocation (the subscription-style grant), distinct from customer_credits_top_up which moves credits once. monthlyAllocation: 0 turns the monthly grant off; rolloverEnabled decides whether an unspent month carries forward. Scope credits:write.
  • Attributable credit ledger — every credit tool now stamps the ledger row with the name of the pkt_ key that made the call ([via api-key:<name>]), so automated top-ups, deductions and monthly allocations are traceable to a key rather than to "the partner". One honest exception: the initialAiCredits grant inside customers_onboard is attributed to the partner, because the gateway reaches the main app as a minted partner token. Use customer_credits_top_up when the ledger must name the key.
  • Plan feature parity — subscription plans can now express 11 more keys, including the three studio flags, selfServeAgentLimit, the dashboard-mode tri-states, the four credit keys, and an experienceType binding validated against your own experiences. A plan passed as planId to customers_onboard therefore configures as much as the dashboard's customer editor can.

No hand-maintained spec to wait on: the OpenAPI document is generated from the live tool registry on every request, so these operations are already in it — schemas, scopes and all.

Endpoints

MethodPathAuthPurpose
GET/api/partner-rest/healthnoneLiveness check.
GET/api/partner-rest/openapi.jsonnone / optionalFull OpenAPI 3.1 document. With a pkt_ bearer, the spec is scope-filtered to the operations that key can invoke.
GET/api/partner-rest/toolsBearerScope-filtered tool listing with input schemas — useful for dynamic form generation.
POST/api/partner-rest/tools/:toolNameBearerExecute one tool. Body is the JSON input. Response is the tool result.

One-call customer onboarding

POST /api/partner/customers/onboard is a main-app endpoint (not a gateway tool path) — it lives on knotie-ai.pro or on your own white-label domain, and you can call it directly with either credential type. The gateway exposes exactly the same contract as the customers_onboard tool.

MethodPathAuthScope
POST/api/partner/customers/onboardX-API-Key: pkt_… or Authorization: Bearer <partner JWT>customers:write (via the gateway tool)
POST/api/partner-rest/tools/customers_onboardAuthorization: Bearer pkt_…customers:write

When both are present the X-API-Key header wins — an explicit key header is an unambiguous statement of intent, so it is never silently ignored. A customer key (ckt_) is rejected even though it carries a partner id: it is a customer's credential and must not be able to mint that partner's customers. The endpoint is behind the Redis-backed partner rate limiter and returns 429 when you exceed it.

Apply order (fixed, and load-bearing)

  1. Your catalog / dashboard-mode defaults for a new customer
  2. planId features — the primary upsell lever
  3. Per-call features overrides — secondary, per-customer
  4. Your partner master switches — last, unconditional, fail-closed

Step 4 runs even when you send no overrides at all: a stored plan row may predate a master switch being turned off, so the clamp is what makes the grant safe — not the input validation. The five master-gated keys are showMediaStudio, showWebsiteStudio, showAiAgents, enableAiMemory and showAiGateway. Requesting one while its master switch is off is not an error — the key is written false and the reason lands in warnings[]. Read that array.

Request body

FieldTypeNotes
emailstring — requiredLower-cased and trimmed server-side. Unique per partner; this is the idempotency key.
firstNamestring ≤ 200Defaults to the local part of the email.
lastNamestring ≤ 200—
companyNamestring ≤ 300Defaults to "firstName lastName".
planIdstringOne of your subscription plans. Applied before overrides and before the clamps.
experienceTypestring 1–64Validated against your own experiences. An unknown value is a warning, not an error — the customer is still created, just without the binding.
featuresobjectPer-customer overrides, validated by the same schema plans use. See the key list below.
initialAiCreditsinteger 0–1,000,000Granted to the new customer's credit balance. A hard leg — if it fails the whole customer is rolled back.
sendWelcomeEmailboolean (default true)The welcome email is the only delivery channel for the generated password. Set false only if you will issue credentials separately.
idempotencyKeystring ≤ 200Optional caller-supplied de-duplication key. De-duplication itself is on (partner, email).
passwordrejectedThere is no password field and there never will be. Sending the key is a hard 400, not a silent strip. A secure password is generated server-side and delivered only in the branded welcome email.

Which features keys are honoured

Anything outside these lists is ignored rather than written — the body must never be able to name an arbitrary column.

  • Portal menu & capability toggles: enableAdvancedAnalytics, enableDetailedCallAnalysis, enableActionPointAnalysis, showIntegration, showKnowledgeBase, showDocsAndMedia, showSocialPlanner, showPhoneNumbers, showCampaigns, showScheduleMeeting, showPricingInformation, showMarketplaceSolutions, showMcp, showServerTerminal, enableVpsPurchase, vpsMarginPercent, showVpsCatalogApps, autoEmbeddingEnabled, enableApiAccess, showApiKeys, enableTeamMembers, maxTeamMembers, storageAllocatedMB, selfServeAgentLimit, customerInfraResellerMode, customerAgencyDashboardMode
  • Master-gated grants (requests, not guarantees): showMediaStudio, showWebsiteStudio, showAiAgents, enableAiMemory, showAiGateway
  • Credit metering & knowledge-base tier: aiCreditsEnabled, aiCreditPricePerMinute, aiCreditGracePeriodSeconds, lowCreditThreshold, lowCreditNotificationsEnabled, restrictCustomerCreditPlanVisibility, visibleCustomerCreditPlanIds, kbProcessingEnabled, kbTier
  • selfServeAgentLimit is clamped to 0–20.
  • enableApiAccess: false also forces showApiKeys: false.
  • kbTier: "premium" needs the admin-granted premium ingestion entitlement; without it the customer is created on the default tier and you get a warning.
  • visibleCustomerCreditPlanIds is re-validated against your own credit plans — ids you do not own are dropped and listed in warnings[] rather than failing the call.
  • Unlike customers_update, enableAiMemory here can never flip your partner-wide switch. An API call must not change partner-level state.

Call it

bash — direct against the main app (partner API key)
curl -X POST https://knotie-ai.pro/api/partner/customers/onboard \
  -H 'X-API-Key: pkt_YOUR_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "email": "[email protected]",
    "firstName": "Alice",
    "lastName": "Smith",
    "companyName": "Acme Corp",
    "planId": "plan_abc123",
    "experienceType": "MEDIA_STUDIO",
    "features": {
      "showKnowledgeBase": true,
      "showMediaStudio": true,
      "selfServeAgentLimit": 3
    },
    "initialAiCredits": 250,
    "sendWelcomeEmail": true
  }'
bash — same contract through the REST gateway
curl -X POST https://mcp.knotie-ai.pro/api/partner-rest/tools/customers_onboard \
  -H 'Authorization: Bearer pkt_YOUR_KEY' \
  -H 'Content-Type: application/json' \
  -d '{ "email": "[email protected]", "companyName": "Acme Corp",
        "planId": "plan_abc123", "initialAiCredits": 250 }'

Response

201 Created — a new customer was provisioned
{
  "customerId": "cus_abc123",
  "userOnboardingId": "onb_abc123",
  "created": true,
  "existing": false,
  "appliedPlanId": "plan_abc123",
  "appliedFeatures": [
    "plan:plan_abc123",
    "experienceType:MEDIA_STUDIO",
    "showKnowledgeBase",
    "selfServeAgentLimit",
    "showMediaStudio",
    "initialAiCredits"
  ],
  "warnings": []
}

appliedFeatures lists what actually landed — it is the honest record, not an echo of your request. Through the gateway the same result is wrapped with a leading outcome (CREATED / EXISTING_UNCHANGED), an attention line and warningCount, so an AI agent cannot scroll past a clamped grant.

200 OK — idempotent hit, NOTHING was mutated
{
  "customerId": "cus_existing",
  "userOnboardingId": "onb_existing",
  "created": false,
  "existing": true,
  "appliedFeatures": [],
  "warnings": [
    "A customer with this email already exists for this partner — returned unchanged. Nothing was mutated (planId, features, and initialAiCredits were ignored)."
  ]
}

This is the contract, not a bug: re-applying a plan or overrides on a repeat call would let a retry silently rewrite a live customer's entitlements, which is exactly what idempotency exists to prevent. To change an existing customer use customers_update / customer_credits_top_up / customer_credits_set_monthly.

Failure policy

Money and toggles are hard failures. If the plan application, the override write or the credit grant fails, the customer is rolled back by a compensating delete and the call returns 500 — you never get a half-configured, billable account. The welcome email is the only leg allowed to degrade: an undelivered email becomes a warnings[] entry and the account still exists.

HTTPWhen
201Customer created.
200Customer already existed — returned unchanged.
400A password key was present, the JSON body was unparseable, or the body failed validation (field errors are returned in details).
401No partner bearer token and no partner API key — or a key that is not a partner key.
404The authenticated partner no longer exists.
429Partner rate limit exceeded. Retry shortly.
500A hard leg failed; the customer was rolled back and was not created. Safe to retry.

Discover what you can set

Rather than hard-coding the field list above into your integration, ask for it. GET /api/partner/customers/capabilities returns the complete writable surface for your account — and it is derived at request time from the same validation schema and catalogs the product itself runs on, so it never falls behind a release. Same auth as the onboarding endpoint (partner bearer token or pkt_ key); read-only; mutates nothing.

  • features — every key you may send inside features, with its type (boolean, number, string, array, or tri-state for the null-means-inherit toggles), its declared bounds or enum values, and a human label and description where we have one.
  • masterGate — on each master-gated key: which partner switch backs it and whether yours is currently on. This is how you find out a grant would be clamped before you send it, instead of reading it back out of warnings[].
  • experiences — the exact experienceType values that will validate for your account. Anything else is dropped with a warning.
  • apps — the ids and names accepted in features.allowedApps, with category and stability.
  • plans — your planId values, so you never have to look one up by hand.
bash — ask what you can set (partner API key)
curl https://knotie-ai.pro/api/partner/customers/capabilities \
  -H 'X-API-Key: pkt_YOUR_KEY'
200 OK — abridged
{
  "counts": { "features": 44, "experiences": 2, "apps": 39, "plans": 3 },
  "features": [
    {
      "key": "selfServeAgentLimit",
      "type": "number", "integer": true, "min": 0, "max": 20,
      "optional": true, "nullable": false,
      "label": null, "description": null, "category": null,
      "masterGate": null
    },
    {
      "key": "showMediaStudio",
      "type": "boolean", "optional": true, "nullable": false,
      "label": "Media Studio",
      "description": "Customer can generate images & video in the branded studio …",
      "category": "infrastructure",
      "masterGate": {
        "partnerField": "mediaStudioEnabled",
        "enabled": false,
        "label": "Media Studio",
        "note": "Your Media Studio master switch is OFF — requesting this will be
                 clamped to false and reported in warnings[]."
      }
    },
    { "key": "kbTier", "type": "string", "enum": ["essential","moderate","premium"] },
    { "key": "showVpsCatalogApps", "type": "tri-state", "nullable": true }
  ],
  "experiences": [
    { "experienceType": "AI_RECEPTIONIST", "name": "Front Desk", "isDefault": true, "enabled": true }
  ],
  "apps": [ { "id": "gmail", "name": "gmail", "displayName": "Gmail", "category": "free" } ],
  "plans": [ { "id": "plan_abc123", "name": "Starter", "isActive": true } ]
}
bash — same thing through the REST gateway
curl -X POST https://mcp.knotie-ai.pro/api/partner-rest/tools/customers_capabilities \
  -H 'Authorization: Bearer pkt_YOUR_KEY' \
  -H 'Content-Type: application/json' -d '{}'

Re-read it rather than caching it. New toggles, new experiences and new apps appear here automatically — that is the point of the endpoint, and a cached copy throws it away.

Response shape

REST responses auto-adapt to the caller's Accept header so scripts, pipelines, and curl callers get plain JSON while MCP-aware clients receive the spec-compliant wrapped envelope.

Default for REST clients — unwrapped plain JSON

When Accept does not include text/event-stream, the gateway parses the MCP envelope and returns the main-app JSON directly. An X-Partner-Mcp-Unwrapped: true response header confirms the auto-unwrap happened.

Accept: application/json → 200 OK
// Response headers include:
//   X-Partner-Mcp-Unwrapped: true

{
  "success": true,
  "data": [ /* customers... */ ],
  "pagination": { "totalCount": 42 }
}

If the upstream tool reported a domain error, the auto-unwrap flow surfaces it as HTTP 502 with a standard { error: { code, message, data } } body — so your client can use try/catch and status-code checks like any normal REST API.

MCP-aware clients — wrapped envelope (spec 2025-11-25)

When Accept includes text/event-stream(MCP clients always send this), OR when you explicitly add ?wrapped=1, the raw MCP tool-result envelope is returned so the shape matches POST /api/partner-mcp exactly.

Accept: application/json, text/event-stream → 200 OK
{
  "content": [
    { "type": "text", "text": "{ \"success\": true, \"data\": [/* ... */] }" }
  ]
}

The text field holds the main-app JSON verbatim. AI agents render it as a tool observation; REST SDKs would have to JSON.parse() it — which is why the unwrapped form above is usually easier for deterministic clients.

Quick start

1. Fetch the OpenAPI spec (anonymously)

bash
curl https://mcp.knotie-ai.pro/api/partner-rest/openapi.json | jq '.paths | keys | length'
# → 137  (135 tools + 2 discovery endpoints: /tools and /openapi.json)

# The spec is rebuilt from the live tool registry on every request, so newly
# shipped tools are already in it. Confirm the latest ones are there:
curl -s https://mcp.knotie-ai.pro/api/partner-rest/openapi.json \
  | jq '.paths | keys[] | select(test("customers_onboard|customer_credits_set_monthly|customers_update"))'

2. List the tools your key can call

bash
curl https://mcp.knotie-ai.pro/api/partner-rest/tools \
  -H 'Authorization: Bearer pkt_YOUR_KEY' | jq '.tools[] | .name'

3. Onboard a fully-configured customer

bash
curl -X POST https://mcp.knotie-ai.pro/api/partner-rest/tools/customers_onboard \
  -H 'Authorization: Bearer pkt_YOUR_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "email": "[email protected]",
    "firstName": "Alice",
    "lastName": "Smith",
    "companyName": "Acme Corp",
    "planId": "plan_abc123",
    "initialAiCredits": 250
  }'

# The legacy customers_create (6 fields, and you must supply a plaintext
# password) still exists for older automation. Prefer customers_onboard —
# see "One-call customer onboarding" above.

4. Put that customer on a recurring monthly credit allocation

bash
curl -X POST https://mcp.knotie-ai.pro/api/partner-rest/tools/customer_credits_set_monthly \
  -H 'Authorization: Bearer pkt_YOUR_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "customerId": "cus_abc123",
    "monthlyAllocation": 500,
    "rolloverEnabled": false,
    "reason": "Growth plan monthly allocation"
  }'

# monthlyAllocation: 0 turns the recurring grant off.
# The ledger row is stamped "[via api-key:<your key name>]" automatically —
# do not append the key name to "reason" yourself.

5. Deploy a VPS stack for that customer

bash
# Purchase VPS (returns checkout URL)
curl -X POST https://mcp.knotie-ai.pro/api/partner-rest/tools/vps_checkout \
  -H 'Authorization: Bearer pkt_YOUR_KEY' \
  -H 'Content-Type: application/json' \
  -d '{ "planId": "vps-plan-2", "region": "eu-fra", "customerId": "cus_abc" }'

# Then deploy apps onto the provisioned instance
curl -X POST https://mcp.knotie-ai.pro/api/partner-rest/tools/vps_instance_reinstall \
  -H 'Authorization: Bearer pkt_YOUR_KEY' \
  -H 'Content-Type: application/json' \
  -d '{ "instanceId": "inst_xyz", "applicationIds": ["n8n","supabase","redis"] }'

Generate a typed SDK

Feed the OpenAPI document into any standard generator to get a typed client in your language of choice.

TypeScript / openapi-fetch
# 1. Download the spec
curl https://mcp.knotie-ai.pro/api/partner-rest/openapi.json > openapi.json

# 2. Generate types
npx openapi-typescript openapi.json -o ./knotie-partner-api.d.ts

# 3. Use
import createClient from 'openapi-fetch';
import type { paths } from './knotie-partner-api';

const client = createClient<paths>({
  baseUrl: 'https://mcp.knotie-ai.pro',
  headers: { Authorization: 'Bearer pkt_YOUR_KEY' },
});

const { data } = await client.POST(
  '/api/partner-rest/tools/customers_create',
  { body: { email: '[email protected]', firstName: 'Alice', lastName: 'Smith',
            companyName: 'Acme', password: 'hunter2!' } },
);
Python / openapi-python-client
curl https://mcp.knotie-ai.pro/api/partner-rest/openapi.json > openapi.json
pip install openapi-python-client
openapi-python-client generate --path openapi.json

# Then use the generated client:
from knotie_partner_api import Client
from knotie_partner_api.api.customers import customers_create
client = Client(base_url='https://mcp.knotie-ai.pro',
                headers={'Authorization': 'Bearer pkt_YOUR_KEY'})
result = customers_create.sync(client=client, json_body={...})

Scope model (same as MCP)

Each tool declares a required scope like customers:read or vps:write. Scopes are set on each pkt_ key in the partner dashboard.

  • * — unrestricted (default for existing keys)
  • domain:* — all read + write in a domain (e.g. vps:*)
  • domain:read / domain:write — specific access

A 403 with data.type: "insufficient_scope" tells you which scope is required vs. which scopes the key holds. The OpenAPI spec's x-partner-mcp-scope extension advertises each operation's required scope.

Error semantics

HTTPdata.typeMeaning
200 (isError:true)—Tool ran but the main app returned a domain error (e.g. 404 on upstream lookup). Body is still a tool-result envelope.
401missing_credentials / invalid_credentials / expired / revoked / mcp_disabled_on_key / ip_not_allowedWWW-Authenticate header points to the PRM document.
403insufficient_scope / partner_not_approved / subscription_inactive / origin_rejectedAuthenticated, but not permitted.
404tool_not_foundUnknown tool name in path.
429daily_limit_exceeded / monthly_limit_exceededRetry-After header set with seconds until reset.
503feature_disabledREST facade disabled on this instance.

Security model — auth operations

The REST facade exposes no partner-auth operations(login, password change, MFA setup, session issuance) and no customer impersonation. A malfunctioning agent cannot take over the partner account or any customer account via this API.

  • Credential-reset tools (customers_reset_password, customers_enable_portal) send the new credentials to the customer's email — they never appear in the API response body.
  • A small set of tools do return plaintext secrets once (VPS root password, new API keys, new LiteLLM keys). These are labeled ⚠️ SENSITIVE in their summary field and gated by the domain scopes (vps:*, customer-api-keys:write, ai-gateway:write, embed:write). Omit those scopes from your pkt_ key if the agent shouldn't be able to emit secrets.
  • Every call — successful or failed — is audited to partner_mcp_invocations.

Full model: Partner MCP / Security model.

Troubleshooting auth errors

Every auth failure returns a structured data.type that tells you exactly what to fix. Common ones:

data.typeWhat it meansHow to fix
missing_credentialsNo Authorization: Bearer or X-API-Key header.Add -H 'Authorization: Bearer pkt_...'. We do NOT accept Basic.
invalid_credentialsKey wasn't found OR decryption failed. Three real sub-cases:
  1. Key was never issued (or was deleted) on this deployment's DB.
  2. ENCRYPTION_KEY mismatch between main app (writes) and Connect Hub (reads). Most common cause. See below.
  3. You pasted the wrong key.
expired / revokedKey lifecycle state.Create a new key in the dashboard.
mcp_disabled_on_keyKey is valid but MCP access is toggled off.Open the key's scope editor in Settings → Partner API Keys and flip "Enable MCP" on.
ip_not_allowedClient IP is not in the key's allowedIps list.Add the IP in the key's settings or remove the allowlist.
insufficient_scopeAuthenticated, but the key doesn't hold the required scope.Response body includes required_scope and granted_scopes. Update the key in the scope editor.
daily_limit_exceededKey hit its daily request budget.Honor the Retry-After header, or raise the limit on the key.

Most-common gotcha: shared-secret mismatch between main app and Connect Hub

Partner keys are encrypted at rest by the main app's ENCRYPTION_KEY and decrypted by Connect Hub. The delegated service JWT on every tool call is signed with the main app's JWT_SECRET. On Connect Hub, those same values live under the unambiguous names MAIN_APP_ENCRYPTION_KEY and MAIN_APP_JWT_SECRET — Connect Hub has its OWN ENCRYPTION_KEY / JWT_SECRET for local purposes which are typically different values.

Verify in each service's runtime:

bash — main app
echo -n "$ENCRYPTION_KEY" | sha256sum
echo -n "$JWT_SECRET"     | sha256sum
bash — Connect Hub
echo -n "$MAIN_APP_ENCRYPTION_KEY" | sha256sum
echo -n "$MAIN_APP_JWT_SECRET"     | sha256sum

Corresponding hashes MUST match byte-for-byte. A mismatch on encryption → invalid_credentials. A mismatch on JWT → the main app rejects the delegated call with its own 401 (tool runs return isError: true).

On Connect Hub, our auth code emits an error-level log line when decryption fails on every candidate — grep for "MAIN_APP_ENCRYPTION_KEY on Connect Hub does not match".

REST vs MCP — which should I use?

REST (this page)

  • • Deterministic scripts, CI, cron jobs
  • • Existing REST clients (Postman, curl, Insomnia)
  • • Auto-generated SDKs via openapi-typescript / openapi-python-client
  • • Zapier / Make / n8n HTTP-request nodes
  • • One-shot operations where you know the tool name

MCP (Partner MCP)

  • • AI agents (Claude Desktop, Cursor, n8n MCP client)
  • • Dynamic tool discovery + typed arguments via the protocol
  • • Streaming responses / long-running operations
  • • Session-aware conversations where the agent picks tools
  • • Multi-tool orchestration in a single chat turn

Both paths share the same pkt_ keys, scope model, audit trail, rate limits, and business-logic delegation. Pick the interface that fits the client.

Related