Skip to content

Automation API

The Automation API records signups, purchases, and refunds, manages Offers, and sends new records to hooks. An automation platform such as Zapier calls it for a creator. The creator grants access to one Workspace through OAuth 2.0.

Base URL: https://app.channelgraph.io/api/v1/automation

The API uses the OAuth 2.0 authorization code grant with PKCE.

Item Value
Authorization endpoint https://app.channelgraph.io/oauth/authorize
Token endpoint https://app.channelgraph.io/oauth/token
Revocation endpoint https://app.channelgraph.io/oauth/revoke
Server metadata https://app.channelgraph.io/.well-known/oauth-authorization-server
Scope automation
Resource indicator https://app.channelgraph.io/api/v1/automation
PKCE Required. Use S256. The server refuses plain.
Client type Confidential. The client authenticates with its secret.
Grant types authorization_code and refresh_token
Access token lifetime One year

ChannelGraph registers each client and issues its client ID and secret. A client registers each redirect URI with ChannelGraph first.

1. Send the creator to the authorization endpoint

Section titled “1. Send the creator to the authorization endpoint”
GET https://app.channelgraph.io/oauth/authorize
?response_type=code
&client_id=<client id>
&redirect_uri=<registered redirect URI>
&scope=automation
&state=<state>
&code_challenge=<S256 challenge>
&code_challenge_method=S256
&resource=https://app.channelgraph.io/api/v1/automation

The creator signs in and selects one of their Workspaces on the consent screen. The grant covers that Workspace only. Then ChannelGraph redirects to redirect_uri with code and state.

Send the resource parameter exactly once with the exact value above. A missing or different value fails with the invalid_target error.

Send a form-encoded POST to the token endpoint. Authenticate the client with HTTP Basic, or send client_id and client_secret in the body.

Terminal window
curl https://app.channelgraph.io/oauth/token \
-d grant_type=authorization_code \
-d code=<code> \
-d redirect_uri=<registered redirect URI> \
-d code_verifier=<PKCE verifier> \
-d client_id=<client id> \
-d client_secret=<client secret> \
-d resource=https://app.channelgraph.io/api/v1/automation
{
"access_token": "<access token>",
"expires_in": 31536000,
"token_type": "Bearer",
"scope": "automation",
"refresh_token": "<refresh token>"
}
Terminal window
curl https://app.channelgraph.io/oauth/token \
-d grant_type=refresh_token \
-d refresh_token=<refresh token> \
-d client_id=<client id> \
-d client_secret=<client secret> \
-d resource=https://app.channelgraph.io/api/v1/automation

The refresh token does not rotate. Thus several runs can refresh one connection at the same time.

Send the access token in the Authorization header.

Terminal window
curl https://app.channelgraph.io/api/v1/automation/me \
-H "Authorization: Bearer <access token>"

The token names the Workspace, so no route takes a Workspace ID. A token stops working when the creator leaves the Workspace or when someone deletes the Workspace.

  • Each request and response body is JSON.
  • Money is a decimal string in major units, such as "49.99". The number of decimal places follows the currency, such as "4900" for JPY.
  • A time is an ISO 8601 string with an offset, such as "2026-10-08T18:24:05+00:00".
  • A record ID is an opaque string.
  • A response can gain new fields. Ignore a field that you do not know.

An error body has a detail field.

{ "detail": "offer_id: no offer in this workspace has this id." }
Status Meaning
401 The token is missing, expired, revoked, or for another resource. The body is {"detail": "Unauthorized"}.
404 The record does not exist.
409 The record conflicts with another one, or the Workspace has reached a limit. A limit error has "code": "limit_reached".
413 The body is larger than 16 KiB.
422 The request is not valid. detail names the field. When the body does not match the schema, detail is a list of field errors.
429 The connection made too many calls. The Retry-After header gives the wait in seconds.

Each client can make 600 calls a minute to one Workspace. The count resets at the start of each minute. Past the limit, a call fails with 429, and Retry-After states the wait.

{ "detail": "Too many attempts. Try again in a minute." }

A signup or a purchase of an Offer.

Field Type Meaning
id string The conversion ID.
kind string signup or purchase.
ts string When the signup or purchase happened.
created_at string When ChannelGraph recorded it.
source string Where it came from: ingress for this API and event ingress, snippet for the site snippet, or an Integration such as gumroad or kit.
offer object The Offer, as {"id", "name"}.
amount string or null What the purchase counts after refunds. Null on a signup, and on a purchase with no amount and no Offer price.
amount_refunded string or null The part that refunds took back. Null when amount is null.
currency string The currency code of both amounts. Empty when there is no amount.
person Person or null The Person who converted. Null when ChannelGraph knows no Person for it.
credited_content array of ContentRef Each piece of Content that earned credit for the conversion.

One human that ChannelGraph knows in the Workspace.

