Base URL
https://hub.knotie-ai.pro
Endpoint
| Method | Path | Auth | Purpose |
|---|
POST | /api/outbound-call | Per-number webhook token | External systems (GHL, n8n, Zapier, Facebook lead bridge) |
Authentication
Pass the per-number outbound webhook token via one of these headers:
Authorization: Bearer <token>
or
Generate the token in the Partner Portal under the phone number’s Outbound Settings. Tokens are:
- Scoped to a single phone number — the
from_number in the request must match the number the token belongs to.
- Shown once at generation time; only a secure hash is stored.
- Revocable at any time from the portal.
Brute-force protection: after 10 failed auth attempts from the same IP within 5 minutes, that IP is blocked for 15 minutes.
Request body
{
"from_number": "+14155551234",
"to_number": "+19876543210",
"dynamic_variables": {
"lead_name": "Sarah",
"outreach_topic": "Q3 renewal",
"company": "Acme Corp"
},
"metadata": {
"campaign_id": "camp_abc123",
"external_id": "crm_lead_789"
}
}
| Field | Type | Required | Description |
|---|
from_number | string | Yes | E.164 caller number. Must match a phone number registered in Knotie and assigned to a Knova agent. |
to_number | string | Yes | E.164 number to call. |
dynamic_variables | object | No | Key-value pairs injected into the agent’s prompt and greeting as {{key}} placeholders. |
metadata | object | No | Arbitrary metadata stored with the call record. Not passed to the agent. |
Phone numbers are auto-normalised to E.164 — a leading + is added if missing and non-digit characters are stripped.
GoHighLevel workflows send a flat customData envelope. The integration gateway detects this automatically — no configuration needed.
{
"contact_id": "abc123",
"first_name": "Sarah",
"last_name": "Jones",
"customData": {
"from_number": "+14155551234",
"to_number": "+19876543210",
"dynamicdata_lead_name": "Sarah Jones",
"dynamicdata_outreach_topic": "Q3 renewal",
"metadata_campaign_id": "camp_abc123"
}
}
Normalisation rules:
| GHL field | Maps to |
|---|
customData.from_number | from_number |
customData.to_number | to_number |
customData.dynamicdata_<name> | dynamic_variables.<name> |
customData.metadata_<name> | metadata.<name> |
| Other top-level GHL fields | Ignored |
A body is treated as GHL format when it has no top-level from_number but has a customData.from_number.
Dynamic variables
Any key in dynamic_variables (or GHL dynamicdata_<name>) becomes a {{key}} substitution in the agent’s system prompt and greeting message.
Example:
Agent system prompt:
You are calling {{lead_name}} about {{outreach_topic}}.
Request:
{
"dynamic_variables": {
"lead_name": "Sarah",
"outreach_topic": "Q3 renewal"
}
}
Agent receives:
You are calling Sarah about Q3 renewal.
Keys not present in dynamic_variables are left as {{key}} — no error is thrown, but the agent will speak the literal placeholder.
Rate limiting
| Limit | Value |
|---|
| Calls per minute per phone number | 60 |
| Auth failures before IP block | 10 in 5 minutes |
| IP block duration | 15 minutes |
Rate limit headers are included on every response:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
When the limit is exceeded:
Response
Success (200 OK)
{
"success": true,
"call_id": "550e8400-e29b-41d4-a716-446655440000",
"provider": "knova",
"agent_id": "agt_abc123",
"from_number": "+14155551234",
"to_number": "+19876543210",
"provider_response": {
"room_name": "knova-out-agt_abc123-550e8400",
"trunk_mode": "byo"
}
}
trunk_mode is either "byo" (partner’s own Twilio/Telnyx trunk) or "shared" (Knotie’s shared trunk).
Error
{
"success": false,
"error": "Human-readable description",
"error_code": "ERROR_CODE"
}
Error codes
error_code | HTTP | Meaning |
|---|
VALIDATION_ERROR | 400 | Missing or invalid fields — a details array is included |
UNAUTHORIZED | 401 | Missing, invalid, or inactive token; or token doesn’t match the from_number |
INSUFFICIENT_CREDITS | 402 | Partner or customer does not have enough credits |
NO_AGENT_MAPPING | 404 | No active Knova agent assigned to the from_number, or outbound is disabled |
NO_OUTBOUND_TRUNK | 422 | The calling route for this number isn’t provisioned yet |
DISPATCH_FAILED | 422 | The call could not be started — retry |
CALL_REJECTED | 422 | The person you called declined or was busy |
CALL_NO_ANSWER | 422 | No answer, or the line was temporarily unavailable |
SIP_TRUNK_FAILURE | 422 | The telephony carrier returned an error |
RATE_LIMITED | 429 | Per-number rate exceeded, or IP blocked after repeated auth failures |
The call-related failures (NO_OUTBOUND_TRUNK, DISPATCH_FAILED, CALL_REJECTED, CALL_NO_ANSWER, SIP_TRUNK_FAILURE) all return HTTP 422 with a JSON body — rather than a 5xx status — so your integration always receives a machine-readable error you can act on.
Full example — curl
curl -X POST https://hub.knotie-ai.pro/api/outbound-call \
-H "Authorization: Bearer owt_xxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"from_number": "+14155551234",
"to_number": "+19876543210",
"dynamic_variables": {
"lead_name": "Sarah",
"outreach_topic": "your Q3 renewal"
},
"metadata": {
"source": "ghl_workflow"
}
}'
Expected response:
{
"success": true,
"call_id": "550e8400-e29b-41d4-a716-446655440000",
"provider": "knova",
"agent_id": "agt_abc123",
"from_number": "+14155551234",
"to_number": "+19876543210",
"provider_response": {
"room_name": "knova-out-agt_abc123-550e8400",
"trunk_mode": "byo"
}
}
Compatibility note
The Knova response envelope is intentionally identical to the Retell outbound response shape. If you already handle Retell outbound call responses in your integration, you do not need to update your response-handling code when switching a number to a Knova agent.