Quickstart
Send a message, get the reply back, and discover + call the capabilities connected to the account. Use any API key with the agent:invoke scope for messaging, and capabilities:read/capabilities:invoke for the capability calls below — mint one under Settings → Developer → API Keys (see Authentication if you don't have one yet). There's no separate sub-key step: one key per integration is enough, and a reseller wanting per-end-customer separation just mints one ordinary key per customer the same way.
1. Send a message
idempotency_key is required — a retried call with the same key returns the same task rather than dispatching twice.
curl -X POST https://ai.synup.com/api/v1/agent/messages \
-H "Authorization: Bearer sy_..." \
-H "Content-Type: application/json" \
-d '{
"idempotency_key": "req-2026-08-31-001",
"message": "What'"'"'s our average review rating this month?"
}'{
"data": {
"id": "task_9f2c...",
"status": "queued",
"stream_url": "https://ai.synup.com/api/v1/agent/stream?token=...",
"stream_refresh_token": "..."
}
}2. Get the reply
Simplest option — poll until status is terminal:
curl https://ai.synup.com/api/v1/agent/messages/task_9f2c... \
-H "Authorization: Bearer sy_..."{
"data": {
"id": "task_9f2c...",
"status": "completed",
"resultText": "Your average rating this month is 4.6, up from 4.4 last month.",
"errorReason": null,
"conversationId": "3f7900ae-34b2-47b0-8254-27864bf3b5c8"
}
}Want the reply live instead of polling, or pushed to you with no polling at all? See Streaming or register a webhook for agent.message.completed in Settings → Developer → Webhooks.
Continuing a conversation
A completed task's conversationId (shown above) is the thread to reuse for a follow-up — pass it back as conversation_id on your next dispatch to keep the same context instead of starting a new conversation. Don't generate this value yourself; it only ever comes from a prior task's response.
curl -X POST https://ai.synup.com/api/v1/agent/messages \
-H "Authorization: Bearer sy_..." \
-H "Content-Type: application/json" \
-d '{
"idempotency_key": "req-2026-08-31-002",
"message": "And how does that compare to last quarter?",
"conversation_id": "3f7900ae-34b2-47b0-8254-27864bf3b5c8"
}'3. See what's connected (optional)
Capabilities only ever come from servers that required the account to actively connect them — a custom MCP server, a Pipedream workflow, or a Synup-provided connector template like Notion, Asana, or HubSpot that the account authorized with its own credentials. The line isn't "did Synup build it" — Synup built Notion's connector too — it's whether the account had to do anything to turn it on. Zero-action defaults every account gets automatically (SEO, content/web-scraping, weather, demographics, and the rest of the built-in toolset) aren't part of this surface regardless; reach those through the Agent instead. If you just want to know what's connected without paying for a full capability fetch, list the servers on their own:
curl https://ai.synup.com/api/v1/capabilities/servers \
-H "Authorization: Bearer sy_..."{
"data": [
{ "name": "mycrm" },
{ "name": "pipedream-billing" }
],
"count": 2
}4. List connected capabilities
Returns every capability connected to this account, up to limit (default 25, max 100) per page. An account with no connected server yet gets { "data": [], "count": 0, "has_next_page": false, "next_cursor": null } — that's the normal, expected response, not an error. A tool's input_schema can be null if one hasn't been cached for it yet — don't assume it's always a populated object.
curl https://ai.synup.com/api/v1/capabilities \
-H "Authorization: Bearer sy_..."{
"data": [
{ "name": "mycrm.lookup_contact", "description": "Look up a contact by email", "input_schema": { "type": "object", "properties": { "email": { "type": "string" } }, "required": ["email"] } },
{ "name": "pipedream-billing.create_invoice", "description": "Draft an invoice for a contact", "input_schema": null }
],
"count": 2,
"has_next_page": false,
"next_cursor": null
}count is the total number of matching capabilities across every page, not this page's data.length — it stays the same as you page through. has_next_pagetells you whether more results exist; when it's true, pass next_cursor back as cursor to fetch the next page:
curl "https://ai.synup.com/api/v1/capabilities?cursor=MjU" \
-H "Authorization: Bearer sy_..."{
"data": [
{ "name": "mycrm.update_contact", "description": "Update a contact's fields", "input_schema": { "type": "object" } }
],
"count": 47,
"has_next_page": false,
"next_cursor": null
}The list is recomputed fresh on every call rather than read from a fixed snapshot, so if a server connects or disconnects while you're mid-walk through a large result set, entries can shift between pages. This is rare in practice — most integrations never notice — but if you need a fully consistent read, re-fetch from page 1 rather than trusting a long cursor walk to stay perfectly stable.
5. Search (optional)
Add ?q= to search by keyword instead of listing everything.
curl "https://ai.synup.com/api/v1/capabilities?q=contact" \
-H "Authorization: Bearer sy_..."6. Filter by server (optional)
Add ?server= to narrow the list to one connected server instead of searching or listing every one of them — useful once you already know which integration you want from step 3. Only that server's tools are fetched, not the whole catalog.
curl "https://ai.synup.com/api/v1/capabilities?server=mycrm" \
-H "Authorization: Bearer sy_..."Comma-separate multiple server names to narrow to more than one:
curl "https://ai.synup.com/api/v1/capabilities?server=mycrm,pipedream-billing" \
-H "Authorization: Bearer sy_..."Combine with ?q=to search within a specific server's tools. A name that doesn't match any connected server just yields an empty list for it — same as a q that matches nothing, never an error.
7. Call one
Pass the name exactly as the list gave it to you, plus whatever arguments that capability expects.
curl -X POST https://ai.synup.com/api/v1/capabilities/call \
-H "Authorization: Bearer sy_..." \
-H "Content-Type: application/json" \
-d '{
"name": "mycrm.lookup_contact",
"arguments": { "email": "jane@example.com" }
}'{
"data": { "id": "c_204", "email": "jane@example.com", "name": "Jane Doe" }
}