Follow an outreach stream's progress: how many people it has reached, on which channels, and how many replied, booked or bought.
Read only, and for server-only applications. People reach a stream as leads (see CRM: leads), never through this endpoint. The guide Outreach streams walks through it.
Get an outreach stream
GET
https://subcontext.com/api/v1/outreach/streams/{id}
Try it
Token scopes:
read or write
An outreach stream's settings and progress: how many people are in it, how many have been messaged, and how many replied, booked, applied and bought. Server-only applications.
Outreach streams must be switched on for your organisation. If they aren't, every stream is 404.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| id required | path | string (uuid) | The stream's id (in the dashboard, in the stream's address). |
Responses
200 The stream.
| Field | Type | Description |
|---|---|---|
| data required | OutreachStream |
An outreach stream and its progress. |
| data.id required | string (uuid) | |
| data.name required | string | |
| data.status required | string |
"active", "cancelled", "closed"
|
| data.archived_at required | string or null (date-time) |
When the stream was archived off the dashboard's streams list; null while it's on it. Archiving changes nothing else. |
| data.kind required | string |
Its shape: "conversation", "sequence", "mailshot", "reminder", "newsletter", "survey"
|
| data.purpose required | string |
What its messages are for, as consent sees it: "service", "first_party_marketing", "third_party_marketing"
|
| data.goal required | string |
What the stream is for. One of:"bookings", "sales", "engagement", "other"
|
| data.channels required | array of string |
The channels it messages on, in order of preference. |
| data.all_channels required | boolean |
Whether each person is messaged on every channel, rather than the first that reaches them. |
| data.follow_up_after_days required | integer |
Days before a follow-up to people who haven't replied (0 for none). |
| data.auto_send required | boolean |
Whether messages go without a person approving each one. |
| data.pace_per_hour required | integer or null |
The most messages sent an hour. |
| data.message_mode required | string |
Messages written for each person ( "drafted", "fixed"
|
| data.from_name required | string or null |
The name its emails come from, when it sets its own (the address doesn't change); null sends them under the name of the agent, or of the team member it's written as. |
| data.reply_email_template_key required | string or null |
The built-in email design (e.g. |
| data.reply_email_template_id required | string or null (uuid) |
One of the organisation's own email templates its emails wear once the person has replied, in place of a built-in one. |
| data.date_column required | string or null |
A reminder: the column of an uploaded list that holds each person's date. |
| data.date_field required | string or null |
A reminder: the lead field (its key) a workflow or the leads list reads each person's date from. |
| data.offset_days required | integer or null |
A reminder: the days from each person's date its first message is sent (negative before it, 0 on the day, positive after). |
| data.send_at_local_time required | string or null |
A reminder or newsletter: the time of day (HH:MM, the organisation's time zone) its messages are sent. |
| data.schedule required | object or null |
A newsletter: when its issues go. |
| data.next_issue_at required | string or null (date-time) |
A newsletter: when its next issue is due. |
| data.skip_if_unchanged required | boolean or null |
A newsletter: whether an issue is skipped when the content it's built from hasn't changed since the last. |
| data.issues_sent_count required | integer or null |
A newsletter: how many issues have gone. |
| data.subscribed_count required | integer or null |
A newsletter: the people subscribed to it now. |
| data.survey required | object or null |
A survey: its |
| data.survey_answered_count required | integer or null |
A survey: how many people have answered. |
| data.awaiting_approval_count required | integer |
Messages waiting for a person to approve them. |
| data.rows_count required | integer |
People in the stream. |
| data.analysed_count required | integer |
People whose message has been prepared. |
| data.skipped_count required | integer |
People left out (no way to reach them, or no consent). |
| data.sent_count required | integer | |
| data.errored_count required | integer | |
| data.outcomes required | object |
How many people replied, booked, started an application and bought. |
| data.outcomes.replied required | integer | |
| data.outcomes.booked required | integer | |
| data.outcomes.applications required | integer | |
| data.outcomes.bound required | integer | |
| data.total_cost_pence required | integer |
What the stream's AI work has cost so far, in whole pence. It's metered in US dollars, so this is converted at today's exchange rate and rounded to the nearest penny. |
| data.cost_ceiling_pence required | integer |
The most it may cost, in whole pence (converted and rounded the same way). The stream stops when it's reached ( |
| data.budget_exceeded required | boolean | |
| data.created_at required | string (date-time) |
{
"data": {
"id": "0199a3c2-8900-7ebb-9b3c-b02132435465",
"name": "Mortgage renewals, October",
"status": "active",
"archived_at": null,
"kind": "conversation",
"purpose": "first_party_marketing",
"goal": "bookings",
"channels": [
"whatsapp",
"email"
],
"all_channels": false,
"follow_up_after_days": 3,
"auto_send": true,
"pace_per_hour": 40,
"message_mode": "drafted",
"from_name": null,
"reply_email_template_key": "plain",
"reply_email_template_id": null,
"date_column": null,
"date_field": null,
"offset_days": null,
"send_at_local_time": null,
"schedule": null,
"next_issue_at": null,
"skip_if_unchanged": null,
"issues_sent_count": null,
"subscribed_count": null,
"survey": null,
"survey_answered_count": null,
"awaiting_approval_count": 0,
"rows_count": 420,
"analysed_count": 418,
"skipped_count": 12,
"sent_count": 396,
"errored_count": 2,
"outcomes": {
"replied": 88,
"booked": 31,
"applications": 9,
"bound": 4
},
"total_cost_pence": 1454,
"cost_ceiling_pence": 3950,
"budget_exceeded": false,
"created_at": "2026-10-01T09:00:00+01:00"
}
}
401 No token was sent, or it isn't valid because it has expired, was revoked (for example because the application's secret was rotated), or was cut short. Get a new token and try once more.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "invalid_token",
"message": "The access token has expired. Ask for a new one."
}
}
403 The token can't be used here. It's missing a scope, it was used from the wrong place (a server-only application's token in a browser, or a browser application's token from a server, another website or a request only server-only applications may make), or it wasn't asked for with `resource=https://subcontext.com/api/v1`.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "forbidden",
"message": "origin 'https://www.example.com' is not allowlisted for this OAuth application"
}
}
404 There's no such record in your organisation.
| Field | Type | Description |
|---|---|---|
| error required | object | |
| error.code required | string |
A short code for the kind of error, such as |
| error.message required | string |
What went wrong and what to do about it, in plain English. |
| error.fields | object |
When the error is about particular inputs, which ones and why,
by name: a nested one with a dot ( |
{
"error": {
"code": "not_found",
"message": "Resource not found"
}
}