curl --request GET \
--url https://api.peachdeskai.com/api/v1/sequences/{sequence_id}/history \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.peachdeskai.com/api/v1/sequences/{sequence_id}/history"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.peachdeskai.com/api/v1/sequences/{sequence_id}/history', 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.peachdeskai.com/api/v1/sequences/{sequence_id}/history",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.peachdeskai.com/api/v1/sequences/{sequence_id}/history"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.peachdeskai.com/api/v1/sequences/{sequence_id}/history")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.peachdeskai.com/api/v1/sequences/{sequence_id}/history")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"items": [
{
"kind": "version",
"id": "<string>",
"version_number": 123,
"created_at": "2023-11-07T05:31:56Z",
"actor": {
"source": "<string>",
"user_id": 123,
"name": "<string>",
"job": "<string>"
},
"changes": {
"sequence_changes": [
{
"path": "<string>",
"kind": "changed",
"old": "<unknown>",
"new": "<unknown>",
"truncated": false,
"ref": "<string>",
"old_name": "<string>",
"new_name": "<string>"
}
],
"step_changes": [
{
"kind": "added",
"name": "<string>",
"step_type": "<string>",
"step_id": "<unknown>",
"changes": [
{
"path": "<string>",
"kind": "changed",
"old": "<unknown>",
"new": "<unknown>",
"truncated": false,
"ref": "<string>",
"old_name": "<string>",
"new_name": "<string>"
}
]
}
]
}
}
],
"next_cursor": "<string>"
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>",
"input": "<unknown>",
"ctx": {}
}
]
}Get Sequence History
Who changed what in this sequence, when — newest first.
One merged timeline over two records. kind: "version" is a human’s save
of the definition, carrying the diff against the previous save;
kind: "event" is an operation that has no snapshot — contacts added or
re-synced, a status moved, the calling hours replaced. Page with limit
and the opaque next_cursor; a cursor this endpoint did not mint is a
400, never a silent restart from the top.
Accepted gap — what this history CANNOT tell you. An AI_CALL step
stores only a pointer to the agent (config.aiAgentId). The agent’s
prompt — what the call actually says — lives in
workflow_definitions.workflow_json, which is out of this feature’s
scope and is not versioned here. So this trail shows that a step now
dials a different agent, and never that the same agent’s prompt
changed. A reader who sees no agent change must NOT conclude the call
still says the same thing; the script may have been rewritten entirely
with nothing appearing here.
EMAIL steps are the opposite case, and the difference matters. The
builder writes emailSubject and emailBody INLINE into the step’s
config, so an email’s wording IS snapshotted and IS diffed — a rewritten
body appears here in full, bounded only by the length cap that sets
truncated. The gap above is specific to the call agent, whose prompt
lives behind a pointer in another table. (config.template_id is
resolved to a template name if a step ever carries one, but no step in
this product writes that key today.)
Names beside ids: a change tagged with ref carries old_name /
new_name resolved inside your organization. They are null for a deleted
row or an id belonging to another tenant; the raw ids always stand.
A deleted sequence still has a history. Snapshots outlive the sequence
on purpose, so this answers for a sequence_id whose row is gone. A 404
means the id has no history belonging to your organization — not that it
never existed.
A deleted author. actor.user_id and actor.name are both null while
actor.source stays "human_session": a person did this and their
account has since been removed. That is a different fact from the
automation acting, which is source: "system" with a job — and the
schema forbids the runner from ever minting a version at all. Branch on
source, never on user_id being null.
curl --request GET \
--url https://api.peachdeskai.com/api/v1/sequences/{sequence_id}/history \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.peachdeskai.com/api/v1/sequences/{sequence_id}/history"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.peachdeskai.com/api/v1/sequences/{sequence_id}/history', 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.peachdeskai.com/api/v1/sequences/{sequence_id}/history",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.peachdeskai.com/api/v1/sequences/{sequence_id}/history"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.peachdeskai.com/api/v1/sequences/{sequence_id}/history")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.peachdeskai.com/api/v1/sequences/{sequence_id}/history")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"items": [
{
"kind": "version",
"id": "<string>",
"version_number": 123,
"created_at": "2023-11-07T05:31:56Z",
"actor": {
"source": "<string>",
"user_id": 123,
"name": "<string>",
"job": "<string>"
},
"changes": {
"sequence_changes": [
{
"path": "<string>",
"kind": "changed",
"old": "<unknown>",
"new": "<unknown>",
"truncated": false,
"ref": "<string>",
"old_name": "<string>",
"new_name": "<string>"
}
],
"step_changes": [
{
"kind": "added",
"name": "<string>",
"step_type": "<string>",
"step_id": "<unknown>",
"changes": [
{
"path": "<string>",
"kind": "changed",
"old": "<unknown>",
"new": "<unknown>",
"truncated": false,
"ref": "<string>",
"old_name": "<string>",
"new_name": "<string>"
}
]
}
]
}
}
],
"next_cursor": "<string>"
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>",
"input": "<unknown>",
"ctx": {}
}
]
}Authorizations
Interactive user session: the login JWT, sent as Authorization: Bearer <token>.

