Skip to main content

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.

EndpointScope
POST /ai-agentsai_agents:write (or settings:write)
GET /ai-agentsai_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}/archiveai_agents:write (or settings:write)
DELETE /ai-agents/{id}ai_agents:write (or settings:write)
POST /ai-agents/{id}/testai_agents:write (or settings:write)
GET /ai-agents/{id}/runsai_agents:read (or settings:read)
GET /ai-agents/provider-keysai_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/runai_agents:write (or settings:write)
GET /ai-agents/enrichment/jobsai_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. The provider is joryio. Billed as one credit per run.
  • byo - your own key. The provider is one of anthropic, 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

FieldTypeRequiredDescription
namestringYesHuman-readable name (max 200).
descriptionstringNoOptional description (max 1000).
tagsstring[]NoWorkspace tag names for filtering/organizing (each max 60; up to 50).
instructionsstringYesGoal / system instructions, Liquid-templated (max 20000).
modelModestringNomanaged (default) or byo.
providerstringNojoryio, anthropic, openai, google, azure, bedrock. Defaults to joryio for managed, anthropic for BYO.
modelstringNoConcrete model id (e.g. claude-opus-4-8).
thinkingLevelstringNominimal, low, medium, or high.
contextSelectorsobjectNoWhat the agent may read (opt-in).
outputSchemaobjectNoOutput shape the model is constrained to. Defaults to { "type": "string" }.
fallbackValueanyNoValue returned when a run fails.
dailyCapintegerNoPer-agent daily invocation cap (default 250000, min 0).
guardrailsobjectNomaxOutputTokens, 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

ParameterTypeDefaultDescription
statusstring-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

FieldTypeRequiredDescription
attributesobjectNoSample contact attributes keyed by name.
segmentMembershipsstring[]NoSample segment memberships.
catalogRecordobjectNoSample catalog/entity record being enriched.
engagementobjectNoSample 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

ParameterTypeDefaultDescription
limitnumber50Rows 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

FieldTypeRequiredDescription
credentialsobjectYesProvider-specific credential values (e.g. { "apiKey": "..." }; Azure/Bedrock take more). Encrypted at rest, never returned.
labelstringNoOptional 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

FieldTypeRequiredDescription
agentIdstringYesThe agent to run (must be an active agent in this workspace).
entityDefinitionIdstringYesThe custom-entity definition whose records are enriched.
targetFieldstringYesThe record field the output is written into (must be a declared field on the entity).
filterobjectNoOptional MongoDB-style filter narrowing which records are enriched.
limitintegerNoMax 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

ParameterTypeDefaultDescription
limitinteger20Rows 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:

StatusWhen
400Invalid 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"
401Missing or invalid API key
403Key lacks the required ai_agents:read / ai_agents:write scope
404Agent, enrichment job, or BYO provider key not found

Next steps