curl --request PUT \
--url https://api.erdo.ai/v1/workstreams/{workstreamSlug}/external-refs \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"external_refs": [
{
"account_id": "<string>",
"external_id": "<string>",
"kind": "<string>",
"label": "<string>",
"provider": "<string>"
}
]
}
'import requests
url = "https://api.erdo.ai/v1/workstreams/{workstreamSlug}/external-refs"
payload = { "external_refs": [
{
"account_id": "<string>",
"external_id": "<string>",
"kind": "<string>",
"label": "<string>",
"provider": "<string>"
}
] }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.put(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PUT',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
external_refs: [
{
account_id: '<string>',
external_id: '<string>',
kind: '<string>',
label: '<string>',
provider: '<string>'
}
]
})
};
fetch('https://api.erdo.ai/v1/workstreams/{workstreamSlug}/external-refs', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.erdo.ai/v1/workstreams/{workstreamSlug}/external-refs",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_POSTFIELDS => json_encode([
'external_refs' => [
[
'account_id' => '<string>',
'external_id' => '<string>',
'kind' => '<string>',
'label' => '<string>',
'provider' => '<string>'
]
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.erdo.ai/v1/workstreams/{workstreamSlug}/external-refs"
payload := strings.NewReader("{\n \"external_refs\": [\n {\n \"account_id\": \"<string>\",\n \"external_id\": \"<string>\",\n \"kind\": \"<string>\",\n \"label\": \"<string>\",\n \"provider\": \"<string>\"\n }\n ]\n}")
req, _ := http.NewRequest("PUT", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.put("https://api.erdo.ai/v1/workstreams/{workstreamSlug}/external-refs")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"external_refs\": [\n {\n \"account_id\": \"<string>\",\n \"external_id\": \"<string>\",\n \"kind\": \"<string>\",\n \"label\": \"<string>\",\n \"provider\": \"<string>\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.erdo.ai/v1/workstreams/{workstreamSlug}/external-refs")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Put.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"external_refs\": [\n {\n \"account_id\": \"<string>\",\n \"external_id\": \"<string>\",\n \"kind\": \"<string>\",\n \"label\": \"<string>\",\n \"provider\": \"<string>\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"events": [
{
"actor_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"actor_type": "<string>",
"body": "<string>",
"created_at": "2023-11-07T05:31:56Z",
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"organization_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"workstream_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
],
"following": true,
"phases": [
{
"completed_at": "2023-11-07T05:31:56Z",
"created_at": "2023-11-07T05:31:56Z",
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"skill_slug": "<string>",
"slug": "<string>",
"sort_order": -1,
"started_at": "2023-11-07T05:31:56Z",
"status": "<string>",
"thread_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"title": "<string>",
"updated_at": "2023-11-07T05:31:56Z",
"workstream_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
],
"resources": [
{
"created_at": "2023-11-07T05:31:56Z",
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"phase_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"relationship_type": "<string>",
"resource_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"resource_type": "<string>",
"title": "<string>",
"workstream_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
],
"workstream": {
"autonomy_mode": "<string>",
"completed_at": "2023-11-07T05:31:56Z",
"created_at": "2023-11-07T05:31:56Z",
"description": "<string>",
"escalation_budget_per_day": -1,
"external_refs": [
{
"account_id": "<string>",
"external_id": "<string>",
"kind": "<string>",
"label": "<string>",
"provider": "<string>"
}
],
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"organization_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"primary_thread_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"project_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"slug": "<string>",
"source_template_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"spent_millicents": 123,
"started_at": "2023-11-07T05:31:56Z",
"status": "<string>",
"title": "<string>",
"token_budget_millicents": 123,
"updated_at": "2023-11-07T05:31:56Z"
}
}{
"code": "not_found",
"details": {},
"message": "<string>"
}SetWorkstreamExternalRefsAPI declares which provider-owned objects a
workstream governs — the machine-readable replacement for a campaign allowlist hand-typed into a knowledge record.
REST only, with no MCP mirror, and the asymmetry is the point: an agent must be able to READ the set of live campaigns it may act on, and must not be able to widen it. The consumer holding the mapping declares scope here; agents read it back on every workstream and ledger read.
curl --request PUT \
--url https://api.erdo.ai/v1/workstreams/{workstreamSlug}/external-refs \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"external_refs": [
{
"account_id": "<string>",
"external_id": "<string>",
"kind": "<string>",
"label": "<string>",
"provider": "<string>"
}
]
}
'import requests
url = "https://api.erdo.ai/v1/workstreams/{workstreamSlug}/external-refs"
payload = { "external_refs": [
{
"account_id": "<string>",
"external_id": "<string>",
"kind": "<string>",
"label": "<string>",
"provider": "<string>"
}
] }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.put(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PUT',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
external_refs: [
{
account_id: '<string>',
external_id: '<string>',
kind: '<string>',
label: '<string>',
provider: '<string>'
}
]
})
};
fetch('https://api.erdo.ai/v1/workstreams/{workstreamSlug}/external-refs', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.erdo.ai/v1/workstreams/{workstreamSlug}/external-refs",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_POSTFIELDS => json_encode([
'external_refs' => [
[
'account_id' => '<string>',
'external_id' => '<string>',
'kind' => '<string>',
'label' => '<string>',
'provider' => '<string>'
]
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.erdo.ai/v1/workstreams/{workstreamSlug}/external-refs"
payload := strings.NewReader("{\n \"external_refs\": [\n {\n \"account_id\": \"<string>\",\n \"external_id\": \"<string>\",\n \"kind\": \"<string>\",\n \"label\": \"<string>\",\n \"provider\": \"<string>\"\n }\n ]\n}")
req, _ := http.NewRequest("PUT", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.put("https://api.erdo.ai/v1/workstreams/{workstreamSlug}/external-refs")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"external_refs\": [\n {\n \"account_id\": \"<string>\",\n \"external_id\": \"<string>\",\n \"kind\": \"<string>\",\n \"label\": \"<string>\",\n \"provider\": \"<string>\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.erdo.ai/v1/workstreams/{workstreamSlug}/external-refs")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Put.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"external_refs\": [\n {\n \"account_id\": \"<string>\",\n \"external_id\": \"<string>\",\n \"kind\": \"<string>\",\n \"label\": \"<string>\",\n \"provider\": \"<string>\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"events": [
{
"actor_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"actor_type": "<string>",
"body": "<string>",
"created_at": "2023-11-07T05:31:56Z",
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"organization_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"workstream_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
],
"following": true,
"phases": [
{
"completed_at": "2023-11-07T05:31:56Z",
"created_at": "2023-11-07T05:31:56Z",
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"skill_slug": "<string>",
"slug": "<string>",
"sort_order": -1,
"started_at": "2023-11-07T05:31:56Z",
"status": "<string>",
"thread_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"title": "<string>",
"updated_at": "2023-11-07T05:31:56Z",
"workstream_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
],
"resources": [
{
"created_at": "2023-11-07T05:31:56Z",
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"phase_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"relationship_type": "<string>",
"resource_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"resource_type": "<string>",
"title": "<string>",
"workstream_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
],
"workstream": {
"autonomy_mode": "<string>",
"completed_at": "2023-11-07T05:31:56Z",
"created_at": "2023-11-07T05:31:56Z",
"description": "<string>",
"escalation_budget_per_day": -1,
"external_refs": [
{
"account_id": "<string>",
"external_id": "<string>",
"kind": "<string>",
"label": "<string>",
"provider": "<string>"
}
],
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"organization_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"primary_thread_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"project_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"slug": "<string>",
"source_template_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"spent_millicents": 123,
"started_at": "2023-11-07T05:31:56Z",
"status": "<string>",
"title": "<string>",
"token_budget_millicents": 123,
"updated_at": "2023-11-07T05:31:56Z"
}
}{
"code": "not_found",
"details": {},
"message": "<string>"
}Authorizations
An Erdo API key (erdo_api_...) or scoped token (erdo_token_...).
Path Parameters
Body
Show child attributes
Show child attributes
Response
Success response
(newest first), embedded so cards/agents see activity without a second call.
Show child attributes
Show child attributes
detail page can render its follow toggle without a second call (Wave E).
Show child attributes
Show child attributes
Show child attributes
Show child attributes
Operational state machine for multi-step work (campaign launches, reports, monitors).
Show child attributes
Show child attributes

