Partner API
The partner API lets an agency's own tools start demo builds: an assistant
built from a prospect's public website, with a test page link and a list of the
common questions it could not answer. The contract is published as OpenAPI at
<BASE_URL>/v1/openapi.yaml (no key needed to read it).
Plan and design decisions: docs/plans/ProvisioningAPIPlan.md.
How demo builds work end to end, plus the test runbook: Demo builds.
Test tools (tools/partner-api/):
| File | Use |
|---|---|
test-demos.ps1 |
Scripted end-to-end check: refusals, a real build, idempotency, polling, delete. Exits non-zero on failure. |
demos.http |
Every request by hand, for the VS Code REST Client extension. |
verify-signature.js |
Verify a captured callback's X-ChatOps-Signature, or sign a sample body. |
Keys
- Created by an agency manager in Agency Console → Demo builds → API keys.
- Every key expires: 30, 90 or 180 days. Six months is the hard maximum. To rotate, create a new key, switch your tools over, then revoke the old one.
- The key (
cop_live_…) and its webhook signing secret (whsec_…) are shown once, at creation. We store only a hash of the key. The signing secret is stored encrypted (INTEGRATION_ENCRYPTION_KEY); without that variable, keys cannot be created. - Send the key as
Authorization: Bearer cop_live_…, or asX-API-Keyfor tools that only allow a plain header. - 60 requests per minute per key.
- A key can carry a default callback URL, used when a request does not name
its own
callbackUrl.
Endpoints
| Method | Path | What it does |
|---|---|---|
| POST | /v1/demos |
Start a build. 202 with the new build; 200 with the existing one when externalRef was used before. |
| GET | /v1/demos?externalRef=… |
Find a build by your own id. |
| GET | /v1/demos/{id} |
Read a build. |
| DELETE | /v1/demos/{id} |
Delete it now. The link stops working immediately. |
Request body for POST /v1/demos:
{
"businessName": "Calm Harbor Therapy",
"websiteUrl": "calmharbortherapy.com",
"contactEmail": "owner@calmharbortherapy.com",
"externalRef": "ghl_contact_8f2k1",
"industry": "therapy",
"region": "US",
"callbackUrl": "https://services.leadconnectorhq.com/hooks/…"
}
Only websiteUrl is required. industry defaults to therapy, region (which
crisis lines the demo shows: US or CA) to US.
status is building, then success or failed. A failed build carries a
reason and never a testPageUrl:
| reason | meaning |
|---|---|
invalid_url |
The address is not a public website. |
crawl_blocked |
The site refused us (robots.txt, 403, a bot wall). |
no_content |
Pages loaded but had no readable text. |
screenshot_failed |
The homepage could not be captured. |
ingest_failed |
The knowledge base could not be built. |
timeout / internal |
Our side failed. Safe to retry with a new externalRef. |
Retrying with the same externalRef returns the original build, including
a failed one. To rebuild, DELETE it first or use a new reference.
Limits
- 50 builds per agency per 24 hours. One build of the same site per 10 minutes.
- At most 15 pages of the site are read. robots.txt is respected.
- Unlike every other crawl in the product, a demo build does not require a verified domain. The page cap is the mitigation.
- Demos are deleted 14 days after creation. The link stops working on that date, and an hourly worker sweep then erases the workspace: knowledge base, vectors, chats and files.
Callbacks
When a build finishes, successfully or not, cmd/worker POSTs the result to the
callback URL: the same fields as GET /v1/demos/{id}, plus
"event": "demo.completed" and a deliveryId. Fields worth knowing for no-code
tools:
answers:{"insurance": "not_answered", "fees": "answered", …}, so each result can be mapped to its own contact field by JSON path.unansweredSummary: the unanswered questions, one per line, ready for an email.externalRef: your id, to match the result to the contact.
Signature: X-ChatOps-Signature: t=<unix seconds>,v1=<hex>, where v1 is
HMAC-SHA256 of <t>.<raw request body> keyed with the webhook signing secret.
Reject a mismatch, or a t more than 5 minutes old. partnerapi.Verify is the Go
reference implementation.
Any 2xx response counts as delivered. Anything else is retried after 1 min, 5 min, 15 min, 1 h, 3 h and 6 h, then marked failed. Callback status shows on each build in the Agency Console.
GoHighLevel recipe
Full step-by-step setup, field mapping and curl tests: Connecting GoHighLevel. In short:
- Workflow action Custom Webhook:
POST <BASE_URL>/v1/demos, headerAuthorization: Bearer cop_live_…, JSON body withexternalRefset to the contact id. - Separate workflow with an Inbound Webhook trigger. Put its URL in the API
key's callback URL (or send it as
callbackUrl). - In that workflow, find the contact by
externalRefand mapstatus,testPageUrl,unansweredSummaryandanswers.*onto custom fields. Branch onstatus: send the delivery email onsuccess, notify yourself onfailed.
GHL's inbound webhook cannot verify the signature itself. The callback carries nothing secret beyond the demo link, which is unlisted and expires.
What the demo is (and is not)
A demo workspace is a separate tenant (tenants.is_demo=1) owned through
demo_builds.organization_id, not as an agency subaccount. It collects no contact
details (no pre-chat form, no AI contact capture, no transcript email) and sends
no alerts. The central console hides demo workspaces unless "Show demo
workspaces" is ticked. Its crisis message lists 911 and 988 and says that
nobody is monitoring the chat.