See the agents pillar page for what agents are and which plan you need before diving into the API.
A custom agent is a pipeline you build from fixed step types: fetch data, filter it, generate a rewrite, and propose a change (matching it to an entry and having the AI model apply it are one step). There's no arbitrary code to write or review — every step is a small, typed handler on our side, so the security boundary is the step list, not your pipeline logic. Custom agents are gated behind the Enterprise plan (customAgents feature key).
An agent instance is one row: a type, a config, a frequency, and an enabled flag. For type: "custom", config.steps is an array of 1–20 steps. Rows flow through the array in order — each step reads fields other steps added and writes its own, the way a shell pipeline passes lines forward.
| Step type | What it does |
|---|---|
fetch_integration_data | Pulls rows from a connected integration (Google Search Console today) |
filter | Keeps rows where a numeric field passes a comparison |
ai_generate | Rewrites a field's text through the AI content model |
propose_change | Matches each row to an entry via semantic search, then has the AI model merge the row into the target field and queues the result as a change proposal for review |
Chain the five step types above into one steps array, then send it as config on POST /agents with type: "custom". A pipeline that turns high-impression, unanswered search queries into drafted FAQ answers:
{
"envId": "production",
"type": "custom",
"name": "FAQ query miner",
"frequency": "weekly",
"config": {
"steps": [
{
"type": "fetch_integration_data",
"integrationType": "googleSearchConsole",
"siteUrl": "https://example.com/",
"windowDays": 28
},
{ "type": "filter", "field": "impressions", "operator": "gte", "value": 500 },
{
"type": "propose_change",
"templateId": "faq",
"fieldKey": "answer",
"instruction": "Draft a direct, one-paragraph FAQ answer for: {{query}}",
"matchField": "query"
}
]
}
}POST /agents runs the config schema plus each step's own validation before it ever creates the row — a bad templateId or unusable fieldKey comes back as a 400, not a broken agent you find out about after the first run. A propose_change step's fieldKey can be any field type except media or reference — the model is told the field's type and rules (length, min/max, pattern) and its output is re-checked against them before a proposal is ever queued.
| Method | Path | Does |
|---|---|---|
| GET | /agents/types | Lists agent types, customAgents-locked or not, for the org's plan |
| GET | /agents | Lists agent instances in the current org |
| POST | /agents | Creates an instance from a type + config |
| PATCH | /agents/:id | Renames it, edits config, toggles enabled, or changes frequency |
| POST | /agents/:id/run | Triggers a run immediately, even while disabled |
| GET | /agents/:id/runs | Last 50 runs, newest first, with proposedCount and aiTokensSpent |
frequency is "daily" or "weekly". Editing config on PATCH /agents/:id re-validates the whole pipeline the same way POST /agents does — a propose_change step pointed at a templateId/fieldKey that no longer exists fails the patch instead of saving a pipeline that can't produce a proposal.
Three causes, in order of how often they show up:
propose_change found no matching entry. Set matchField to a row field worded closer to how entries are actually written, or point templateId at a template with more coverage of the topic — matching also requires the target entries to already have an embedding, so a template with only a handful of entries may not clear the similarity floor.propose_change stops processing remaining rows rather than failing the run — check aiTokensSpent on GET /agents/:id/runs against the plan's monthly cap.fetch_integration_data is actually pulling reads as "nothing matched" when it's really "nothing passed."GET /agents/:id/runs returns proposedCount and error per run — a 0 with no error means the pipeline ran clean and nothing qualified, not that something broke.
Yes. There's no eval, no user-supplied function, no arbitrary HTTP calls beyond the fixed googleSearchConsole integration path. Every step is one of the five typed handlers above, validated by its own schema before the pipeline saves — the attack surface is exactly as wide as those five step types, not as wide as whatever code an org could otherwise submit. See how the same model powers AI-driven edits more broadly on the MCP server pillar page.
No. Every propose_change step ends in a change proposal, reviewed and approved the same way a prebuilt agent's proposals are — a custom agent never writes to an entry on its own.
Any order the config schema accepts. A propose_change step is self-contained — it matches the entry to update (via semantic search) and applies the change in the same step, so it has no ordering requirement relative to other propose_change or find_interlinks steps.
Google Search Console only, as of the current API version.
No. It runs against Draftbase's own AI content model — no separate API key or provider connection required, only the org's monthly AI token quota.