Field Type Meaning
id string The Person ID.
email string Their email address.
created_at string When ChannelGraph first saw them.
purchases integer How many purchases they made.
spent string or null What they spent after refunds, in the Workspace’s display currency.
spent_currency string The Workspace’s display currency.
found_through FoundThrough or null How they first found the creator. Null when ChannelGraph knows no first event.

The first thing a Person did.

Field Type Meaning
kind string content for a click on a link on Content, link for a click on a Freestanding Link, or source for an event that no click explains.
ts string When it happened.
touchpoint_kind string The kind of the first event, such as click or signup.
content ContentRef or null The Content. Null unless kind is content.
link_label string The label of the Freestanding Link. Empty unless kind is link.
source string The system that reported the event, such as kit. Empty unless kind is source.
Field Type Meaning
id string The Content ID.
title string The title.
platform string The platform of its Channel, such as youtube. Empty when it has no Channel.
url string Its address on the platform.
Field Type Meaning
id string The Offer ID.
name string The name.
type string product, service, lead_magnet, or newsletter.
price string or null The price. Null for a free Offer.
currency string The currency of the price. Empty for a free Offer.
created_at string When the Offer was created.

An A/B test of two destinations on one Link.

Field Type Meaning
id string The A/B test ID.
started_at string When it started.
ended_at string or null When it found its winner.
link object The Link under test, as {"id", "label", "url"}. url is the tracked address.
content ContentRef or null The Content that the Link sits on. Null for a Freestanding Link.
winner object or null The winning destination, as {"label", "url"}.
lift number or null The winner’s relative gain over the other destination. 0.18 means 18% better.
{
"id": "7hq2m9x4c1v8d3ka",
"kind": "purchase",
"ts": "2026-10-08T18:24:05+00:00",
"created_at": "2026-10-08T18:24:07+00:00",
"source": "ingress",
"offer": { "id": "p4c6e8g0j2l4n6q8", "name": "Video Planning Course" },
"amount": "49.00",
"amount_refunded": "0.00",
"currency": "USD",
"person": {
"id": "m1b3d5f7h9k1m3p5",
"email": "jordan@example.com",
"created_at": "2026-09-14T16:02:11+00:00",
"purchases": 2,
"spent": "98.00",
"spent_currency": "USD",
"found_through": {
"kind": "content",
"ts": "2026-09-14T15:58:40+00:00",
"touchpoint_kind": "click",
"content": {
"id": "k8v2q4m6t8w0y2a4",
"title": "How I Plan a Week of Videos",
"platform": "youtube",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
},
"link_label": "",
"source": ""
}
},
"credited_content": [
{
"id": "k8v2q4m6t8w0y2a4",
"title": "How I Plan a Week of Videos",
"platform": "youtube",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
}
]
}
{
"id": "t0f2h4k6m8p0r2t4",
"started_at": "2026-09-20T09:00:00+00:00",
"ended_at": "2026-10-07T21:30:00+00:00",
"link": {
"id": "v3g5j7l9n1q3",
"label": "Course link",
"url": "https://go.example.com/course"
},
"content": null,
"winner": { "label": "Sales page B", "url": "https://example.com/course-b" },
"lift": 0.18
}

GET /me

Returns the Workspace and the creator that the token names.

Field Type Meaning
workspace_id string The Workspace ID.
workspace_name string The Workspace name.
user_email string The email address of the creator who granted access.
{
"workspace_id": "w5n7q9s1u3w5y7a9",
"workspace_name": "Jordan Makes Videos",
"user_email": "jordan@example.com"
}

Status codes: 200, 401, 429.

POST /events

Records a signup, a purchase, or a refund. The body is an event with the same fields and rules as event ingress. This route takes signup, purchase, and refund. It refuses identify.

Terminal window
curl https://app.channelgraph.io/api/v1/automation/events \
-H "Authorization: Bearer <access token>" \
-H "Content-Type: application/json" \
-d '{
"type": "purchase",
"offer_id": "p4c6e8g0j2l4n6q8",
"order_id": "1001",
"amount": "49.00",
"currency": "USD",
"email": "jordan@example.com",
"token": "cv_Xq3v9LmA0bC1dE2fG3hI4j"
}'

Response 202:

Field Type Meaning
event_id string The event ID. It is the event_id you sent, or the ID that ChannelGraph derived.
conversion Conversion or null The conversion that the event made or changed. For a refund, this is the refunded purchase. Null when the event made no conversion.
{
"event_id": "purchase:order:p4c6e8g0j2l4n6q8:1001",
"conversion": { "id": "7hq2m9x4c1v8d3ka", "kind": "purchase", "...": "..." }
}

A retry of the same event answers 202 with the same event_id and records nothing new. See idempotency.

Status codes: 202, 401, 413, 422, 429.

GET /offers

