API
Everything the CLI does goes through this HTTP API, and the CLI's --json output is the API's response bodies, so the two never disagree.
Authentication
Send a bearer token on every request:
export DROMAD_API_KEY=dmk_… # Account › API keys; shown once
curl -s https://dromad.dev/api/v1/me -H "Authorization: Bearer $DROMAD_API_KEY"
API keys (dmk_…) are for servers, CI and agents; revoke them at Account › API keys. A key acts in one workspace, the one it was made for, and spends that workspace's credit: everything outside it is 404 to the key. The token dromad login stores (dct_…) is a per-device credential for a person, and reaches every workspace they belong to. GET /me lists the workspaces a credential can act in.
Keys can also be managed over the API: GET /api-keys lists them, with last_used_at and revoked_at; POST /api-keys with {"name": "…", "workspace_id": "ws_…"} creates one (for your own workspace if workspace_id is omitted) and returns the secret once, in key; DELETE /api-keys/{id} revokes one.
Errors
Every non-2xx response has one shape:
{"error": {"code": "conflict", "message": "Only a finished run can be shared; dr_… is queued.", "engine": null, "retryable": false}}
code is stable and machine-readable; message is for people; engine names the answer engine when one is at fault; retryable says whether the same request could succeed later. Treat an unknown code as its HTTP status.
| Code |
Status |
Meaning |
invalid_request |
400 |
The request is malformed or asks for something impossible. |
unauthenticated |
401 |
No credential, or a revoked or unknown one. |
insufficient_credit |
402 |
The account's credit does not cover the request. Nothing was created; message gives what it needs, what is available and where to add credit. retryable is false. |
permission_denied |
403 |
The credential cannot do this. |
not_found |
404 |
No such object, or it is not yours. |
conflict |
409 |
The object is not in a state that allows this (sharing an unfinished run). |
rate_limited |
429 |
The account has too many runs queued or running at once. message gives the numbers; retryable is true. |
engine_error |
502 |
An answer engine failed; engine says which. |
internal_error |
500 |
Dromad failed. |
cli_outdated |
426 |
The request came from a dromad CLI older than the server supports. Nothing was run. See below. |
An engine failing inside a prompt does not fail the request: that run is failed with its own error, and the batch is partial.
CLI versions. GET /version needs no credential and returns {"min_cli_version": "0.2.0"}, the oldest dromad CLI the server answers. The CLI names its version in User-Agent (dromad-cli/0.2.2), and a released version below the minimum gets cli_outdated from every other endpoint, before authentication and before anything is created or charged, because commands have changed meaning between versions. Only that User-Agent is checked: your own client is never refused for its version.
IDs and objects
IDs are <prefix>_<ULID> and sort by creation time: ws_ workspace, pr_ project, dr_ run, rq_ batch, fo_ fanout, fa_ questions analysis, ca_ citation analysis, sh_ share, ak_ API key. A run is one engine execution or one page audit; a batch is the runs one prompt request started; a fanout is the prompt runs of a set of prompts on several engines, repeated, with what each searched; an analysis is derived from runs. Finished runs and analyses never change.
Work is filed under a project, and a project belongs to a workspace (Teams). POST /prompts, /fanouts, /questions and /audits take "project": "pr_…" and answer 400 without it; GET /projects lists the ones a credential can use and POST /projects starts one. A citation analysis names no project: it is filed with its runs, which must all be in one. Runs and analyses report project_id and created_by; lists take ?project= and ?workspace=. Anything in a workspace the credential cannot act in is 404, the same as something that does not exist; 403 (permission_denied) means you are in the workspace and only an admin may do that.
Starting work and waiting for it
Work that calls an engine or fetches a page is queued, and the request returns at once:
| Request |
Returns |
POST /prompts |
202 · the batch and its queued runs |
POST /fanouts |
202 · the queued fanout, its runs queued |
POST /fanouts/estimate |
200 · what that fanout would run and its estimated cost; informational, starts and authorizes nothing |
POST /questions |
202 · the queued questions analysis |
POST /audits |
202 · the queued audit run |
POST /citation-analyses |
201 · the finished analysis (it calls no model) |
To wait, fetch the object with ?wait=N (0–30 seconds): the request returns as soon as the object finishes, or after N seconds with it still running. GET /runs/{id}, GET /batches/{id}, GET /fanouts/{id}, GET /analyses/{id} and GET /runs?batch=… all accept it.
AUTH="Authorization: Bearer $DROMAD_API_KEY"
batch=$(curl -s -X POST https://dromad.dev/api/v1/prompts -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"prompt": "best search APIs for AI agents", "engines": ["chatgpt", "claude", "gemini"], "project": "pr_…"}' | jq -r .batch.id)
curl -s "https://dromad.dev/api/v1/batches/$batch?wait=30" -H "$AUTH" | jq '.batch.status'
Run statuses are queued, running, completed and failed. A batch is completed when every run completed, partial when some failed, and failed when all did.
GET /runs and GET /analyses return {"items": [...], "next_cursor": "…"}, newest first. Pass limit (1–200, default 50) and, for the next page, cursor=<next_cursor>. next_cursor is null on the last page.
Sharing
A finished run or analysis can have one public, read-only link. The same four requests exist under /runs/{id}/share and /analyses/{id}/share:
| Request |
Does |
GET …/share |
{"shared": true/false, "share": …}: whether a link is active, and which. |
POST …/share |
Creates the link, or returns the active one. 409 conflict if the object has not finished. |
POST …/share/rotate |
Replaces the link; the old one stops working. |
DELETE …/share |
Stops sharing; the link stops working at once. |
The share object's url is the page to hand out. Shared pages need no account and ask search engines not to index them.
Examples
# What engines search for a set of prompts. The estimate is informational: what
# decides whether the fanout starts is the workspace's available credit (402 if short).
BODY='{"prompts": [{"text": "free web search MCP server for Claude Code"}], "runs": 3, "project": "pr_…"}'
curl -s -X POST https://dromad.dev/api/v1/fanouts/estimate -H "$AUTH" -H 'Content-Type: application/json' -d "$BODY" \
| jq '{executions, total_usd, available_usd, sufficient}'
fanout=$(curl -s -X POST https://dromad.dev/api/v1/fanouts -H "$AUTH" -H 'Content-Type: application/json' -d "$BODY" | jq -r .id)
curl -s "https://dromad.dev/api/v1/fanouts/$fanout?wait=30" -H "$AUTH" \
| jq '.prompts[].engines[] | {engine, runs: [.runs[] | [.queries[].text]]}'
# Questions for a topic
curl -s -X POST https://dromad.dev/api/v1/questions -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"topic": "AI search API", "count": 30, "project": "pr_…"}'
# Count what a set of runs and batches cited
curl -s -X POST https://dromad.dev/api/v1/citation-analyses -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"ids": ["rq_…", "rq_…"], "domains": ["exa.ai"], "brands": ["Exa"]}'
# Audit a page
curl -s -X POST https://dromad.dev/api/v1/audits -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"url": "https://exa.ai/docs/reference/search", "project": "pr_…"}'
# Share a finished analysis
curl -s -X POST https://dromad.dev/api/v1/analyses/ca_…/share -H "$AUTH" | jq -r .url
Reference
Generated from /api/v1/openapi.json, the schema the API itself serves, so it cannot fall behind. Try requests in the interactive explorer.
GET /api/v1/version
The oldest `dromad` CLI this server answers. Needs no credential. An older CLI gets `cli_outdated` (HTTP 426) from every other endpoint, before anything is run or charged; other clients are not checked.
- Auth
- none
- CLI
no command: compare it with `dromad --version`
- 200
- ServerVersion
Account
GET /api/v1/me
The account and credential this request authenticated as.
- Auth
- bearer token
- CLI
dromad whoami
- 200
- Me
GET /api/v1/api-keys
List Api Keys
- Auth
- bearer token
- CLI
Account › API keys, in the browser
- 200
- ApiKeyList
POST /api/v1/api-keys
Create an API key that acts in workspace_id (your own workspace if omitted) and spends its credit. The response carries the key itself, once.
- Auth
- bearer token
- CLI
Account › API keys, in the browser
- Body
- ApiKeyCreateRequest
- 201
- ApiKeyCreated
DELETE /api/v1/api-keys/{key_id}
Revoke Api Key
- Auth
- bearer token
- CLI
Account › API keys, in the browser
path: key_id- string · required
- 204
- No Content
Cli auth
POST /api/v1/cli/auth/start
Start
- Auth
- none
- CLI
dromad login
- Body
- CLIAuthStartRequest
- 200
- CLIAuthStart
POST /api/v1/cli/auth/poll
Poll
- Auth
- none
- CLI
dromad login
- Body
- CLIAuthPollRequest
- 200
- CLIAuthPoll
DELETE /api/v1/cli/auth/token
Revoke the CLI token this request is authenticated with (dromad logout).
- Auth
- bearer token
- CLI
dromad logout
- 204
- No Content
Runs
POST /api/v1/prompts
Run a prompt on one or more engines. Returns the queued batch and its runs at once; wait for them with GET /batches/{id}?wait=30.
- Auth
- bearer token
- CLI
dromad prompt "…"
- Body
- PromptRequest
- 202
- PromptBatch
GET /api/v1/runs
Newest first, across every workspace this credential can act in. project or workspace narrows it; fanout filters to one fanout's runs and batch to one batch, and with wait (seconds, up to 30) the call holds until that batch has finished.
- Auth
- bearer token
- CLI
dromad runs list
query: batch- string | null
query: fanout- string | null
query: project- string | null
query: workspace- string | null
query: limit- integer
query: cursor- string | null
query: wait- integer
- 200
- RunList
GET /api/v1/runs/{run_id}
One run. With wait (seconds, up to 30) the call holds until it has finished.
- Auth
- bearer token
- CLI
dromad runs show dr_…
path: run_id- string · required
query: wait- integer
- 200
- Run
GET /api/v1/batches/{batch_id}
A batch and its runs. With wait (seconds, up to 30) the call holds until every run has finished.
- Auth
- bearer token
- CLI
dromad runs list --batch rq_…
path: batch_id- string · required
query: wait- integer
- 200
- PromptBatch
Analyses
POST /api/v1/citation-analyses
Count what a set of prompt runs cited. ids takes run IDs and batch IDs; the analysis is computed from stored results and returned complete.
- Auth
- bearer token
- CLI
dromad citations rq_… dr_…
- Body
- CitationAnalysisRequest
- 201
- CitationAnalysis
POST /api/v1/questions
Generate the questions people ask about a topic. Returns the queued analysis at once; wait for it with GET /analyses/{id}?wait=30.
- Auth
- bearer token
- CLI
dromad questions "…"
- Body
- QuestionsRequest
- 202
- QuestionsAnalysis
GET /api/v1/analyses
Newest first; kind filters (questions or citations), project or workspace narrows to one.
- Auth
- bearer token
- CLI
dromad analyses list
query: kind- string | null
query: project- string | null
query: workspace- string | null
query: limit- integer
query: cursor- string | null
- 200
- AnalysisList
GET /api/v1/analyses/{analysis_id}
One analysis. With wait (seconds, up to 30) the call holds until it has finished.
- Auth
- bearer token
- CLI
dromad analyses show fa_…
path: analysis_id- string · required
query: wait- integer
- 200
- any
Fanouts
POST /api/v1/fanouts/estimate
What a fanout would run and its estimated cost: prompts × engines × runs prompt runs, each at its engine's current prompt price. Informational: it starts nothing, holds nothing and is not a quote.
- Auth
- bearer token
- CLI
dromad fanout "…" --estimate
- Body
- FanoutRequest
- 200
- FanoutEstimate
POST /api/v1/fanouts
Run every prompt on every engine runs times, web search on, to record what each searched. Returns the queued fanout at once; wait for it with GET /fanouts/{id}?wait=30. The whole fanout is priced and its credit reserved at once: if the workspace's available credit does not cover all of it, none of it starts (insufficient_credit). Each run is then charged when it completes and released if it fails.
- Auth
- bearer token
- CLI
dromad fanout "…"
- Body
- FanoutRequest
- 202
- Fanout
GET /api/v1/fanouts
Newest first, without their observations. project or workspace narrows it.
- Auth
- bearer token
- CLI
dromad fanouts list
query: project- string | null
query: workspace- string | null
query: limit- integer
query: cursor- string | null
- 200
- FanoutList
GET /api/v1/fanouts/{fanout_id}
The fanout and every query observed, nested prompt → engine → run. With wait (seconds, up to 30) the call holds until every run has finished. Each run's answer, sources and raw envelope are on the run (GET /runs/{run_id}).
- Auth
- bearer token
- CLI
dromad fanouts show fo_…
path: fanout_id- string · required
query: wait- integer
- 200
- Fanout
Audits
POST /api/v1/audits
Audit one page. Returns the queued run at once; its result is the audit document once GET /runs/{id}?wait=30 reports it finished.
- Auth
- bearer token
- CLI
dromad audit https://…
- Body
- AuditRequest
- 202
- Run
Sharing
GET /api/v1/runs/{object_id}/share
Whether the object is shared, and its link if it is.
- Auth
- bearer token
path: object_id- string · required
- 200
- ShareStatus
POST /api/v1/runs/{object_id}/share
Share with anyone who has the link. Only finished objects can be shared; asking again returns the same link.
- Auth
- bearer token
- CLI
dromad share dr_…
path: object_id- string · required
- 200
- ShareState
DELETE /api/v1/runs/{object_id}/share
Stop sharing: the link stops working at once.
- Auth
- bearer token
- CLI
dromad unshare dr_…
path: object_id- string · required
- 204
- No Content
POST /api/v1/runs/{object_id}/share/rotate
Replace the link: the old one stops working, the new one is returned.
- Auth
- bearer token
- CLI
dromad share dr_… --rotate
path: object_id- string · required
- 200
- ShareState
GET /api/v1/analyses/{object_id}/share
Whether the object is shared, and its link if it is.
- Auth
- bearer token
path: object_id- string · required
- 200
- ShareStatus
POST /api/v1/analyses/{object_id}/share
Share with anyone who has the link. Only finished objects can be shared; asking again returns the same link.
- Auth
- bearer token
- CLI
dromad share ca_…
path: object_id- string · required
- 200
- ShareState
DELETE /api/v1/analyses/{object_id}/share
Stop sharing: the link stops working at once.
- Auth
- bearer token
- CLI
dromad unshare ca_…
path: object_id- string · required
- 204
- No Content
POST /api/v1/analyses/{object_id}/share/rotate
Replace the link: the old one stops working, the new one is returned.
- Auth
- bearer token
- CLI
dromad share ca_… --rotate
path: object_id- string · required
- 200
- ShareState
Billing
GET /api/v1/pricing
What each operation costs now. Needs no credential. A prompt on several engines costs the sum of its engines' prices.
- Auth
- none
- CLI
dromad pricing
- 200
- RateCard
GET /api/v1/billing
A workspace's credit: what it has, what is held for work in progress, and what new work can use. Your personal workspace unless workspace names another you belong to.
- Auth
- bearer token
- CLI
dromad balance
query: workspace- string | null
- 200
- BillingSummary
GET /api/v1/billing/transactions
Every change to the workspace's balance, newest first, with who caused it.
- Auth
- bearer token
- CLI
Account › Billing, in the browser
query: workspace- string | null
query: limit- integer
query: cursor- string | null
- 200
- TransactionList
GET /api/v1/billing/usage
What was charged, by day, product and engine. Work that failed was not charged and is not listed.
- Auth
- bearer token
- CLI
Account › Billing, in the browser
query: workspace- string | null
query: days- integer
- 200
- UsageReport
Workspaces
GET /api/v1/workspaces
The workspaces this credential can act in, your own first.
- Auth
- bearer token
- CLI
dromad link
- 200
- WorkspaceList
POST /api/v1/workspaces
Start a team workspace. You are its admin.
- Auth
- bearer token
- CLI
Workspaces › New, in the browser
- Body
- WorkspaceCreateRequest
- 201
- Workspace
GET /api/v1/workspaces/{workspace_id}
Get Workspace
- Auth
- bearer token
- CLI
Workspace › Members, in the browser
path: workspace_id- string · required
- 200
- Workspace
PATCH /api/v1/workspaces/{workspace_id}
Admins only.
- Auth
- bearer token
- CLI
Workspace › Settings, in the browser
path: workspace_id- string · required
- Body
- WorkspaceUpdateRequest
- 200
- Workspace
GET /api/v1/workspaces/{workspace_id}/members
List Members
- Auth
- bearer token
- CLI
Workspace › Members, in the browser
path: workspace_id- string · required
- 200
- MemberList
PATCH /api/v1/workspaces/{workspace_id}/members/{member_id}
Admins only. A workspace keeps at least one admin.
- Auth
- bearer token
- CLI
Workspace › Members, in the browser
path: workspace_id- string · required
path: member_id- integer · required
- Body
- MemberUpdateRequest
- 200
- Member
DELETE /api/v1/workspaces/{workspace_id}/members/{member_id}
An admin removes anyone; anyone removes themselves. Their work stays: it is the workspace's.
- Auth
- bearer token
- CLI
Workspace › Members, in the browser
path: workspace_id- string · required
path: member_id- integer · required
- 204
- No Content
GET /api/v1/workspaces/{workspace_id}/invitations
Admins only.
- Auth
- bearer token
- CLI
Workspace › Members, in the browser
path: workspace_id- string · required
- 200
- InvitationList
POST /api/v1/workspaces/{workspace_id}/invitations
Admins only. The response carries the link, once; it is also emailed when this server can send mail (emailed says whether it was).
- Auth
- bearer token
- CLI
Workspace › Members, in the browser
path: workspace_id- string · required
- Body
- InvitationCreateRequest
- 201
- InvitationCreated
DELETE /api/v1/workspaces/{workspace_id}/invitations/{invitation_id}
Revoke Invitation
- Auth
- bearer token
- CLI
Workspace › Members, in the browser
path: workspace_id- string · required
path: invitation_id- string · required
- 204
- No Content
Projects
GET /api/v1/projects
Every project this credential can see, by workspace; workspace narrows to one.
- Auth
- bearer token
- CLI
dromad list
query: workspace- string | null
- 200
- ProjectList
POST /api/v1/projects
Start a project. Any member of the workspace can.
- Auth
- bearer token
- CLI
dromad init
- Body
- ProjectCreateRequest
- 201
- Project
PATCH /api/v1/projects/{project_id}
Rename a project, or say what it is about. Any member can. context replaces the old one whole.
- Auth
- bearer token
- CLI
Projects › the project, in the browser
path: project_id- string · required
- Body
- ProjectUpdateRequest
- 200
- Project
GET /api/v1/projects/{project_id}
Get Project
- Auth
- bearer token
- CLI
dromad status
path: project_id- string · required
- 200
- Project
Schemas
AnalysisSummary
| Field | Type | Notes |
id | string | required |
kind | string | required |
status | string | required |
subject | string | required |
project_id | string | null | |
created_by | string | null | |
run_count | integer | required |
created_at | string (date-time) | required |
completed_at | string (date-time) | null | |
ApiKey
| Field | Type | Notes |
id | string | required |
name | string | required |
prefix | string | required |
workspace_id | string | null | The one workspace this key acts in and whose credit it spends. |
created_at | string (date-time) | required |
last_used_at | string (date-time) | null | |
revoked_at | string (date-time) | null | |
ApiKeyCreateRequest
| Field | Type | Notes |
name | string | required |
workspace_id | string | null | Where the key acts. Your own workspace if omitted. |
ApiKeyCreated
Returned once, at creation. key is never retrievable again.
| Field | Type | Notes |
id | string | required |
name | string | required |
prefix | string | required |
workspace_id | string | null | The one workspace this key acts in and whose credit it spends. |
created_at | string (date-time) | required |
last_used_at | string (date-time) | null | |
revoked_at | string (date-time) | null | |
key | string | required |
ApiKeyList
| Field | Type | Notes |
items | list[ApiKey] | required |
AuditContent
| Field | Type | Notes |
extraction_method | string | null | Which arm of the main-content cascade found the content. |
word_count | integer | |
likely_js_dependent | boolean | null | The served HTML looks like a JavaScript shell (Dromad does not render JS). |
excerpt | string | |
blocks | integer | |
code_blocks | integer | |
tables | integer | |
links | any | |
rendering_signals | object | |
AuditDates
| Field | Type | Notes |
published | string | null | |
modified | string | null | |
last_modified_header | string | null | |
AuditFetch
| Field | Type | Notes |
status | string | required · success, redirect, client_error, server_error, blocked, non_html, fetch_error |
http_status | integer | null | |
redirect_chain | list[RedirectHop] | |
content_type | string | |
server_headers | dict[str, string] | |
elapsed_ms | integer | |
error | string | null | |
truncated | boolean | |
robots_txt | RobotsTxt | null | |
AuditFinding
| Field | Type | Notes |
key | string | required |
version | integer | required |
name | string | required |
category | string | required |
severity | string | required |
evidence_level | string | required |
status | string | required |
reason | string | null | |
items | list[FindingItem] | |
why_it_matters | string | required |
recommendation | string | required |
AuditPage
| Field | Type | Notes |
url | string | required · As requested. |
final_url | string | required · Where the fetch landed after redirects. |
domain | string | null | |
AuditRequest
| Field | Type | Notes |
url | string | required |
project | string | null | The project to file this under (pr_…). |
AuditStructure
| Field | Type | Notes |
headings | list[Heading] | The main-content outline, in order. |
h1_count | integer | h1 headings in the main content's outline. Whether the page has an h1 anywhere is the missing_h1 finding. |
level_skips | list[LevelSkip] | |
Batch
| Field | Type | Notes |
id | string | required |
kind | string | required |
status | string | required |
project_id | string | null | |
created_by | string | null | |
input | object | required |
run_ids | list[string] | required |
created_at | string (date-time) | required |
updated_at | string (date-time) | required |
BillingSummary
| Field | Type | Notes |
workspace_id | string | null | Whose credit this is. |
enabled | boolean | required · False while this server charges for nothing. |
balance_usd | string | required · Purchased plus promotional credit. |
purchased_usd | string | required · Bought. Does not expire. |
promotional_usd | string | required · Given by Dromad. Spent first. |
held_usd | string | required · Set aside for work queued or running. |
available_usd | string | required · Balance less what is held: what new work can use. |
spend_month_to_date_usd | string | required · Charged since the 1st, UTC. |
add_credit_url | string | required |
CLIAuthPoll
| Field | Type | Notes |
status | string | required |
token | string | null | |
token_id | string | null | |
email | string | null | |
CLIAuthPollRequest
| Field | Type | Notes |
device_code | string | required |
CLIAuthStart
What the CLI shows the user, and the secret it polls with.
device_code never leaves the CLI; user_code is what the person compares in the browser before approving.
| Field | Type | Notes |
device_code | string | required |
user_code | string | required |
verification_uri | string | required |
verification_uri_complete | string | required |
expires_in | integer | required |
interval | integer | required |
CLIAuthStartRequest
| Field | Type | Notes |
client_name | string | required |
CitationAnalysis
What a set of prompt runs cited. See :mod:`dromad.schemas.citations` for what each count means and how unknowns are reported.
| Field | Type | Notes |
id | string | required |
status | string | required |
subject | string | required |
project_id | string | null | The project this is filed under. Null on a shared page. |
created_by | string | null | Who made it, by email. Null on a shared page. |
run_ids | list[string] | required |
error | RunError | null | |
created_at | string (date-time) | required |
completed_at | string (date-time) | null | |
kind | string | |
method | string | required |
brands | list[string] | |
target_domains | list[string] | |
totals | CitationTotals | required |
engines | list[EngineSummary] | |
domains | list[DomainRow] | |
pages | list[PageRow] | |
overlap | CitationOverlap | required |
retrieved_not_cited | list[SeenNotCited] | |
textual_mentions | list[TextualMention] | |
domain_coverage | list[DomainCoverage] | |
excluded_runs | list[ExcludedRun] | |
CitationAnalysisRequest
| Field | Type | Notes |
ids | list[string] | required · Run IDs (dr_…) and/or batch IDs (rq_…); a batch contributes all its runs. |
brands | list[string] | Terms to count in answer text (naive, case-insensitive word match). |
domains | list[string] | Domains to report coverage for, e.g. exa.ai. Subdomains count. |
use_project_context | boolean | With no brands and no domains given, use the ones the runs' project records: its domains, and its brands and competitors as terms. What was used is in the analysis, as always. |
CitationOverlap
| Field | Type | Notes |
engines | integer | required |
domains_by_engine_count | dict[str, integer] | required · How many cited domains were cited by 1, 2, … engines. |
pages_by_engine_count | dict[str, integer] | required |
domains_cited_by_every_engine | list[string] | required |
pages_cited_by_every_engine | list[string] | required |
CitationTotals
| Field | Type | Notes |
runs | integer | required |
prompts | integer | required |
engines | integer | required |
citations | integer | required |
unique_cited_urls | integer | required |
unique_cited_domains | integer | required |
sources_observed | integer | required · Every source any answer cited or listed. |
retrieved_not_cited | integer | required · Sources seen in search results or retrieval but not cited. |
citation_status_unknown | integer | required · Sources whose citation status the provider did not expose. |
unresolved_sources | integer | required · Redirect URLs that could not be resolved. |
runs_without_citation_marks | integer | required |
Credential
The credential a request authenticated with.
| Field | Type | Notes |
kind | string | required |
id | string | required |
name | string | required |
DomainCoverage
| Field | Type | Notes |
domain | string | required |
runs | integer | required |
cited_runs | integer | required |
unknown_runs | integer | required · Runs that did not cite it but whose citations were not exposed. |
cited_rate | number | required · cited_runs / runs; a floor when unknown_runs > 0. |
prompts | integer | required |
cited_prompts | integer | required |
citations | integer | required |
pages | list[string] | required |
by_engine | list[EngineCoverage] | required |
prompts_cited | list[string] | required |
prompts_seen_not_cited | list[string] | required · Prompts where it appeared in results but was never cited. |
prompts_absent | list[string] | required · Prompts where it never appeared at all. |
DomainRow
| Field | Type | Notes |
domain | string | required |
citations | integer | required |
pages | integer | required · Distinct cited pages on this domain. |
runs | integer | required |
prompts | integer | required |
engines | list[string] | required |
seen_not_cited | integer | required · Times it was seen in a run without being cited. |
EngineCoverage
| Field | Type | Notes |
engine | string | required |
runs | integer | required |
cited_runs | integer | required |
unknown_runs | integer | required |
EngineSummary
| Field | Type | Notes |
engine | string | required |
runs | integer | required |
runs_with_citations | integer | required |
citations | integer | required |
unique_cited_domains | integer | required |
runs_without_citation_marks | integer | required |
ExcludedRun
| Field | Type | Notes |
run_id | string | required |
reason | string | required |
Fanout
| Field | Type | Notes |
id | string | required |
name | string | required |
status | string | required · Derived from the runs: queued, running, completed, partial or failed. |
project_id | string | null | |
created_by | string | null | |
engines | list[string] | required |
runs | integer | required |
prompt_count | integer | required |
execution_count | integer | required |
counts | dict[str, integer] | required · Executions by run status. |
query_count | integer | required · Queries observed so far. Nothing is deduplicated. |
created_at | string (date-time) | required |
completed_at | string (date-time) | null | When the last run finished, once all have. |
held_usd | string | null | Null while billing is off. |
charged_usd | string | null | |
prompts | list[FanoutPromptObservations] | required |
FanoutEngineObservations
| Field | Type | Notes |
engine | string | required |
query_count | integer | required · Queries observed across this engine's completed runs. Nothing is deduplicated. |
runs | list[FanoutRunObservation] | required |
FanoutEstimate
What a fanout would run, and what that is estimated to cost at the prices in effect now. For showing before starting; asking for one starts nothing. It is not a quote and authorizes nothing: what a fanout may start is decided when it is created, by the credit the workspace then has.
| Field | Type | Notes |
prompts | integer | required |
engines | list[string] | required |
runs | integer | required |
executions | integer | required · prompts × engines × runs: one prompt run each. |
max_executions | integer | required · The most one fanout may run on this server. |
identical_prompts | integer | required · Prompts whose text repeats an earlier one. They are run all the same. |
lines | list[FanoutEstimateLine] | required |
total_usd | string | required · The estimated cost: every prompt run at its engine's current price. A run that fails is not charged. |
pricing_version | string | required |
billing_enabled | boolean | required |
available_usd | string | null | Credit the workspace can use now. Null while billing is off. |
sufficient | boolean | null | Whether that covers the total. Null while billing is off. |
FanoutEstimateLine
| Field | Type | Notes |
engine | string | required |
executions | integer | required |
unit_price_usd | string | required |
amount_usd | string | required |
FanoutList
| Field | Type | Notes |
items | list[FanoutSummary] | required |
next_cursor | string | null | |
FanoutPromptSource
Where a prompt came from, when it came from another Dromad object.
| Field | Type | Notes |
kind | string | |
analysis_id | string | required |
question_id | string | required |
FanoutRequest
| Field | Type | Notes |
name | string | null | Defaults to the first prompt. |
prompts | list[FanoutPromptInput] | required · Kept exactly as given, in this order. A prompt given twice is run twice. |
engines | list[string] | |
runs | integer | Independent executions of each prompt on each engine. |
project | string | null | The project to file this under (pr_…). |
FanoutRunObservation
One execution: what this engine searched this time.
| Field | Type | Notes |
run_id | string | required |
run_index | integer | required · 1-based: which repeat of this prompt on this engine. |
status | string | required |
model | string | null | The model the provider reported using. |
searched | boolean | null | Whether the engine searched. Null if not recorded, or not done. |
search_calls | integer | null | Searches it ran. More than the queries' calls if one showed no query text. |
queries | list[ObservedQuery] | |
error | RunError | null | |
charge | RunCharge | null | |
created_at | string (date-time) | required |
started_at | string (date-time) | null | |
completed_at | string (date-time) | null | |
FanoutSummary
| Field | Type | Notes |
id | string | required |
name | string | required |
status | string | required · Derived from the runs: queued, running, completed, partial or failed. |
project_id | string | null | |
created_by | string | null | |
engines | list[string] | required |
runs | integer | required |
prompt_count | integer | required |
execution_count | integer | required |
counts | dict[str, integer] | required · Executions by run status. |
query_count | integer | required · Queries observed so far. Nothing is deduplicated. |
created_at | string (date-time) | required |
completed_at | string (date-time) | null | When the last run finished, once all have. |
FindingEvidence
| Field | Type | Notes |
label | string | required |
value | any | |
source | string | |
FindingItem
| Field | Type | Notes |
summary | string | required |
subtype | string | null | |
severity | string | required |
details | object | |
evidence | list[FindingEvidence] | |
Heading
| Field | Type | Notes |
level | integer | required |
text | string | required |
Invitation
| Field | Type | Notes |
id | string | required |
email | string | required · Who the admin meant. The link, not the address, is what admits. |
role | string | required |
status | string | required · pending, accepted, revoked or expired. |
invited_by | string | required |
accepted_by | string | null | Who took it, which may not be the address it was sent to. |
created_at | string (date-time) | required |
expires_at | string (date-time) | required |
InvitationCreateRequest
| Field | Type | Notes |
email | string | required |
role | string | admin or member. |
InvitationCreated
Returned once. url is the only copy of the link: Dromad keeps a hash.
| Field | Type | Notes |
id | string | required |
email | string | required · Who the admin meant. The link, not the address, is what admits. |
role | string | required |
status | string | required · pending, accepted, revoked or expired. |
invited_by | string | required |
accepted_by | string | null | Who took it, which may not be the address it was sent to. |
created_at | string (date-time) | required |
expires_at | string (date-time) | required |
url | string | required |
emailed | boolean | required · Whether Dromad sent it. If not, send the link yourself. |
LevelSkip
| Field | Type | Notes |
from_level | integer | required |
to_level | integer | required |
at | string | required |
LinkCounts
Links to http(s) pages. Same-page anchors (#section) and mailto:, tel: and javascript: hrefs are not links here, so an API reference whose body only links to its own parameters has main: 0.
| Field | Type | Notes |
total | integer | |
main | integer | In the main content. |
navigation | integer | In nav, header, footer and sidebars. |
internal | integer | |
external | integer | |
LlmsTxtProbe
| Field | Type | Notes |
kind | string | required |
url | string | required |
http_status | integer | null | |
found | boolean | required · A 200 whose body is not an HTML page. |
links | integer | |
Me
| Field | Type | Notes |
email | string | required |
credential | Credential | required |
workspaces | list[Workspace] | Where this credential can act, your own first. |
Member
| Field | Type | Notes |
id | integer | required |
email | string | required |
name | string | |
role | string | required |
joined_at | string (date-time) | required |
MemberList
| Field | Type | Notes |
items | list[Member] | required |
MemberUpdateRequest
| Field | Type | Notes |
role | string | required · admin or member. |
ObservedQuery
| Field | Type | Notes |
text | string | required |
position | integer | null | 1-based place in the run's envelope. Null if not recorded. |
search_call | integer | null | 1-based search call it was part of. Null if not recorded. |
provider_search_id | string | null | |
PriceRow
| Field | Type | Notes |
product | string | required · prompt, questions, audit or citations. |
engine | string | null | Set where the price depends on the engine. |
unit | string | required · What one unit is: prompt, question, url or analysis. |
unit_price_usd | string | required · Per unit. 0 means included. |
Project
What a workspace is working on. Every member of the workspace sees it and everything filed under it; it never moves to another workspace.
| Field | Type | Notes |
id | string | required |
name | string | required |
slug | string | required |
workspace_id | string | required |
workspace_name | string | required |
context | any | |
created_by | string | required |
created_at | string (date-time) | required |
ProjectContext
What a project's work is about. Recorded, and shown wherever the project is; it changes what a command does only where that command says so.
| Field | Type | Notes |
primary_domain | string | null | |
domains | list[string] | |
brands | list[string] | |
competitors | list[string] | |
ProjectCreateRequest
| Field | Type | Notes |
name | string | required |
workspace_id | string | null | Where to make it. Your own workspace (or the key's) if omitted. |
context | ProjectContext | null | |
ProjectList
| Field | Type | Notes |
items | list[Project] | required |
ProjectUpdateRequest
| Field | Type | Notes |
name | string | null | |
context | ProjectContext | null | Replaces the project's context whole. |
PromptBatch
A prompt batch and its runs: the response to POST /prompts (runs queued) and, once finished, what dromad prompt --json prints.
| Field | Type | Notes |
batch | Batch | required |
runs | list[Run] | required |
PromptRequest
| Field | Type | Notes |
prompt | string | required |
engines | list[string] | |
project | string | null | The project to file this under (pr_…). |
PromptResult
What one engine answered, and the sources it showed.
| Field | Type | Notes |
answer | string | required |
sources | list[SourceObservation] | required |
searches | list[SearchObservation] | required |
citations_exposed | boolean | required · Whether the answer carried any citation marks at all. |
searched | boolean | null | Whether the engine searched the web. Null when that was not recorded. |
search_calls | integer | null | How many searches it ran; one search can carry several queries, or none the provider showed. Null when that was not recorded. |
Question
| Field | Type | Notes |
id | string | required |
text | string | required |
cluster_id | string | required |
intent | string | required |
specificity | string | null | |
QuestionCluster
| Field | Type | Notes |
id | string | required |
name | string | required |
intent | string | required |
question_ids | list[string] | required |
QuestionsAnalysis
| Field | Type | Notes |
id | string | required |
status | string | required |
subject | string | required |
project_id | string | null | The project this is filed under. Null on a shared page. |
created_by | string | null | Who made it, by email. Null on a shared page. |
run_ids | list[string] | required |
error | RunError | null | |
created_at | string (date-time) | required |
completed_at | string (date-time) | null | |
kind | string | |
topic | string | required |
requested_count | integer | required |
questions | list[Question] | |
clusters | list[QuestionCluster] | |
provenance | QuestionsProvenance | null | |
charge | RunCharge | null | Held for requested_count, paid for the questions generated. |
QuestionsProvenance
| Field | Type | Notes |
strategy | string | required |
prompt_version | string | required |
prompt_sha | string | required · SHA-256 of prompt_text. |
prompt_text | string | required · The full generation prompt, as sent. |
model | string | null | |
run_id | string | required |
generated_at | string (date-time) | required |
QuestionsRequest
| Field | Type | Notes |
topic | string | required |
count | integer | How many questions to ask for. Charged for those that come back. |
project | string | null | The project to file this under (pr_…). |
RateCard
The prices in effect. A prompt on several engines costs the sum of its engines' prices; there is no separate price for running all of them.
| Field | Type | Notes |
version | string | required |
effective_from | string (date-time) | required |
currency | string | |
prices | list[PriceRow] | required |
RedirectHop
| Field | Type | Notes |
url | string | required |
status | integer | required |
location | string | required |
RobotsTxt
| Field | Type | Notes |
url | string | required |
http_status | integer | null | |
found | boolean | required |
allows_page | boolean | null | required · Whether robots.txt lets Dromad fetch the page. |
Run
| Field | Type | Notes |
id | string | required |
kind | string | required |
status | string | required |
engine | string | null | |
model | string | null | The model the provider reported using. |
batch_id | string | null | |
fanout_id | string | null | The fanout this run is one execution of, if any. |
project_id | string | null | The project this run is filed under. Null on a shared page. |
created_by | string | null | Who ran it, by email. Null on a shared page. |
input | object | required |
result | PromptResult | AuditResult | null | |
error | RunError | null | |
usage | Usage | null | |
charge | RunCharge | null | What this run costs the account. Null if it was not billable. |
cost_usd | number | null | No longer reported: always null. See `charge`. |
duration_ms | integer | null | |
created_at | string (date-time) | required |
started_at | string (date-time) | null | |
completed_at | string (date-time) | null | |
RunCharge
What one run costs the account. held while the work is queued or running: that much credit is set aside. Then captured (paid; a set of questions pays for the questions it generated, which can be fewer than it held for) or released (the work failed and cost nothing).
| Field | Type | Notes |
status | string | required · held, captured or released. |
amount_usd | string | required · Held while held; paid once captured; 0 if released. |
unit | string | required |
units | integer | required |
unit_price_usd | string | required |
pricing_version | string | required |
RunError
| Field | Type | Notes |
code | string | required |
message | string | required |
engine | string | null | |
retryable | boolean | |
RunList
| Field | Type | Notes |
items | list[RunSummary] | required |
next_cursor | string | null | |
RunSummary
| Field | Type | Notes |
id | string | required |
kind | string | required |
status | string | required |
engine | string | null | |
batch_id | string | null | |
fanout_id | string | null | |
project_id | string | null | |
created_by | string | null | |
subject | string | required · The prompt text, or what else the run was about. |
source_count | integer | null | |
cited_count | integer | null | |
created_at | string (date-time) | required |
completed_at | string (date-time) | null | |
SearchObservation
One query the engine searched, as the provider showed it.
Providers order their search calls; several queries inside one call were issued together and have no order among themselves. The three provenance fields are null on a run recorded before they were kept.
| Field | Type | Notes |
query | string | required |
position | integer | null | 1-based place in the envelope, across the run. |
search_call | integer | null | 1-based search call the query belongs to. |
provider_search_id | string | null | The provider's own ID for that search call. |
SeenNotCited
| Field | Type | Notes |
url | string | required |
domain | string | null | |
runs | integer | required |
engines | list[string] | required |
ServerVersion
| Field | Type | Notes |
min_cli_version | string | required |
ShareState
| Field | Type | Notes |
id | string | required |
url | string | required · The public page. Anyone with it can read the object. |
target_id | string | required |
target_kind | string | required · run or analysis |
created_at | string (date-time) | required |
revoked_at | string (date-time) | null | |
rotated_from | string | null | |
ShareStatus
| Field | Type | Notes |
shared | boolean | required |
share | ShareState | null | |
SourceObservation
| Field | Type | Notes |
url | string | required |
raw_url | string | required · The URL exactly as the engine returned it. |
resolution | string | required |
domain | string | null | required · Registered domain of `url`; null if unresolved. |
domain_hint | string | null | For an unresolved redirect, the domain the engine's title named. A hint only. |
title | string | null | |
cited | boolean | null | required |
citation_count | integer | |
citation_positions | list[integer] | 1-based positions in the answer's sequence of citations. |
search_query_observed | boolean | null | required · Appeared in the results of a search the engine ran. |
retrieval_observed | boolean | null | required · The provider showed that the page's content reached the model. |
TextualMention
Naive by design: a case-insensitive whole-word match of the term in answer text. A single-word brand that is also an ordinary word, or a brand written differently, will be miscounted.
| Field | Type | Notes |
term | string | required |
runs_mentioning | integer | required |
runs | integer | required |
prompts_mentioning | integer | required |
prompts | integer | required |
occurrences | integer | required |
runs_mentioning_by_engine | dict[str, integer] | required |
Transaction
One change to the balance. Holds are not transactions: only money that moved is.
| Field | Type | Notes |
id | string | required |
kind | string | required · promo_grant, purchase, usage, reversal or adjustment. |
bucket | string | required · promo or purchased. |
amount_usd | string | required · Signed: above zero adds credit. |
description | string | required |
operation_id | string | null | For usage: the batch, analysis or run that was charged. |
run_id | string | null | |
actor | string | null | Who spent or bought it, by email. Null for credit Dromad gave. |
api_key_id | string | null | The API key the charged work was started with, if any. |
created_at | string (date-time) | required |
TransactionList
| Field | Type | Notes |
items | list[Transaction] | required |
next_cursor | string | null | |
Usage
| Field | Type | Notes |
input_tokens | integer | null | |
output_tokens | integer | null | |
reasoning_tokens | integer | null | |
total_tokens | integer | null | |
web_searches | integer | null | |
UsageReport
| Field | Type | Notes |
days | integer | required |
total_usd | string | required |
rows | list[UsageRow] | required |
UsageRow
| Field | Type | Notes |
day | string (date) | required |
product | string | required |
engine | string | null | |
unit | string | required |
operations | integer | required · Runs that were charged. |
units | integer | required |
charged_usd | string | required |
Workspace
| Field | Type | Notes |
id | string | required |
name | string | required |
kind | string | required · personal (yours alone) or team. |
role | string | required · Your role in it: admin or member. |
created_at | string (date-time) | required |
WorkspaceCreateRequest
| Field | Type | Notes |
name | string | required |
WorkspaceUpdateRequest
| Field | Type | Notes |
name | string | required |