curl --request POST \
--url https://app.famulor.io/api/v1/history/actions \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"action": "reanalyze",
"kind": "messaging",
"ids": [
"be4c9efe-0000-4000-8000-000000000010"
]
}
'import requests
url = "https://app.famulor.io/api/v1/history/actions"
payload = {
"action": "reanalyze",
"kind": "messaging",
"ids": ["be4c9efe-0000-4000-8000-000000000010"]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
action: 'reanalyze',
kind: 'messaging',
ids: ['be4c9efe-0000-4000-8000-000000000010']
})
};
fetch('https://app.famulor.io/api/v1/history/actions', 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://app.famulor.io/api/v1/history/actions",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'action' => 'reanalyze',
'kind' => 'messaging',
'ids' => [
'be4c9efe-0000-4000-8000-000000000010'
]
]),
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://app.famulor.io/api/v1/history/actions"
payload := strings.NewReader("{\n \"action\": \"reanalyze\",\n \"kind\": \"messaging\",\n \"ids\": [\n \"be4c9efe-0000-4000-8000-000000000010\"\n ]\n}")
req, _ := http.NewRequest("POST", 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.post("https://app.famulor.io/api/v1/history/actions")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"action\": \"reanalyze\",\n \"kind\": \"messaging\",\n \"ids\": [\n \"be4c9efe-0000-4000-8000-000000000010\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.famulor.io/api/v1/history/actions")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"action\": \"reanalyze\",\n \"kind\": \"messaging\",\n \"ids\": [\n \"be4c9efe-0000-4000-8000-000000000010\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"data": {
"ok": 123,
"failed": 123,
"results": [
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"kind": "call",
"ok": true,
"error": "<string>",
"detail": {}
}
]
}
}{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after the indicated delay."
}
}{
"error": {
"code": "internal_error",
"message": "Request protection is temporarily unavailable."
}
}Run history actions on any channel
Cross-channel superset of POST /calls/actions. Re-evaluate the post-conversation analysis (summary, sentiment, success verdict and extracted fields), start an enhanced re-transcription from a call recording, resend the end-of-conversation webhook, reopen a completed WhatsApp conversation with AI auto-replies, or add/remove the contact phone from the suppression (blacklist) list.
Set kind to select the record type:
call— phone calls, live chat, avatar sessions and WhatsApp voice (idsare call IDs)messaging— WhatsApp, Telegram, Slack, Messenger, Teams, Discord, Google Chat and X conversations (idsare conversation IDs)email— email threads (idsare the thread’s root message ID, which is whatGET /historyreturns as the rowid)
retranscribe is only valid for kind: call, requires exactly one ID, and returns the rounded recording minutes plus the credit quote. resend_webhook always rebuilds the payload from the record as it is stored right now, so a resend after a reanalyze delivers the updated analysis. resume_ai is only valid for kind: messaging and only while a completed WhatsApp conversation’s 24-hour customer service window is still open. It starts a fresh internal inactivity timer without extending the Meta window. blacklist_add and blacklist_remove are phone-based and therefore only valid for kind: call.
Other actions process up to 50 IDs per request. Required scope: calls:write. Blacklist actions also need suppression:write or campaigns:write (unrestricted keys have full access).
remove removes a completed conversation from History, call/history list and detail APIs, and MCP reads without immediately erasing stored data. It preserves billing, usage and statistics. Existing workspace retention rules continue to apply using the original timestamps. Active conversations cannot be removed. An email message ID removes its entire thread. Retries are idempotent. User-owned credentials require workspace owner/admin access; workspace service-account credentials require calls:write. Check each per-record result: active or missing records return ok: false. Previously issued download links and exported copies are not revoked. A new incoming email or messaging reply restores thread visibility; historical imports and outbound messages do not.
curl --request POST \
--url https://app.famulor.io/api/v1/history/actions \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"action": "reanalyze",
"kind": "messaging",
"ids": [
"be4c9efe-0000-4000-8000-000000000010"
]
}
'import requests
url = "https://app.famulor.io/api/v1/history/actions"
payload = {
"action": "reanalyze",
"kind": "messaging",
"ids": ["be4c9efe-0000-4000-8000-000000000010"]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
action: 'reanalyze',
kind: 'messaging',
ids: ['be4c9efe-0000-4000-8000-000000000010']
})
};
fetch('https://app.famulor.io/api/v1/history/actions', 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://app.famulor.io/api/v1/history/actions",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'action' => 'reanalyze',
'kind' => 'messaging',
'ids' => [
'be4c9efe-0000-4000-8000-000000000010'
]
]),
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://app.famulor.io/api/v1/history/actions"
payload := strings.NewReader("{\n \"action\": \"reanalyze\",\n \"kind\": \"messaging\",\n \"ids\": [\n \"be4c9efe-0000-4000-8000-000000000010\"\n ]\n}")
req, _ := http.NewRequest("POST", 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.post("https://app.famulor.io/api/v1/history/actions")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"action\": \"reanalyze\",\n \"kind\": \"messaging\",\n \"ids\": [\n \"be4c9efe-0000-4000-8000-000000000010\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.famulor.io/api/v1/history/actions")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"action\": \"reanalyze\",\n \"kind\": \"messaging\",\n \"ids\": [\n \"be4c9efe-0000-4000-8000-000000000010\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"data": {
"ok": 123,
"failed": 123,
"results": [
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"kind": "call",
"ok": true,
"error": "<string>",
"detail": {}
}
]
}
}{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after the indicated delay."
}
}{
"error": {
"code": "internal_error",
"message": "Request protection is temporarily unavailable."
}
}Authorizations
API key (fam_..., created under Settings → API Keys) or an OAuth 2.0 access token (fam_at_...). REST operations also require API Access for the credential's workspace. Keys can be restricted to scopes such as assistants:read, calls:write, campaigns:write, automations:read, dashboards:read, dashboards:write, leads:write, segments:write, loop:read, loop:write, phone_numbers:write, sip_trunks:write, knowledge:write, voices:read, billing:read, billing:write, settings:write, platform:read, platform:write; a *:write scope implies the matching *:read. Automation and dashboard endpoints also accept the legacy calls:* scope. Keys without scope restrictions have full access within the workspace's available capabilities.
Body
remove, reanalyze, retranscribe, resend_webhook, resume_ai, blacklist_add, blacklist_remove Which History record type the ids address.
call, messaging, email 1 - 50 elementsResponse
Per-record results.
Show child attributes
Show child attributes