Returns the Workspace’s Offers, newest first.

Parameter Type Default Meaning
name string Return only Offers with this exact name. Case does not matter.
q string Return only Offers whose name contains this text. Case does not matter.
limit integer 100 How many Offers to return, from 1 to 100.
offset integer 0 How many Offers to skip, from 0 to 10,000.

The server moves an out-of-range limit or offset to the nearest allowed value.

Terminal window
curl "https://app.channelgraph.io/api/v1/automation/offers?name=Video%20Planning%20Course&limit=1" \
-H "Authorization: Bearer <access token>"
{
"items": [
{
"id": "p4c6e8g0j2l4n6q8",
"name": "Video Planning Course",
"type": "product",
"price": "49.00",
"currency": "USD",
"created_at": "2026-08-02T10:15:00+00:00"
}
]
}

Status codes: 200, 401, 422, 429.

POST /offers

Field Type Required Meaning
name string Yes The name, 1 to 255 characters.
type string No product, service, lead_magnet, or newsletter. The default is product.
price string or number No The price in major units, such as "49.00". Omit it, or send null or "", for a free Offer.
currency string No The three-letter currency code. A price above zero needs one.
description string No Up to 2,000 characters.
Terminal window
curl https://app.channelgraph.io/api/v1/automation/offers \
-H "Authorization: Bearer <access token>" \
-H "Content-Type: application/json" \
-d '{"name": "Video Planning Course", "type": "product", "price": "49.00", "currency": "USD"}'

Response 201: the new Offer.

When an Offer that you created has the name, the response is 409 with that Offer:

{
"detail": "An offer with that name already exists.",
"offer": { "id": "p4c6e8g0j2l4n6q8", "name": "Video Planning Course", "...": "..." }
}

A Workspace holds up to 10,000 Offers. Past that, the response is 409 with "code": "limit_reached".

Status codes: 201, 401, 409, 422, 429.

GET /people/find

Parameter Type Required Meaning
email string Yes The email address. Case does not matter.

Returns {"items": [...]} with the matching Person, or an empty list.

Terminal window
curl "https://app.channelgraph.io/api/v1/automation/people/find?email=jordan@example.com" \
-H "Authorization: Bearer <access token>"

Status codes: 200, 401, 422, 429.

These routes return the newest records of one kind. A platform uses them to show samples while a creator sets up a trigger.

Route Returns
GET /conversions The newest conversions, as {"items": [Conversion]}.
GET /people The newest People with an email address, as {"items": [Person]}.
GET /experiments The newest A/B tests that found a winner, as {"items": [Experiment]}.
Parameter Type Default Meaning
limit integer 3 How many records to return, from 1 to 10.

Status codes: 200, 401, 422, 429.

A hook sends each new record of one kind to a target URL. A platform makes a hook when a creator turns on an instant trigger, and deletes it when the trigger goes off.

Event Sends
conversion.created Each new Conversion.
person.created Each Person who gives an email address for the first time.
experiment.decided Each A/B test that finds its winner. An A/B test that the creator ends does not count.

POST /hooks

Field Type Required Meaning
event string Yes One of the events above.
target_url string Yes Where to send each record, up to 2,000 characters. It must start with https://hooks.zapier.com/.
Terminal window
curl https://app.channelgraph.io/api/v1/automation/hooks \
-H "Authorization: Bearer <access token>" \
-H "Content-Type: application/json" \
-d '{"event": "conversion.created", "target_url": "https://hooks.zapier.com/hooks/standard/123/abc/"}'

Response 201:

Field Type Meaning
id string The hook ID. Keep it to unsubscribe.
event string The event.
target_url string The target URL.
created_at string The hook’s creation time.

A Workspace holds up to 500 hooks. Past that, the response is 409 with "code": "limit_reached".

Status codes: 201, 401, 409, 422, 429.

DELETE /hooks/{hook_id}

Deletes the hook. Only the client that made a hook can delete it.

Status codes: 204, 401, 404, 429. A 404 also means that ChannelGraph already removed the hook.

ChannelGraph sends each record as one JSON object in one POST to the target URL. The body is a Conversion, a Person, or an Experiment.

  • A record goes to each hook at most once.
  • A record waits about 30 seconds before it goes. The wait lets ChannelGraph find its Person and its credited Content.
  • A hook gets only the records that arrive after the platform makes the hook.
  • A record whose own time is more than one day old never goes. Thus history that an Integration brings in sends nothing.
  • A 2xx answer counts as delivered.
  • After any other answer, a timeout, or a network error, ChannelGraph tries again. The wait between tries doubles from one minute up to 15 minutes. The tries stop one hour after the record arrived.
  • A 410 answer deletes the hook.
  • ChannelGraph does not follow a redirect. A request times out after 10 seconds.
  • When the creator who made the hook leaves the Workspace, ChannelGraph deletes the hook.