Build an endpoint
What your endpoint must do to be callable by Assistive — HTTPS, publicly reachable, a JSON object, inside the timeout.
A context source is an HTTP endpoint on your server that we call and that returns JSON. This page is the contract it has to satisfy.
Read Verify our signature alongside this — that's the other half, and it's the half people get wrong.
Requirements
| Requirement | Detail |
|---|---|
| HTTPS | Required. A non-HTTPS URL fails immediately as unreachable. |
| Publicly reachable | Must be on the public internet. See the callout below. |
| Returns a JSON object | Not an array, not a scalar. Anything else is invalid_response. |
| Responds in time | Default 5s, hard cap 10s, configurable per source. |
| Response size | Capped at 1 MiB. Anything beyond that is truncated. |
| Redirects | Not followed. A 3xx is used as-is, which means it won't give us data. |
Your endpoint cannot be on localhost, a VPN, or a private VPC address
This is the first thing people hit, so know it before you start: we block private,
loopback, link-local, multicast, and carrier-grade-NAT (100.64.0.0/10) addresses. It's
an SSRF defence — a context source URL is attacker-influenceable in the general case, so we
will not let one be pointed at internal infrastructure.
We also resolve DNS and then dial the resolved IP directly, so a hostname cannot
re-resolve to an internal address between the check and the connection. A public DNS name
pointing at 10.0.0.5 won't work either.
Practically: you cannot test this against localhost. Use a tunnel that gives you a
public HTTPS hostname, or deploy to a staging host that's genuinely on the internet. Trying
to point a source at your laptop is a dead end, and it fails as
unreachable.
Parameters
When you register a source you declare the parameters it accepts — a name, a type, and whether it's required.
In this version, the agent types the values. A source doesn't automatically receive the ticket's customer, the ticket ID, or anything else about where it was run from. Design your endpoint around values a human can reasonably type — an order number, an email, a shipment ID — not around context you're hoping we'll infer.
Two validation rules your agents will run into, so they're worth knowing:
- An unknown parameter is rejected.
- A required parameter must be present and non-empty.
GET: parameters go into the URL template
Parameters are substituted wherever {name} appears in the URL template, and the values are
URL-path-escaped for you.
Template: https://api.acme.com/shipments/{shipment_id}
Parameter: shipment_id = SHP-9921
We call: https://api.acme.com/shipments/SHP-9921POST: parameters go into a JSON body
Parameters are sent as a flat JSON object:
{ "shipment_id": "SHP-9921" }That body is also what gets signed — see the signing contract.
Pick POST when a value doesn't belong in a URL (it's long, or it's sensitive enough that you
don't want it in your access logs), and GET when you want a plain, cacheable read.
The field map
Your endpoint can return whatever it likes. The field map decides what an agent actually sees.
You map dotted JSON paths in your response to human-readable labels:
json_path | label |
|---|---|
data.status | Status |
data.eta | Estimated arrival |
Given this response:
{
"data": {
"status": "in_transit",
"eta": "2026-07-16",
"carrier_internal_ref": "ZX-88191",
"warehouse_notes": "…"
}
}the agent sees Status: in_transit and Estimated arrival: 2026-07-16. Nothing else.
Only mapped values are surfaced and stored
The raw response body is never persisted. Fields you didn't map — the internal ref, the warehouse notes — are used to produce nothing and kept nowhere.
So there's no need to build a special, trimmed-down endpoint for us. Point us at the read endpoint you already have and let the field map do the narrowing.
If none of your mapped paths resolve, the lookup fails
A field map that resolves nothing against your response is an
invalid_response — not a silent empty result. We'd
rather tell the agent the integration is misconfigured than show them a blank panel and let
them believe there was no data.
Partial resolution is fine. If three paths are mapped and two resolve, the agent sees those two. It's only zero that's an error.
So when a source that used to work starts failing this way, suspect your response shape changed — a renamed field, or a payload that grew a wrapper object.
Custom headers
You can attach static headers to a source, sent on every call. This is where you'd put a credential for your own API if you want a second check on top of the HMAC — an API key, a bearer token, whatever your endpoint already expects.
They're write-only from the agent's point of view: agents never see them. A credential you put here can't be read back out by the people using the source.
The signature is what actually proves the request is from us, so custom headers are belt-and-braces rather than the primary defence. But if your endpoint already sits behind an API gateway that wants a key, this is how you feed it one.