curl --request POST \
--url https://api.decimal.ai/api/v1/agents \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"agent_name": "<string>",
"description": "<string>",
"system_prompt": "<string>",
"pack": "<string>",
"skill_ids": [
"<string>"
]
}
'import requests
url = "https://api.decimal.ai/api/v1/agents"
payload = {
"agent_name": "<string>",
"description": "<string>",
"system_prompt": "<string>",
"pack": "<string>",
"skill_ids": ["<string>"]
}
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({
agent_name: '<string>',
description: '<string>',
system_prompt: '<string>',
pack: '<string>',
skill_ids: ['<string>']
})
};
fetch('https://api.decimal.ai/api/v1/agents', 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.decimal.ai/api/v1/agents",
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([
'agent_name' => '<string>',
'description' => '<string>',
'system_prompt' => '<string>',
'pack' => '<string>',
'skill_ids' => [
'<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.decimal.ai/api/v1/agents"
payload := strings.NewReader("{\n \"agent_name\": \"<string>\",\n \"description\": \"<string>\",\n \"system_prompt\": \"<string>\",\n \"pack\": \"<string>\",\n \"skill_ids\": [\n \"<string>\"\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://api.decimal.ai/api/v1/agents")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"agent_name\": \"<string>\",\n \"description\": \"<string>\",\n \"system_prompt\": \"<string>\",\n \"pack\": \"<string>\",\n \"skill_ids\": [\n \"<string>\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.decimal.ai/api/v1/agents")
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 \"agent_name\": \"<string>\",\n \"description\": \"<string>\",\n \"system_prompt\": \"<string>\",\n \"pack\": \"<string>\",\n \"skill_ids\": [\n \"<string>\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"agent_name": "travel-planner",
"agent_id": "5f2c1a90-0f1e-4b6c-9a11-2b3c4d5e6f70",
"manifest_id": "9c8b7a60-1122-4c33-8d44-55e66f778899",
"version_label": "v1",
"skills_assigned": 2,
"skills_failed": []
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>",
"input": "<unknown>",
"ctx": {}
}
]
}Create an agent
Create an agent by registering its baseline manifest.
Returns 201 with the new agent’s id, the baseline manifest it was created
from, and how many of the requested skills were assigned. A skill that
can’t be attached — unknown id, seeded demo data, or the plan’s
linked-skill allowance used up — is reported in skills_failed
rather than failing the whole create: the agent is the thing being made
here, and losing it because one skill id was stale would be the wrong
trade. skills_failed_detail carries the reason, and for a plan refusal
an error_code the UI can key an upgrade prompt off.
skill_ids may name your own skills OR public registry skills. Attaching
a registry skill here is free on every plan (2026-08-22 pricing decision:
the moment that demonstrates the product isn’t paywalled) — the Pro+
gate applies to curating an agent’s skills afterwards, not to this. The
quantity cap is unchanged either way.
curl --request POST \
--url https://api.decimal.ai/api/v1/agents \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"agent_name": "<string>",
"description": "<string>",
"system_prompt": "<string>",
"pack": "<string>",
"skill_ids": [
"<string>"
]
}
'import requests
url = "https://api.decimal.ai/api/v1/agents"
payload = {
"agent_name": "<string>",
"description": "<string>",
"system_prompt": "<string>",
"pack": "<string>",
"skill_ids": ["<string>"]
}
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({
agent_name: '<string>',
description: '<string>',
system_prompt: '<string>',
pack: '<string>',
skill_ids: ['<string>']
})
};
fetch('https://api.decimal.ai/api/v1/agents', 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.decimal.ai/api/v1/agents",
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([
'agent_name' => '<string>',
'description' => '<string>',
'system_prompt' => '<string>',
'pack' => '<string>',
'skill_ids' => [
'<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.decimal.ai/api/v1/agents"
payload := strings.NewReader("{\n \"agent_name\": \"<string>\",\n \"description\": \"<string>\",\n \"system_prompt\": \"<string>\",\n \"pack\": \"<string>\",\n \"skill_ids\": [\n \"<string>\"\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://api.decimal.ai/api/v1/agents")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"agent_name\": \"<string>\",\n \"description\": \"<string>\",\n \"system_prompt\": \"<string>\",\n \"pack\": \"<string>\",\n \"skill_ids\": [\n \"<string>\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.decimal.ai/api/v1/agents")
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 \"agent_name\": \"<string>\",\n \"description\": \"<string>\",\n \"system_prompt\": \"<string>\",\n \"pack\": \"<string>\",\n \"skill_ids\": [\n \"<string>\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"agent_name": "travel-planner",
"agent_id": "5f2c1a90-0f1e-4b6c-9a11-2b3c4d5e6f70",
"manifest_id": "9c8b7a60-1122-4c33-8d44-55e66f778899",
"version_label": "v1",
"skills_assigned": 2,
"skills_failed": []
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>",
"input": "<unknown>",
"ctx": {}
}
]
}Authorizations
Enter your API key (e.g. dai_sk_test_key_001)
Cookies
Body
Body of POST /api/v1/agents.
agent_name is Optional[str] here despite being REQUIRED by the
contract, and that is on purpose: declaring it str would make a missing
field a FastAPI 422 while an empty-string field is a 400, so the caller
would have to handle two error shapes for one mistake. Optional + an
explicit check in the handler routes every name problem — absent, empty,
wrong shape, reserved prefix — through the same 400 with the same message
field. (A genuinely non-string value, e.g. {"agent_name": 12}, is still a
422; pydantic rejects it before the handler runs.)
Lowercase letters, digits, hyphens and underscores; 1–200 chars. Becomes the agent's URL path segment and must be unique per org.
Free-text note about what this agent does.
The agent's system prompt. Becomes version 1 of the agent's prompt object, which is what load_agent() reads and what the dashboard edits; it is also recorded as a prompt component on the baseline manifest, which goes on describing what RAN. Max 100,000 characters.
100000The role pack this agent was created from — an archetype slug from GET /api/v1/registry/packs. Version 1 of the prompt is then recorded with provenance='pack', which is what makes 'did they keep the starter?' answerable later. When system_prompt is omitted, the pack's starter prompt is used as-is.
Skills to attach to the new agent. Either one of your org's own skills, or any public registry skill — attaching registry skills while creating an agent is free on every plan (curating them afterwards is Pro+). Capped by the plan's linked-skill allowance; an id that is neither is reported in skills_failed.
Response
Successful Response
The response is of type Response Create Agent Api V1 Agents Post · object.