1Overview & how it works
The CoModel AI API is a secure, versioned, machine-to-machine interface for building agents on top of a published decision model. An agent authenticates, discovers the models it is allowed to use, runs deterministic queries against a frozen snapshot, invokes allowlisted action templates, and prepares approved write-backs that it executes in its own system.
run_id never changes.Base URL & versioning
Every endpoint lives under https://comodel.ai/v1. The current API version is v1; each response echoes it in the api_version body field and the X-API-Version header.
What you need before you start
- A published model — only models in the Published state are reachable; a published model exposes exactly one frozen run.
- API client credentials — a
client_idandclient_secretissued by an account admin from the workspace. Each client is scoped to specific models and a specific set of permissions (scopes). - The model's API vocabulary — the metric ids the model exposes to the API. Discover them with
prepare_agent_context(below).
2Authentication & tokens
The API uses an OAuth-style client-credentials exchange. You trade your client_id and client_secret for a short-lived Bearer token, then send that token on every subsequent call.
Request fields: client_id and client_secret are required. scopes (optional) is a list that must be a subset of the scopes your client is permitted; omit it to receive all of them. expires_in (optional, seconds) may request a shorter token lifetime — tokens default to 1 hour and are capped at 1 hour.
curl -s -X POST https://comodel.ai/v1/auth/token \
-H "Content-Type: application/json" \
-d '{
"client_id": "cli_9f3a...",
"client_secret": "cms_xxxxxxxxxxxxxxxx",
"scopes": ["models:read", "runs:read", "queries:write", "agents:invoke"]
}'Response — the standard success envelope plus the token fields:
{
"api_version": "v1",
"request_id": "req_5c1d8e2f4a9b46d7a0c3e1f2",
"access_token": "eyJ...signed-token...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "models:read runs:read queries:write agents:invoke",
"token_id": "a1b2c3d4e5f6"
}Sending the token
Put the token in the Authorization header on every other endpoint:
Authorization: Bearer eyJ...signed-token...
Inspect the calling token — useful for confirming which models and scopes it can reach:
{
"api_version": "v1",
"request_id": "req_...",
"client_id": "cli_9f3a...",
"account_id": "acct_7b21...",
"scopes": ["models:read", "runs:read", "queries:write", "agents:invoke"],
"allowed_model_ids": ["mdl_42af..."],
"token_id": "a1b2c3d4e5f6"
}3API conventions
Success envelope
Every /v1 success response carries api_version and a unique request_id, plus model_id and run_id where they apply. The endpoint's own data is merged at the top level alongside these fields:
{
"api_version": "v1",
"request_id": "req_...",
"model_id": "mdl_42af...",
"run_id": "run_88c1...",
"result": { "...": "endpoint-specific payload" }
}Response headers
Every /v1 response (success or error) includes X-Request-Id and X-API-Version. Log the request id — it matches the request_id in the body and identifies the call in the audit trail.
Error contract
There is a single error shape across the whole API. Errors never use the success envelope:
{
"error": {
"code": "INSUFFICIENT_SCOPE",
"message": "This token lacks the required scope 'queries:write'.",
"request_id": "req_...",
"details": { "required_scope": "queries:write" }
}
}Scopes
A token only carries the scopes you were granted. The scopes relevant to building agents on a published model are:
models:read— list and inspect published models.runs:read— inspect a published model's frozen run.queries:write— run deterministic queries.agents:invoke— invoke agent action templates.admin:manage— advanced diagnostics (adds a raw block of internal matrices / detector evidence to query and agent responses).
sources:read, sources:write and sync:run — govern the separate data-ingestion (connector) surface and are not part of building an agent on an already-published model, so they are out of scope for this guide.Rate limits & payload size
- The token endpoint is limited to 10 requests/minute per client by default; exceeding it returns
429 RATE_LIMITED. - Request bodies are capped at 200 MB; a larger body returns
413 PAYLOAD_TOO_LARGE.
4Discovering models & runs
List the published models this client is allowed to use.
{
"api_version": "v1",
"request_id": "req_...",
"models": [
{ "model_id": "mdl_42af...", "name": "Retail Margin Model", "status": "published" }
],
"count": 1
}Inspect a single model. run_id is the model's frozen published snapshot; vocabulary_size is the number of API-visible metrics.
{
"api_version": "v1",
"request_id": "req_...",
"model_id": "mdl_42af...",
"run_id": "run_88c1...",
"name": "Retail Margin Model",
"status": "published",
"vocabulary_size": 12
}A published model exposes exactly one run — its frozen snapshot. Any other run id returns 404 NOT_FOUND.
{
"api_version": "v1",
"request_id": "req_...",
"model_id": "mdl_42af...",
"run_id": "run_88c1...",
"status": "published"
}5Deterministic queries
Run a deterministic query against the frozen published snapshot. The call is side-effect-free: it only reads the snapshot. The body is { "query_type": ..., "input": { ... } }.
Every query returns a uniform structured result under result:
query_type— the query that produced this answer.summary— a human-readable one-liner rendered from the data.answer— the query-specific payload (shapes shown per type below).expected_effects— a normalized list of metric/direction/effect entries.direction/confidence— headline direction and confidence when applicable, elsenull.traceability—{ metrics_used, relationships_used, levers_used, facts_used }showing exactly what the answer was built from.limits— any caveats (e.g.uncertainty_band_unavailable).
facts_used is always empty: business facts are inert at publish. Pass the admin:manage scope to additionally receive a raw diagnostics block and advanced_diagnostics: true at the top level.metric_drivers
What most influences a target metric. Input: target_metric (required); top_k and rank_by (direct | propagated | direct_plus_propagated) are optional.
curl -s -X POST https://comodel.ai/v1/models/mdl_42af.../query \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "query_type": "metric_drivers", "input": { "target_metric": "gross_margin" } }'{
"api_version": "v1", "request_id": "req_...",
"model_id": "mdl_42af...", "run_id": "run_88c1...",
"advanced_diagnostics": false,
"result": {
"query_type": "metric_drivers",
"summary": "Top 2 driver(s) of gross_margin.",
"answer": {
"summary": "Top 2 driver(s) of gross_margin.",
"drivers": [
{ "metric": "discount_rate", "direction": "negative",
"direct_effect": -0.41, "propagated_effect": -0.06,
"total_effect": -0.47, "confidence": 0.82 }
]
},
"expected_effects": [
{ "metric": "discount_rate", "direction": "negative", "effect": -0.47, "confidence": 0.82 }
],
"direction": null, "confidence": 0.82,
"traceability": { "metrics_used": ["gross_margin", "discount_rate"],
"relationships_used": [{ "source": "discount_rate", "target": "gross_margin" }],
"levers_used": ["discount_rate"], "facts_used": [] },
"limits": []
}
}downstream_impact
What a source metric flows into. Input: source_metric (required).
{
"result": {
"query_type": "downstream_impact",
"summary": "Top 3 downstream impact(s) of discount_rate.",
"answer": { "summary": "...", "impacts": [
{ "metric": "gross_margin", "direction": "negative", "delta": -0.47 }
] },
"expected_effects": [ { "metric": "gross_margin", "direction": "negative", "effect": -0.47 } ],
"direction": null, "confidence": null,
"traceability": { "metrics_used": ["discount_rate", "gross_margin"],
"relationships_used": [{ "source": "discount_rate", "target": "gross_margin" }],
"levers_used": ["discount_rate"], "facts_used": [] },
"limits": []
}
}simulate_scenario
Apply a percentage change to a tunable lever and read the propagated effects. Input: lever_metric (required, must be a tunable lever — otherwise 422 LEVER_NOT_TUNABLE); target_metric and delta_pct are optional.
{
"result": {
"query_type": "simulate_scenario",
"summary": "Simulated -5.0% change at discount_rate -> gross_margin delta 0.024.",
"answer": {
"summary": "...", "lever_metric": "discount_rate", "target_metric": "gross_margin",
"delta_pct": -5.0, "target_delta": 0.024, "point_estimate": 0.024,
"impacts": [ { "metric": "gross_margin", "delta": 0.024 } ],
"uncertainty_source": "posterior_samples", "uncertainty_band": [0.018, 0.031]
},
"expected_effects": [ { "metric": "gross_margin", "effect": 0.024 } ],
"direction": "positive", "confidence": null,
"traceability": { "metrics_used": ["discount_rate", "gross_margin"],
"relationships_used": [], "levers_used": ["discount_rate"], "facts_used": [] },
"limits": []
}
}relationship_explanation
The fused evidence binding one metric to another. Input: source_metric and target_metric (both required).
{
"result": {
"query_type": "relationship_explanation",
"summary": "Relationship evidence for discount_rate -> gross_margin.",
"answer": { "summary": "...", "relationship": {
"direction": "negative", "confidence": 0.82, "strength": 0.47 } },
"expected_effects": [], "direction": "negative", "confidence": 0.82,
"traceability": { "metrics_used": ["discount_rate", "gross_margin"],
"relationships_used": [{ "source": "discount_rate", "target": "gross_margin" }],
"levers_used": ["discount_rate"], "facts_used": [] },
"limits": []
}
}model_health
Model-level diagnostics. No input required.
{
"result": {
"query_type": "model_health",
"summary": "Model diagnostics.",
"answer": { "summary": "Model diagnostics.", "diagnostics": { "...": "..." } },
"expected_effects": [], "direction": null, "confidence": null,
"traceability": { "metrics_used": [], "relationships_used": [],
"levers_used": [], "facts_used": [] },
"limits": []
}
}decision_binding_check
Whether a source→target edge is decision-grade (safe to plan on). Input: source_metric and target_metric (both required).
{
"result": {
"query_type": "decision_binding_check",
"summary": "discount_rate -> gross_margin: decision_edge_status=promoted, ...",
"answer": {
"binding_found": true, "relationship_status": "established",
"direction_status": "negative", "decision_edge_status": "promoted",
"w_orientation_safe": true, "planning_decision_safe": true, "reasons": [] },
"expected_effects": [], "direction": "negative", "confidence": 0.71,
"traceability": { "metrics_used": ["discount_rate", "gross_margin"],
"relationships_used": [{ "source": "discount_rate", "target": "gross_margin" }],
"levers_used": ["discount_rate"], "facts_used": [] },
"limits": []
}
}prepare_agent_context
A one-call bootstrap for an agent: the model's vocabulary, its tunable levers, the query types it supports, and the agent actions available. No input required — start here.
{
"result": {
"query_type": "prepare_agent_context",
"summary": "Agent context: 12 metric(s), 3 tunable lever(s), 7 query type(s), 6 agent action(s).",
"answer": {
"run_id": "run_88c1...",
"vocabulary": [ { "metric_id": "gross_margin", "metric_name": "Gross Margin" } ],
"tunable_levers": ["discount_rate"],
"supported_query_types": ["metric_drivers", "downstream_impact",
"simulate_scenario", "relationship_explanation", "model_health",
"decision_binding_check", "prepare_agent_context"],
"agent_actions": [ { "action": "create_recommendation",
"description": "Recommend the strongest actionable driver for a target metric.",
"supported_modes": ["analysis_only", "prepare_payload"],
"requires_approval": false } ]
},
"expected_effects": [], "direction": null, "confidence": null,
"traceability": { "metrics_used": ["discount_rate"], "relationships_used": [],
"levers_used": ["discount_rate"], "facts_used": [] },
"limits": []
}
}6The agent runtime
This is the core of building an agent. Instead of executing arbitrary code, an agent invokes one of a fixed set of allowlisted action templates. Each template consults the deterministic queries above, reasons over the results without any language model, and returns a structured, auditable result.
Body: { "action": ..., "mode": ..., "input": { ... }, "approved": false }. mode defaults to analysis_only.
Action templates
- create_recommendation — recommend the strongest actionable driver for a target. Params:
target_metric. Consultsmetric_drivers. Modes: analysis_only, prepare_payload. Requires fact: tunable_levers. - prepare_approval_packet — assemble a human-approval packet for a proposed lever change. Params:
lever_metric. Consultssimulate_scenario. Modes: analysis_only, prepare_payload. Requires fact: tunable_levers. - generate_workflow_payload — produce a structured workflow payload for an external system to run. Params:
lever_metric. Consultssimulate_scenario. Modes: analysis_only, prepare_payload. Requires fact: tunable_levers. - rank_options — rank candidate drivers by their effect on a target. Params:
target_metric. Consultsmetric_drivers. Modes: analysis_only, prepare_payload. - explain_decision — explain the evidence binding a source to a target. Params:
source_metric,target_metric. Consultsrelationship_explanationanddecision_binding_check. Mode: analysis_only. - prepare_external_writeback — prepare an approved external write-back for a lever change. Params:
lever_metric. Consultssimulate_scenario. Modes: analysis_only, prepare_payload, approved_writeback. Requires fact: tunable_levers. Requires approval.
The three safety modes
- analysis_only — returns reasoning and the consulted analyses only. The default, and the safest.
- prepare_payload — additionally returns a structured
prepared_payloadfor your system to act on. Nothing is executed. - approved_writeback — only for an approval-requiring, writeback-allowed template when
approved: trueis sent. Returns awritebackblock. Still never executed server-side.
Automatic downgrade behavior
The runtime fails safe. If a template's required facts are missing (for example a lever that is not tunable in this model), the effective mode is forced to analysis_only and a missing_facts:... entry is added to limits. If approved_writeback is requested without approved: true, it is downgraded and a writeback_not_approved limit is added — it is never silently executed.
The write-back contract
executed: false and delivery: "client_must_execute". Your system performs the actual write against your own tools.An analysis_only invocation (note llm_used: false):
curl -s -X POST https://comodel.ai/v1/models/mdl_42af.../agents/invoke \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "action": "explain_decision",
"input": { "source_metric": "discount_rate", "target_metric": "gross_margin" } }'{
"api_version": "v1", "request_id": "req_...",
"model_id": "mdl_42af...", "run_id": "run_88c1...",
"advanced_diagnostics": false,
"result": {
"action": "explain_decision",
"requested_mode": "analysis_only", "effective_mode": "analysis_only",
"analysis": [ { "query_type": "relationship_explanation", "...": "..." } ],
"reasoning": ["Relationship evidence for discount_rate -> gross_margin."],
"traceability": { "model_id": "mdl_42af...", "run_id": "run_88c1...",
"metrics_used": ["discount_rate", "gross_margin"],
"relationships_used": [{ "source": "discount_rate", "target": "gross_margin" }],
"levers_used": ["discount_rate"], "facts_used": [] },
"limits": [], "llm_used": false
}
}An approved write-back — note the payload is returned but executed: false:
curl -s -X POST https://comodel.ai/v1/models/mdl_42af.../agents/invoke \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "action": "prepare_external_writeback", "mode": "approved_writeback",
"approved": true, "input": { "lever_metric": "discount_rate" } }'{
"result": {
"action": "prepare_external_writeback",
"requested_mode": "approved_writeback", "effective_mode": "approved_writeback",
"prepared_payload": { "action": "prepare_external_writeback",
"input": { "lever_metric": "discount_rate" },
"model_id": "mdl_42af...", "run_id": "run_88c1...",
"executed": false, "delivery": "client_must_execute" },
"writeback": { "approved": true, "executed": false,
"delivery": "client_must_execute",
"payload": { "...": "same prepared_payload" } },
"limits": [], "llm_used": false
}
}Decision simulations (supporting endpoint)
Where a published model exposes decision simulation, you can also run a full scenario directly against its frozen snapshot. This is the same deterministic runner the agent flow consults.
Body: { "scenario_id": ..., "selected_choice_id": ..., "state_overrides": { ... } }. A model that does not expose simulation returns 422 SIMULATION_NOT_AVAILABLE.
7Build an agent: end to end
Tying it together — from credentials to a write-back your own system executes.
1. Get a token
TOKEN=$(curl -s -X POST https://comodel.ai/v1/auth/token \
-H "Content-Type: application/json" \
-d '{ "client_id": "cli_9f3a...", "client_secret": "cms_xxxx",
"scopes": ["models:read", "queries:write", "agents:invoke"] }' \
| python3 -c 'import sys,json; print(json.load(sys.stdin)["access_token"])')2. List the models you can use
curl -s https://comodel.ai/v1/models -H "Authorization: Bearer $TOKEN"
3. Bootstrap the model context
Discover the vocabulary, tunable levers and available actions in one call:
curl -s -X POST https://comodel.ai/v1/models/mdl_42af.../query \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "query_type": "prepare_agent_context" }'4. Query for drivers
curl -s -X POST https://comodel.ai/v1/models/mdl_42af.../query \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "query_type": "metric_drivers", "input": { "target_metric": "gross_margin" } }'5. Invoke an agent template
curl -s -X POST https://comodel.ai/v1/models/mdl_42af.../agents/invoke \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "action": "create_recommendation", "mode": "prepare_payload",
"input": { "target_metric": "gross_margin" } }'6. Prepare and approve a write-back
curl -s -X POST https://comodel.ai/v1/models/mdl_42af.../agents/invoke \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "action": "prepare_external_writeback", "mode": "approved_writeback",
"approved": true, "input": { "lever_metric": "discount_rate" } }'7. Execute it in your own system
CoModel returns the write-back payload with executed: false. Take result.writeback.payload and perform the actual change in your own tool (CRM, planning system, ticketing, etc.). CoModel deliberately never reaches into your systems — you stay in control of every external action.
request_id in the audit trail.8Error reference
All errors use the single error contract shown in Conventions. The most common codes:
400 INVALID_REQUEST/INVALID_SCOPE— malformed token request or a scope your client may not have.401 MISSING_TOKEN/INVALID_TOKEN/TOKEN_EXPIRED/TOKEN_REVOKED/INVALID_CLIENT/CLIENT_REVOKED— authentication problems.403 INSUFFICIENT_SCOPE— the token lacks the scope an endpoint requires.403 OBJECT_FORBIDDEN— the client is not authorized for that model (or it belongs to another tenant).404 NOT_FOUND— model or run not found, or not published.409 MODEL_NOT_PUBLISHED— the model has no published snapshot yet.400 QUERY_TYPE_UNSUPPORTED— unknownquery_type.400 MISSING_PARAMETER/METRIC_NOT_API_VISIBLE— a required input is missing or the metric is not in the published API vocabulary.422 LEVER_NOT_TUNABLE— the simulate lever is not a tunable lever.422 INVALID_QUERY— the engine rejected the query.400 ACTION_NOT_ALLOWED/INVALID_MODE/MODE_NOT_SUPPORTED— agent action or mode problems.422 SIMULATION_NOT_AVAILABLE— the model does not expose decision simulation.429 RATE_LIMITED— too many token requests.413 PAYLOAD_TOO_LARGE— request body exceeds 200 MB.