AI Agents API
The AI Agents API manages generate-only AI agents: reusable objects that read a bounded context and emit validated structured output for your journeys and catalog jobs to act on. An agent has no tools and no actions - it never sends, branches, or writes on its own. See the AI Agents dashboard guide for the concepts.
This API mirrors the Settings → AI Agents dashboard surface. Use it to script agent creation, register bring-your-own (BYO) provider keys, dry-run an agent against a sample context, and read run traces.
All endpoints on this page are relative to the base URL: https://api-eu1.joryio.com - see API Overview.
Authentication
Every request is authenticated with either an API key carrying the relevant scope, or a dashboard session (JWT). Agents are workspace-scoped - a key only ever sees and edits its own workspace's agents.
Authorization: Bearer your_api_key_or_jwt
Content-Type: application/json
Scopes per endpoint
Read endpoints need ai_agents:read; write endpoints need ai_agents:write. Dashboard sessions may instead use the settings:read / settings:write role scope the AI surfaces share - either grants access.
| Endpoint | Scope |
|---|---|
POST /ai-agents | ai_agents:write (or settings:write) |
GET /ai-agents | ai_agents:read (or settings:read) |
GET /ai-agents/{id} | ai_agents:read (or settings:read) |
PUT /ai-agents/{id} | ai_agents:write (or settings:write) |
POST /ai-agents/{id}/archive | ai_agents:write (or settings:write) |
DELETE /ai-agents/{id} | ai_agents:write (or settings:write) |
POST /ai-agents/{id}/test | ai_agents:write (or settings:write) |
GET /ai-agents/{id}/runs | ai_agents:read (or settings:read) |
GET /ai-agents/provider-keys | ai_agents:read (or settings:read) |
PUT /ai-agents/provider-keys/{provider} | ai_agents:write (or settings:write) |
DELETE /ai-agents/provider-keys/{provider} | ai_agents:write (or settings:write) |
POST /ai-agents/enrichment/run | ai_agents:write (or settings:write) |
GET /ai-agents/enrichment/jobs | ai_agents:read (or settings:read) |
GET /ai-agents/enrichment/jobs/{jobId} | ai_agents:read (or settings:read) |
Core concepts
Model mode and provider
An agent's modelMode is either managed or byo:
managed- Joryio's hosted Claude model. Theproviderisjoryio. Billed as one credit per run.byo- your own key. Theprovideris one ofanthropic,openai,google,azure,bedrock. Register the key first via the provider-keys endpoints. Billed as a small flat platform fee per run.
Output schema
outputSchema.type is string, number, boolean, or json. For json, supply a fields array of { name, type, description? } where type is a primitive. Set includeExplanation: true to capture the model's reasoning in an explanation field.
Context selectors
contextSelectors is opt-in - the agent reads nothing unless listed: attributeKeys, segmentIds, catalogFields, requiredCatalogFields (catalog fields that must be present - enrichment skips, and never bills, a row missing any of them), includeBrandVoice, includeRecentEngagement, and maskPiiKeys (keys masked before the context reaches the model; PII marked globally under Custom Attributes is also always masked).
Run outcomes
Every run resolves to one outcome: success, fallback, timeout, rate_limited, invalid_config, or budget_exceeded. See the error contract.
Create an agent
Endpoint
POST /ai-agents
Request body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Human-readable name (max 200). |
description | string | No | Optional description (max 1000). |
tags | string[] | No | Workspace tag names for filtering/organizing (each max 60; up to 50). |
instructions | string | Yes | Goal / system instructions, Liquid-templated (max 20000). |
modelMode | string | No | managed (default) or byo. |
provider | string | No | joryio, anthropic, openai, google, azure, bedrock. Defaults to joryio for managed, anthropic for BYO. |
model | string | No | Concrete model id (e.g. claude-opus-4-8). |
thinkingLevel | string | No | minimal, low, medium, or high. |
contextSelectors | object | No | What the agent may read (opt-in). |
outputSchema | object | No | Output shape the model is constrained to. Defaults to { "type": "string" }. |
fallbackValue | any | No | Value returned when a run fails. |
dailyCap | integer | No | Per-agent daily invocation cap (default 250000, min 0). |
guardrails | object | No | maxOutputTokens, timeoutMs, retryOnTransient. |
Example request
curl -X POST https://api-eu1.joryio.com/ai-agents \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Cart subject-line writer",
"instructions": "Write a short, upbeat email subject line for the abandoned cart. Max 60 characters.",
"modelMode": "managed",
"contextSelectors": {
"attributeKeys": ["first_name", "cart_total"],
"includeBrandVoice": true
},
"outputSchema": {
"type": "json",
"fields": [{ "name": "subject", "type": "string" }],
"includeExplanation": true
},
"fallbackValue": { "subject": "You left something behind" },
"dailyCap": 50000,
"guardrails": { "timeoutMs": 20000, "retryOnTransient": true }
}'
Response
{
"id": "8f0e2b3a-1c4d-4e5f-9a0b-1c2d3e4f5a6b",
"organizationId": "org_123",
"workspaceId": "ws_456",
"name": "Cart subject-line writer",
"description": null,
"status": "active",
"instructions": "Write a short, upbeat email subject line for the abandoned cart. Max 60 characters.",
"modelMode": "managed",
"provider": "joryio",
"model": "",
"thinkingLevel": null,
"contextSelectors": {
"attributeKeys": ["first_name", "cart_total"],
"includeBrandVoice": true
},
"outputSchema": {
"type": "json",
"fields": [{ "name": "subject", "type": "string" }],
"includeExplanation": true
},
"fallbackValue": { "subject": "You left something behind" },
"dailyCap": 50000,
"guardrails": { "timeoutMs": 20000, "retryOnTransient": true },
"createdBy": "user_789",
"createdAt": "2026-07-11T09:00:00.000Z",
"updatedAt": "2026-07-11T09:00:00.000Z"
}
List agents
Endpoint
GET /ai-agents
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
status | string | - | Optional filter: active or archived. |
Agents are returned newest-updated first.
Example request
curl -X GET "https://api-eu1.joryio.com/ai-agents?status=active" \
-H "Authorization: Bearer your_api_key"
Response
[
{
"id": "8f0e2b3a-1c4d-4e5f-9a0b-1c2d3e4f5a6b",
"name": "Cart subject-line writer",
"status": "active",
"modelMode": "managed",
"provider": "joryio",
"dailyCap": 50000,
"updatedAt": "2026-07-11T09:00:00.000Z"
}
]
Get one agent
Endpoint
GET /ai-agents/{id}
The path parameter {id} is the agent's UUID.
Example request
curl -X GET https://api-eu1.joryio.com/ai-agents/8f0e2b3a-1c4d-4e5f-9a0b-1c2d3e4f5a6b \
-H "Authorization: Bearer your_api_key"
Returns the full agent object (same shape as the create response). Returns 404 if the agent does not exist in this workspace.
Update an agent
Endpoint
PUT /ai-agents/{id}
Partial update - send only the fields you want to change. All create fields are accepted, plus status (active or archived).
Example request
curl -X PUT https://api-eu1.joryio.com/ai-agents/8f0e2b3a-1c4d-4e5f-9a0b-1c2d3e4f5a6b \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{
"dailyCap": 100000,
"guardrails": { "timeoutMs": 15000, "retryOnTransient": false }
}'
Returns the updated agent object.
Archive an agent
Soft-archive an agent: status becomes archived, which halts use but keeps its run history.
Endpoint
POST /ai-agents/{id}/archive
Example request
curl -X POST https://api-eu1.joryio.com/ai-agents/8f0e2b3a-1c4d-4e5f-9a0b-1c2d3e4f5a6b/archive \
-H "Authorization: Bearer your_api_key"
Returns the archived agent object ("status": "archived").
Delete an agent
Endpoint
DELETE /ai-agents/{id}
Example request
curl -X DELETE https://api-eu1.joryio.com/ai-agents/8f0e2b3a-1c4d-4e5f-9a0b-1c2d3e4f5a6b \
-H "Authorization: Bearer your_api_key"
Response
{ "success": true }
Test (preview) an agent
Dry-run the agent against a sample context you supply. The run uses a fresh run key and surface test, so it never counts against a real journey. Returns just the customer-facing result - no metering fields.
Endpoint
POST /ai-agents/{id}/test
Request body
| Field | Type | Required | Description |
|---|---|---|---|
attributes | object | No | Sample contact attributes keyed by name. |
segmentMemberships | string[] | No | Sample segment memberships. |
catalogRecord | object | No | Sample catalog/entity record being enriched. |
engagement | object | No | Sample recent-engagement summary. |
Example request
curl -X POST https://api-eu1.joryio.com/ai-agents/8f0e2b3a-1c4d-4e5f-9a0b-1c2d3e4f5a6b/test \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{
"attributes": { "first_name": "Dana", "cart_total": 249.90 },
"segmentMemberships": ["vip"]
}'
Response
{
"outcome": "success",
"output": { "subject": "Dana, your cart misses you" },
"explanation": "Used the first name and an upbeat tone from the brand voice."
}
outcome is one of success, fallback, timeout, rate_limited, invalid_config, or budget_exceeded. explanation is null when the schema does not include one.
List agent runs
Return recent run traces for one agent, newest first.
Endpoint
GET /ai-agents/{id}/runs
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | number | 50 | Rows to return (1–200). |
Example request
curl -X GET "https://api-eu1.joryio.com/ai-agents/8f0e2b3a-1c4d-4e5f-9a0b-1c2d3e4f5a6b/runs?limit=25" \
-H "Authorization: Bearer your_api_key"
Response
{
"rows": [
{
"id": "run_abc123",
"agentId": "8f0e2b3a-1c4d-4e5f-9a0b-1c2d3e4f5a6b",
"surface": "journey",
"provider": "joryio",
"model": "claude-opus-4-8",
"modelMode": "managed",
"inputTokens": 420,
"outputTokens": 28,
"latencyMs": 1180,
"outcome": "success",
"output": { "subject": "Dana, your cart misses you" },
"explanation": "Used the first name and an upbeat tone.",
"error": null,
"createdAt": "2026-07-11T09:05:00.000Z"
}
]
}
The trace stores input references only (which execution, node, record, or user) - never the raw prompt text.
List BYO provider keys
Return the registered bring-your-own provider keys for the workspace. Credential values are never returned - the credentials object is always empty.
Endpoint
GET /ai-agents/provider-keys
Example request
curl -X GET https://api-eu1.joryio.com/ai-agents/provider-keys \
-H "Authorization: Bearer your_api_key"
Response
[
{
"id": "key_111",
"provider": "openai",
"credentials": {},
"label": "Production OpenAI",
"status": "active",
"lastUsedAt": "2026-07-11T08:00:00.000Z",
"lastError": null,
"createdAt": "2026-07-01T00:00:00.000Z",
"updatedAt": "2026-07-11T08:00:00.000Z"
}
]
Upsert a BYO provider key
Create or replace the key for one provider. There is one key per (workspace, provider). The managed joryio provider takes no key and is rejected.
Endpoint
PUT /ai-agents/provider-keys/{provider}
The path parameter {provider} is one of anthropic, openai, google, azure, bedrock.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
credentials | object | Yes | Provider-specific credential values (e.g. { "apiKey": "..." }; Azure/Bedrock take more). Encrypted at rest, never returned. |
label | string | No | Optional label (max 120). |
Example request
curl -X PUT https://api-eu1.joryio.com/ai-agents/provider-keys/openai \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{
"credentials": { "apiKey": "sk-your-openai-key" },
"label": "Production OpenAI"
}'
Response
{
"id": "key_111",
"provider": "openai",
"credentials": {},
"label": "Production OpenAI",
"status": "active",
"lastUsedAt": null,
"lastError": null,
"createdAt": "2026-07-11T09:10:00.000Z",
"updatedAt": "2026-07-11T09:10:00.000Z"
}
Delete a BYO provider key
Endpoint
DELETE /ai-agents/provider-keys/{provider}
Example request
curl -X DELETE https://api-eu1.joryio.com/ai-agents/provider-keys/openai \
-H "Authorization: Bearer your_api_key"
Response
{ "success": true }
Run catalog enrichment
Run a generate-only agent over a custom-entity's records and write each output into a target field - product descriptions, tags, a normalized category. The job is async/queued: this endpoint creates a job (status: queued), enqueues the work, and returns immediately - a large catalog (up to 100k rows) processes off the request path. Poll Get an enrichment job for progress. The job is idempotent per record (the run key is jobId:recordId), so a retried job never re-charges an already-enriched record.
Endpoint
POST /ai-agents/enrichment/run
Request body
| Field | Type | Required | Description |
|---|---|---|---|
agentId | string | Yes | The agent to run (must be an active agent in this workspace). |
entityDefinitionId | string | Yes | The custom-entity definition whose records are enriched. |
targetField | string | Yes | The record field the output is written into (must be a declared field on the entity). |
filter | object | No | Optional MongoDB-style filter narrowing which records are enriched. |
limit | integer | No | Max records to process this job (up to 100000, hard-capped server-side). |
Example request
curl -X POST https://api-eu1.joryio.com/ai-agents/enrichment/run \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{
"agentId": "8f0e2b3a-1c4d-4e5f-9a0b-1c2d3e4f5a6b",
"entityDefinitionId": "product",
"targetField": "ai_description",
"filter": { "category": "shoes" },
"limit": 200
}'
Response
The queued job. status is queued at submit time; the counts fill in as the job runs. Poll Get an enrichment job to watch it complete.
{
"jobId": "job_9a8b7c",
"status": "queued",
"agentId": "8f0e2b3a-1c4d-4e5f-9a0b-1c2d3e4f5a6b",
"entityDefinitionId": "product",
"targetField": "ai_description",
"counts": {
"total": 200,
"processed": 0,
"succeeded": 0,
"failed": 0,
"skipped": 0
},
"error": null,
"createdAt": "2026-07-11T09:20:00.000Z",
"updatedAt": "2026-07-11T09:20:00.000Z"
}
Get an enrichment job
Poll one enrichment job's status and counts.
Endpoint
GET /ai-agents/enrichment/jobs/{jobId}
The path parameter {jobId} is the job id returned by Run catalog enrichment.
Example request
curl -X GET https://api-eu1.joryio.com/ai-agents/enrichment/jobs/job_9a8b7c \
-H "Authorization: Bearer your_api_key"
Response
status is one of queued, running, completed, or failed. error is populated only when the job as a whole failed.
{
"jobId": "job_9a8b7c",
"status": "completed",
"agentId": "8f0e2b3a-1c4d-4e5f-9a0b-1c2d3e4f5a6b",
"entityDefinitionId": "product",
"targetField": "ai_description",
"counts": {
"total": 200,
"processed": 200,
"succeeded": 194,
"failed": 2,
"skipped": 4
},
"error": null,
"createdAt": "2026-07-11T09:20:00.000Z",
"updatedAt": "2026-07-11T09:22:30.000Z"
}
Returns 404 if the job does not exist in this workspace. Needs ai_agents:read (or settings:read).
List enrichment jobs
Return recent enrichment jobs for the workspace, newest first - for progress and history.
Endpoint
GET /ai-agents/enrichment/jobs
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | integer | 20 | Rows to return (newest first). |
Example request
curl -X GET "https://api-eu1.joryio.com/ai-agents/enrichment/jobs?limit=20" \
-H "Authorization: Bearer your_api_key"
Response
An array of jobs, each the same shape as Get an enrichment job.
[
{
"jobId": "job_9a8b7c",
"status": "completed",
"agentId": "8f0e2b3a-1c4d-4e5f-9a0b-1c2d3e4f5a6b",
"entityDefinitionId": "product",
"targetField": "ai_description",
"counts": {
"total": 200,
"processed": 200,
"succeeded": 194,
"failed": 2,
"skipped": 4
},
"error": null,
"createdAt": "2026-07-11T09:20:00.000Z",
"updatedAt": "2026-07-11T09:22:30.000Z"
}
]
Needs ai_agents:read (or settings:read).
Error responses
All errors share the standard shape - there is no separate machine-readable error-code vocabulary; use the HTTP status plus the message field. See Error Response in the API Overview.
{
"statusCode": 404,
"message": "AI agent 8f0e2b3a-1c4d-4e5f-9a0b-1c2d3e4f5a6b not found",
"timestamp": "2026-07-11T09:20:00.000Z",
"path": "/ai-agents/8f0e2b3a-1c4d-4e5f-9a0b-1c2d3e4f5a6b"
}
Notable statuses on this API:
| Status | When |
|---|---|
400 | Invalid configuration - e.g. "instructions are required", "No BYO key configured for provider 'openai' - add a key before using it in BYO mode", "status must be one of: active, draft, archived" |
401 | Missing or invalid API key |
403 | Key lacks the required ai_agents:read / ai_agents:write scope |
404 | Agent, enrichment job, or BYO provider key not found |