curl --request PATCH \
--url https://api.reply.io/v3/ai-sdr/learnings/{id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"isEnabled": false
}
'import requests
url = "https://api.reply.io/v3/ai-sdr/learnings/{id}"
payload = { "isEnabled": False }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({isEnabled: false})
};
fetch('https://api.reply.io/v3/ai-sdr/learnings/{id}', 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.reply.io/v3/ai-sdr/learnings/{id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'isEnabled' => false
]),
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.reply.io/v3/ai-sdr/learnings/{id}"
payload := strings.NewReader("{\n \"isEnabled\": false\n}")
req, _ := http.NewRequest("PATCH", 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.patch("https://api.reply.io/v3/ai-sdr/learnings/{id}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"isEnabled\": false\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.reply.io/v3/ai-sdr/learnings/{id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"isEnabled\": false\n}"
response = http.request(request)
puts response.read_body{
"id": 5498,
"text": "Never open a message with \"Hello\".",
"categories": [
"personalizedMessage"
],
"source": "manual",
"scope": "user",
"sequenceIds": [],
"isEnabled": true,
"createdAt": "2026-09-02T09:14:07Z",
"updatedAt": "2026-09-11T16:41:55Z",
"ownerUserId": 1234,
"isOwn": true,
"conflictState": "takesPrecedence",
"conflicts": [
{
"otherLearningId": 5512,
"otherText": "Always open with a short greeting.",
"categories": [
"personalizedMessage"
],
"state": "takesPrecedence",
"reason": "One learning forbids a greeting the other requires."
}
]
}Update an AI learning
Coming soon. This endpoint will be available by early October 2026.
ai-sdr:write scope (or a broader one that includes it).
Use this endpoint when you need to change part of an existing AI learning without resending all of it; the full updated learning comes back in the response. Send at least one of text, categories, scope, isEnabled and sequenceIds — every field you leave out keeps its current value. Leaving text out is the way to switch a learning on or off without touching its wording: rewording a learning the AI generated turns it into a manually authored one, which can hit the manual limit even though the number of learnings does not change, and the call is then refused with a conflict. sequenceIds names at most 50 sequences with no repeats, and any it adds must be ones you can see; it is required once the learning ends up on the sequence scope and rejected on every other one. Moving a sequence-scoped learning to another scope drops its sequenceIds for you — send scope on its own — but sending sequenceIds alongside a scope that does not take them is still refused. Moving a learning to the workspace or organization scope needs the matching permission, and a learning somebody else set for your team or org cannot be changed by you at all. The AI Learnings feature must be enabled for your team.
curl --request PATCH \
--url https://api.reply.io/v3/ai-sdr/learnings/{id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"isEnabled": false
}
'import requests
url = "https://api.reply.io/v3/ai-sdr/learnings/{id}"
payload = { "isEnabled": False }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({isEnabled: false})
};
fetch('https://api.reply.io/v3/ai-sdr/learnings/{id}', 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.reply.io/v3/ai-sdr/learnings/{id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'isEnabled' => false
]),
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.reply.io/v3/ai-sdr/learnings/{id}"
payload := strings.NewReader("{\n \"isEnabled\": false\n}")
req, _ := http.NewRequest("PATCH", 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.patch("https://api.reply.io/v3/ai-sdr/learnings/{id}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"isEnabled\": false\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.reply.io/v3/ai-sdr/learnings/{id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"isEnabled\": false\n}"
response = http.request(request)
puts response.read_body{
"id": 5498,
"text": "Never open a message with \"Hello\".",
"categories": [
"personalizedMessage"
],
"source": "manual",
"scope": "user",
"sequenceIds": [],
"isEnabled": true,
"createdAt": "2026-09-02T09:14:07Z",
"updatedAt": "2026-09-11T16:41:55Z",
"ownerUserId": 1234,
"isOwn": true,
"conflictState": "takesPrecedence",
"conflicts": [
{
"otherLearningId": 5512,
"otherText": "Always open with a short greeting.",
"categories": [
"personalizedMessage"
],
"state": "takesPrecedence",
"reason": "One learning forbids a greeting the other requires."
}
]
}Authorizations
Authenticate every request with a Bearer token. Pass your Reply API key in the
Authorization header:
Authorization: Bearer <your-api-key>
Get your API key from the Reply dashboard: Settings → API Key.
Path Parameters
AI learning ID
x >= 1Body
Partial update of an AI learning. Send at least one field; every field you leave out keeps its current value.
New wording of the rule. Rewording a learning whose source is aiGenerated turns it into a
manual one.
1 - 1000Where the rule applies
1Where a learning applies. personalizedMessage shapes outbound message drafting, replyHandling
shapes answers to incoming mail. A learning can carry both.
personalizedMessage, replyHandling Who the learning binds. Moving a learning off the sequence scope drops its sequenceIds —
send scope on its own. workspace and organization need the matching permission.
organization, workspace, user, sequence Whether the learning is switched on
Sequences the learning is limited to; any sequence added must be one you can see. Required
once the learning ends up on the sequence scope and rejected on every other scope.
50x >= 1Response
The updated learning
A standing rule that shapes how the AI writes, together with the learnings it contradicts.
Unique identifier of the learning
The standing rule the AI follows
Where the rule applies
Where a learning applies. personalizedMessage shapes outbound message drafting, replyHandling
shapes answers to incoming mail. A learning can carry both.
personalizedMessage, replyHandling Whether the AI derived the learning or a person wrote it
aiGenerated, manual Who the learning binds
organization, workspace, user, sequence Sequences the learning is limited to. Empty unless scope is sequence.
Whether the learning is switched on
When the learning was created (UTC)
When the learning was last changed (UTC). null if it has never been changed.
User ID of the learning's owner. null when the owner is no longer a Reply user.
Whether the learning is yours. false for a learning a team or organization admin set for everyone.
How the learning stands against the learnings it contradicts. An unresolved learning is not applied.
none, overridden, takesPrecedence, unresolved Every other learning this one contradicts. Empty when conflictState is none.
Show child attributes
Show child attributes