REST API
API Reference
Complete REST API documentation for the AgentCost backend
Base URL
https://api.agentcost.techAll API endpoints are relative to this base URL
Authentication
AgentCost uses two types of authentication:
| Type | Used For | Header |
|---|---|---|
| API Key | SDK tracking, analytics, events | Authorization: Bearer sk_xxx |
| JWT Token | Dashboard, user actions, team management | Authorization: Bearer eyJ... |
# Using API Key (for SDK/tracking)
curl -H "Authorization: Bearer sk_your_project_api_key" \
YOUR_API_URL/v1/analytics/overview
# Using JWT Token (for user actions)
curl -H "Authorization: Bearer your_jwt_token" \
YOUR_API_URL/v1/projects/{project_id}/membersSecurity: API keys provide project-level access for your SDK. JWT tokens are user-specific and expire after 1 hour, but are automatically refreshed.
User Login & Registration
These endpoints handle user account creation and authentication. After login, you receive a JWT token to use with protected endpoints.
/v1/auth/registerCreate a new user account
Request Body:
{
"email": "user@example.com",
"password": "your_secure_password",
"name": "John Doe"
}Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"user": {
"id": "usr_abc123",
"email": "user@example.com",
"name": "John Doe",
"email_verified": false
},
"verification_email_sent": true,
"default_project": {
"id": "123e4567-e89b-42d3-a456-426614174000",
"name": "My First Project",
"api_key": "sk_live_xxxxxxxxxxxx"
}
}Registration signs you in immediately — the response carries the same tokens as login. A verification email is sent in the background; verify whenever convenient. The default project's api_key is shown only this once — store it securely.
/v1/auth/loginAuthenticate and get access tokens
Request Body:
{
"email": "user@example.com",
"password": "your_password",
"remember_me": true
}Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"expires_in": 3600,
"user": {
"id": "usr_abc123",
"email": "user@example.com",
"name": "John Doe"
}
}/v1/auth/refreshGet a new access token using refresh token
Request Body:
{
"refresh_token": "your_refresh_token"
}Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"expires_in": 3600
}/v1/auth/logoutAuth requiredInvalidate current session
Response (204 No Content)
Session is invalidated. The access token will no longer be valid.
Health Check
/v1/healthCheck if the backend is running and healthy
Response:
{
"status": "ok",
"version": "0.1.0",
"timestamp": "2024-01-23T10:30:45.123Z"
}Projects
/v1/projectsCreate a new project and get an API key
Request Body:
{
"name": "my-project",
"description": "Optional project description"
}Response:
{
"id": "proj_abc123",
"name": "my-project",
"description": "Optional project description",
"api_key": "sk_live_xxxxxxxxxxxx",
"key_prefix": "sk_live_",
"is_active": true,
"created_at": "2024-01-23T10:30:45.123Z",
"updated_at": "2024-01-23T10:30:45.123Z",
"owner_id": "usr_abc123",
"warning": "Save this API key now! It cannot be retrieved later."
}Important: The API key is shown only once on creation. Store it securely and use rotation to generate a new one later.
/v1/projects/meAuth requiredGet the current project (API key auth)
Response:
{
"id": "proj_abc123",
"name": "my-project",
"description": "Optional project description",
"api_key": null,
"key_prefix": null,
"is_active": true,
"created_at": "2024-01-23T10:30:45.123Z",
"updated_at": "2024-01-23T10:30:45.123Z"
}/v1/projects/{id}Auth requiredGet project details by ID
Response:
{
"id": "proj_abc123",
"name": "my-project",
"description": "Optional project description",
"api_key": null,
"key_prefix": null,
"is_active": true,
"created_at": "2024-01-23T10:30:45.123Z"
}API keys are write-only and are never returned in read endpoints.
/v1/projects/{project_id}Auth requiredUpdate project settings
Request Body:
{
"name": "Updated project name",
"description": "Updated description",
"is_active": true
}Response:
{
"id": "proj_abc123",
"name": "Updated project name",
"description": "Updated description",
"api_key": null,
"key_prefix": null,
"is_active": true,
"created_at": "2024-01-23T10:30:45.123Z"
}/v1/projects/{project_id}Auth requiredDelete a project
Response (200 OK)
{ "status": "deleted" }Warning: Deleting a project removes all associated events and analytics.
/v1/projects/{project_id}/api-key/rotateAuth requiredRotate the project API key (Admin only, JWT auth)
Response:
{
"status": "ok",
"project_id": "proj_abc123",
"api_key": "sk_live_xxxxxxxxxxxx",
"key_prefix": "sk_live_",
"message": "Save this API key now. It cannot be retrieved later."
}Team Management
Manage team members and their access to your project. All team endpoints require JWT authentication.
| Role | Permissions |
|---|---|
| Admin | Full access: invite/remove members, change roles, delete project |
| Member | View analytics, create events, export data |
| Viewer | Read-only access to analytics and events |
/v1/projects/{project_id}/membersAuth requiredList all members of a project
Response:
{
"members": [
{
"id": "mem_123",
"user_id": "usr_abc",
"email": "admin@example.com",
"name": "John Doe",
"role": "admin",
"is_owner": true,
"is_pending": false,
"accepted_at": "2024-01-20T10:00:00Z"
},
{
"id": "mem_456",
"user_id": "usr_def",
"email": "viewer@example.com",
"name": "Jane Smith",
"role": "viewer",
"is_owner": false,
"is_pending": false,
"accepted_at": "2024-01-22T15:30:00Z"
}
],
"total": 2
}/v1/projects/{project_id}/membersAuth requiredInvite a user to the project (Admin only)
Request Body:
{
"email": "newmember@example.com",
"role": "member"
}Response:
{
"message": "Invitation sent to newmember@example.com",
"membership_id": "mem_789",
"role": "member"
}An invitation email is sent to the user. They must accept it to join the project.
/v1/projects/invitations/pendingAuth requiredGet your pending project invitations
Response:
{
"invitations": [
{
"project_id": "proj_abc123",
"project_name": "My Project",
"role": "member",
"invited_by": {
"name": "John Doe",
"email": "john@example.com"
},
"invited_at": "2024-01-23T10:30:45.123Z"
}
],
"total": 1
}/v1/projects/{project_id}/invitations/acceptAuth requiredAccept a project invitation
Response:
{
"status": "accepted",
"project_id": "proj_abc123",
"role": "member"
}/v1/projects/{project_id}/invitations/declineAuth requiredDecline a project invitation
Response (204 No Content)
/v1/projects/{project_id}/members/{user_id}Auth requiredUpdate a member's role (Admin only)
Request Body:
{
"role": "admin"
}Response:
{
"status": "updated",
"new_role": "admin"
}/v1/projects/{project_id}/members/{user_id}Auth requiredRemove a member from the project (Admin only)
Response (204 No Content)
/v1/projects/{project_id}/leaveAuth requiredLeave a project voluntarily
Response (204 No Content)
Project owners cannot leave. They must transfer ownership or delete the project.
Events
/v1/events/batchAuth requiredIngest a batch of LLM call events (used by SDK)
Try it from your terminal — this one command ingests a sample event and lights up your dashboard:
curl -X POST "https://api.agentcost.tech/v1/events/batch" \
-H "Authorization: Bearer sk_your_project_api_key" \
-H "Content-Type: application/json" \
-d '{
"project_id": "123e4567-e89b-42d3-a456-426614174000",
"events": [
{
"agent_name": "my-first-agent",
"model": "gpt-4o-mini",
"input_tokens": 150,
"output_tokens": 80,
"latency_ms": 1234,
"timestamp": "2026-08-04T10:30:45Z",
"success": true
}
]
}'project_id is your project's UUID from Settings (not its name) and must match the API key's project — a mismatch returns 403. total_tokens and cost are optional; the server derives and prices them for you.
Full request body — every field beyond the required four (agent_name, model, input_tokens, output_tokens, plus timestamp) is optional:
{
"project_id": "proj_abc123",
"events": [
{
"agent_name": "router-agent",
"model": "gpt-4o",
"input_tokens": 1500,
"output_tokens": 80,
"cached_tokens": 1200,
"cache_write_tokens": 0,
"latency_ms": 1234,
"timestamp": "2026-08-15T10:30:45.123Z",
"success": true,
"event_id": "delivery-42",
"trace_id": "0532f9c4-a022-4e98-a543-d8e17c5b90a6",
"metadata": {"user_id": "alice@example.com", "session_id": "run-7f3a"}
}
],
"outcomes": [
{"trace_id": "0532f9c4-a022-4e98-a543-d8e17c5b90a6", "success": true}
]
}cached_tokensis the part ofinput_tokensserved from the provider's prompt cache — it changes cost materially on cache-heavy workloads and is priced at real cache rates.event_idmakes delivery idempotent: a replay returns 200 withevents_duplicateincremented and stores nothing, even under concurrent retries.trace_idaccepts up to 64 characters, so UUIDs minted by an external orchestrator fit.outcomesmay be sent with an emptyeventslist — a run denied by a policy layer still gets its ending recorded.metadata.user_idandmetadata.session_idbecome indexed analytics dimensions — see Analytics.
Response:
{
"status": "ok",
"events_stored": 1,
"events_received": 1,
"events_rejected": 0,
"events_duplicate": 0,
"outcomes_recorded": 1,
"rejected": [],
"timestamp": "2026-08-15T10:30:46.001Z"
}/v1/eventsAuth requiredGet recent events for the authenticated project
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
| limit | int | 100 | Maximum events to return |
| offset | int | 0 | Number of events to skip |
| agent_name | str | - | Filter by agent name |
Analytics
/v1/analytics/overviewAuth requiredGet cost overview for the project
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
| range | str | Time range: 24h, 7d, 30d, 90d |
Response:
{
"total_cost": 45.32,
"total_calls": 2150,
"total_tokens": 1250000,
"avg_cost_per_call": 0.021,
"avg_latency_ms": 850.5,
"success_rate": 99.5,
"period_start": "2024-01-16T00:00:00Z",
"period_end": "2024-01-23T00:00:00Z"
}/v1/analytics/agentsAuth requiredGet per-agent cost breakdown
Response:
[
{
"agent_name": "router-agent",
"total_calls": 850,
"total_tokens": 425000,
"total_cost": 18.50,
"avg_latency_ms": 750,
"success_rate": 99.8
},
{
"agent_name": "technical-agent",
"total_calls": 650,
"total_tokens": 520000,
"total_cost": 15.20,
"avg_latency_ms": 920,
"success_rate": 99.2
}
]/v1/analytics/modelsAuth requiredGet per-model cost breakdown
Response:
[
{
"model": "gpt-4",
"total_calls": 500,
"total_tokens": 300000,
"input_tokens": 180000,
"output_tokens": 120000,
"total_cost": 25.50,
"cost_share": 56.3
},
{
"model": "gpt-3.5-turbo",
"total_calls": 1200,
"total_tokens": 600000,
"input_tokens": 400000,
"output_tokens": 200000,
"total_cost": 8.40,
"cost_share": 18.5
}
]/v1/analytics/timeseriesAuth requiredGet time series data for charting
Response:
[
{
"timestamp": "2024-01-23T00:00:00Z",
"cost": 5.32,
"calls": 245,
"tokens": 125000
},
{
"timestamp": "2024-01-23T01:00:00Z",
"cost": 4.85,
"calls": 220,
"tokens": 115000
}
]/v1/analytics/fullAuth requiredGet complete analytics response (overview + agents + models + timeseries)
Response:
{
"overview": { ... },
"agents": [ ... ],
"models": [ ... ],
"timeseries": [ ... ]
}/v1/analytics/by/{dimension}Auth requiredCost and volume grouped by user, session, workflow, tool, model or agent
user and session read the user_id / session_id keys from event metadata — this is what answers what is each developer costing us. Events with no value for the dimension are excluded, not bucketed under a placeholder.
curl -H "Authorization: Bearer sk_your_project_api_key" \
"https://api.agentcost.tech/v1/analytics/by/user?range=30d"Response:
[
{
"key": "alice@example.com",
"total_calls": 4210,
"total_tokens": 9812004,
"total_cost": 412.86,
"avg_latency_ms": 1180.4,
"success_rate": 99.2
}
]/v1/analytics/cacheAuth requiredPrompt-cache hit rate and savings for a window, in USD
Savings are measured against billing every cached token at the model's full input rate; a model with no published cache rate contributes zero, exactly as ingest prices it.
{
"total_input_tokens": 48120044,
"cached_tokens": 34350211,
"cache_write_tokens": 1204110,
"cache_hit_rate": 71.4,
"events_with_cache": 18744,
"read_savings": 212.4,
"write_premium": 18.05,
"net_savings": 194.35
}Workflows & Traces
Cost attributed to the shape of a run rather than to the model that served it. These endpoints read only events carrying trace structure, which the SDK adds when you use workflow(), step() and tool(). Calls made outside a workflow are absent here by design, and remain visible under Analytics. Every endpoint accepts range (1h, 24h, 7d, 30d, 90d).
/v1/analytics/workflowsAuth requiredCost per workflow, including the average cost of a single run
Response:
[
{
"workflow": "support-triage",
"runs": 9500,
"total_cost": 321.47,
"avg_cost_per_run": 0.0338,
"max_cost_per_run": 0.0879,
"total_calls": 41800,
"avg_calls_per_run": 4.4,
"avg_steps_per_run": 3,
"max_depth": 2,
"success_rate": 98.7
}
]/v1/analytics/workflows/stepsAuth requiredCost per step. calls_per_run above 1 indicates retries or a loop
Optional workflow query parameter restricts the result to one workflow.
[
{
"workflow": "support-triage",
"step_name": "search_docs",
"calls": 23400,
"runs": 9500,
"calls_per_run": 2.4,
"cost_per_run": 0.0209,
"total_cost": 203.18,
"avg_latency_ms": 1250,
"success_rate": 96.9
}
]/v1/analytics/workflows/toolsAuth requiredLLM spend incurred while a named tool was running
Response:
[
{
"tool_name": "search_docs",
"calls": 23400,
"runs": 9500,
"total_cost": 203.18,
"total_tokens": 51000000,
"avg_latency_ms": 1250
}
]/v1/analytics/workflows/repeated-workAuth requiredIdentical calls repeated within a single run, and what they cost
Distinct from the cross-run duplication the caching analyzer reports: that argues for a cache, this usually means the control flow is looping. wasted_cost covers every occurrence beyond the first.
[
{
"trace_id": "9f2c41a0b7d3e5f1",
"workflow": "support-triage",
"step_name": "search_docs",
"model": "gpt-4o",
"occurrences": 4,
"spend": 0.0435,
"wasted_cost": 0.0326,
"first_seen": "2026-08-11T09:14:22Z"
}
]/v1/analytics/workflows/outcomesAuth requiredCost per completed outcome, charging failed runs to the successes
Populated only for runs that called track_costs.outcome(). Runs that declared nothing are counted as unknown rather than as failures.
[
{
"workflow": "support-triage",
"runs": 9500,
"succeeded": 8645,
"failed": 684,
"unknown": 171,
"cost_on_success": 292.20,
"cost_on_failure": 23.12,
"cost_per_success": 0.0365,
"success_rate": 92.67
}
]/v1/analytics/workflows/distributionAuth requiredDistribution of cost per run, with percentiles and the tail's share of spend
Computed over every run in the window rather than a top-N slice. Defaults to the highest-spend workflow; pass workflow to choose one, and buckets (6-60) to set the resolution. The final histogram bucket is the tail, marked is_tail.
{
"workflow": "support-triage",
"runs": 9500,
"truncated": false,
"p50": 0.035,
"p95": 0.045,
"p99": 0.156,
"max": 0.182,
"tail_runs": 476,
"tail_threshold": 0.0461,
"tail_share_percent": 14.8,
"tail_ratio": 4.5,
"histogram": [
{ "lower": 0.022, "upper": 0.0228, "count": 12, "is_tail": false }
]
}/v1/analytics/tracesAuth requiredIndividual runs, most expensive first
Optional workflow parameter. Use the returned trace_id with the endpoint below.
[
{
"trace_id": "9f2c41a0b7d3e5f1",
"workflow": "support-triage",
"calls": 11,
"total_cost": 0.0879,
"max_depth": 2,
"failed_calls": 0,
"started_at": "2026-08-11T09:14:20Z",
"duration_ms": 7420
}
]/v1/analytics/traces/{trace_id}Auth requiredEvery span of one run, ordered as it executed
Spans are returned flat with parent ids rather than pre-nested, so a span whose parent never arrived cannot break the response. Returns 404 if the trace does not belong to your project.
{
"trace_id": "9f2c41a0b7d3e5f1",
"workflow": "support-triage",
"total_cost": 0.0879,
"total_calls": 11,
"max_depth": 2,
"duration_ms": 7420,
"spans": [
{
"span_id": "1b40a23d06f0401f",
"parent_span_id": null,
"step_name": "classify",
"tool_name": null,
"step_index": 0,
"depth": 1,
"model": "gpt-4o",
"cost": 0.00082,
"latency_ms": 340,
"success": true
}
]
}Guardrails
A declared boundary per agent, judged against observed usage. Four boundaries, each optional: permitted tools, read-only, permitted models, and per-run limits on tool calls and cost. This is a separate concept from success rate: success measures whether a call raised an error, compliance measures whether an agent stayed inside the boundary you declared. Tool boundaries only see calls instrumented with track_costs.tool(...) and per-run limits only see calls inside track_costs.workflow(), so every verdict is reported alongside instrumentation coverage. Model boundaries see every call.
/v1/guardrailsAuth requiredDeclared guardrails for every agent in the project
Response:
[
{
"id": "6f0e2a44-9c1b-4d2f-8a3e-1b2c3d4e5f60",
"agent_name": "research-agent",
"allowed_tools": ["web_search"],
"read_only": true,
"allowed_models": null,
"max_tool_calls_per_run": 8,
"max_cost_per_run_usd": null,
"enabled": true,
"created_at": "2026-08-12T09:14:22Z",
"updated_at": "2026-08-30T16:02:10Z"
}
]/v1/guardrails/complianceAuth requiredObserved tool usage judged against each agent's declared guardrail
Accepts range (1h, 24h, 7d, 30d, 90d). A breach is {kind, subject, count, limit, observed, last_seen}: subject is the tool, the model, or for per-run kinds the worst run's trace_id; count is breaching calls, or runs over the limit. Kinds: undeclared_tool, write_in_readonly, undeclared_model, tool_calls_over_limit and run_cost_over_limit. Tools a read-only agent used that carry no read/write tag are listed in unknown_access_tools rather than silently judged either way. Each agent also carries the detail behind the verdict: tool_usage and model_usage (calls, cost, tag, whether permitted), run_stats (p50, p95 and max tool calls and cost per run, so a limit can be set from observed behaviour) and breach_series (breaching calls or runs per day).
{
"agents": [
{
"agent_name": "email-drafter",
"status": "breach",
"read_only": true,
"allowed_tools": null,
"allowed_models": null,
"max_tool_calls_per_run": null,
"max_cost_per_run_usd": null,
"total_calls": 15000,
"total_cost": 4.33,
"tracked_tool_calls": 120,
"runs_seen": 0,
"observed_tools": ["send_email"],
"observed_models": ["gpt-4o"],
"tool_usage": [
{ "tool_name": "send_email", "calls": 120, "last_seen": "2026-09-01T18:44:03Z", "access": "write", "breach_kind": "write_in_readonly" }
],
"model_usage": [
{ "model": "gpt-4o", "calls": 15000, "cost": 4.33, "permitted": true }
],
"run_stats": null,
"breach_series": [
{ "day": "2026-09-01", "count": 120 }
],
"breaches": [
{
"kind": "write_in_readonly",
"subject": "send_email",
"count": 120,
"limit": null,
"observed": null,
"last_seen": "2026-09-01T18:44:03Z"
}
],
"unknown_access_tools": []
},
{
"agent_name": "research-agent",
"status": "breach",
"read_only": true,
"allowed_tools": ["web_search"],
"allowed_models": null,
"max_tool_calls_per_run": 8,
"max_cost_per_run_usd": null,
"total_calls": 2600,
"total_cost": 137.92,
"tracked_tool_calls": 2210,
"runs_seen": 442,
"observed_tools": ["web_search"],
"observed_models": ["claude-sonnet-4"],
"tool_usage": [
{ "tool_name": "web_search", "calls": 2210, "last_seen": "2026-09-01T09:02:41Z", "access": "read", "breach_kind": null }
],
"model_usage": [
{ "model": "claude-sonnet-4", "calls": 2600, "cost": 137.92, "permitted": true }
],
"run_stats": {
"runs": 442, "p50_tool_calls": 5, "p95_tool_calls": 11, "max_tool_calls": 14,
"p50_cost": 0.27, "p95_cost": 0.59, "max_cost": 0.76
},
"breach_series": [
{ "day": "2026-08-30", "count": 9 },
{ "day": "2026-09-01", "count": 15 }
],
"breaches": [
{
"kind": "tool_calls_over_limit",
"subject": "7c1e4b0a9d2f48e6b3a5c7d9e1f2a3b4",
"count": 24,
"limit": 8,
"observed": 14,
"last_seen": "2026-09-01T09:02:41Z"
}
],
"unknown_access_tools": []
}
],
"tool_tags": [
{ "tool_name": "send_email", "access": "write" },
{ "tool_name": "web_search", "access": "read" }
],
"start_time": "2026-08-26T00:00:00Z",
"end_time": "2026-09-02T00:00:00Z",
"total_calls": 98400,
"tool_tracked_calls": 26100
}Breaches are alerted at ingest
When a batch contains a breaching tool call, owners and admins receive an in-app notification and the project webhook (if configured) receives a guardrail.breach event signed like budget alerts. One alert per agent, subject and kind per hour. Per-run limits are judged on the run's stored totals, so a run that crosses its limit across several batches still alerts.
{
"project_id": "6f0e2a44-9c1b-4d2f-8a3e-1b2c3d4e5f60",
"agent_name": "email-drafter",
"kind": "write_in_readonly",
"subject": "send_email",
"count": 3,
"limit": null,
"observed": null,
"observed_at": "2026-09-02T18:44:03Z"
}/v1/projects/{project_id}/guardrailsAuth requiredCreate or replace the guardrail for one agent (member session, EDIT_PROJECT)
Every field is optional and null means unbounded. allowed_tools and allowed_models: null permits any, [] permits none. max_tool_calls_per_run (integer, at least 1) and max_cost_per_run_usd (positive) are judged over calls that share a trace_id. The SDK's project API key cannot call this — the credential being judged must not be able to rewrite the policy it is judged against.
{
"agent_name": "research-agent",
"allowed_tools": ["web_search"],
"read_only": true,
"allowed_models": ["claude-sonnet-4"],
"max_tool_calls_per_run": 8,
"max_cost_per_run_usd": 0.25,
"enabled": true
}/v1/projects/{project_id}/guardrails/{agent_name}Auth requiredRemove the guardrail for one agent (member session, EDIT_PROJECT)
/v1/projects/{project_id}/guardrails/tool-tagsAuth requiredTag a tool name as read or write (member session, EDIT_PROJECT)
{ "tool_name": "send_email", "access": "write" }/v1/projects/{project_id}/guardrails/tool-tags/{tool_name}Auth requiredRemove a tool's read/write tag (member session, EDIT_PROJECT)
Optimizations
/v1/optimizationsAuth requiredGet AI-powered cost optimization suggestions
Response:
[
{
"type": "model_downgrade",
"title": "Switch router-agent from gpt-4 to gpt-3.5-turbo",
"description": "Agent 'router-agent' uses gpt-4 but generates only 50 tokens on average.",
"estimated_savings_monthly": 45.50,
"estimated_savings_percent": 95.0,
"priority": "high",
"action_items": [
"Review prompts and outputs",
"Test with gpt-3.5-turbo",
"Update model configuration"
]
}
]/v1/optimizations/summaryAuth requiredGet summary of potential savings
Response:
{
"total_potential_savings_monthly": 125.50,
"total_potential_savings_percent": 35.2,
"suggestion_count": 5,
"high_priority_count": 2
}Error Handling
The API uses standard HTTP status codes. Error responses include a message explaining what went wrong:
{
"detail": "Invalid API key"
}| Status Code | Description |
|---|---|
| 200 | Success |
| 201 | Created |
| 400 | Bad Request - Invalid input |
| 401 | Unauthorized - Invalid or missing API key |
| 404 | Not Found - Resource does not exist |
| 429 | Too Many Requests - Rate limit exceeded |
| 500 | Internal Server Error |
Rate Limiting
The API enforces rate limiting to ensure fair usage and protect the service. Rate limits are applied per API key or IP address.
Default limits: 100 requests per minute
Rate limit headers are included in all API responses:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 45When rate limited, you'll receive a 429 response:
{
"detail": "Rate limit exceeded. Please slow down.",
"retry_after": 45,
"limit": 100,
"period": "60 seconds"
}Tip: The SDK automatically handles rate limiting with built-in batching and retry logic. You typically don't need to worry about rate limits when using the SDK.
SDKs & Libraries
Official SDK for integrating AgentCost into your applications:
Python SDK
For OpenAI, Anthropic, Gemini, and LangChain applications
pip install agentcostComing Soon: JavaScript/TypeScript SDK, Go SDK, and REST client libraries for other languages.
Webhooks
Budget threshold crossings are pushed to your endpoint as they happen, signed so the receiver can verify origin and freshness. Delivery is best-effort and never delays event ingestion — poll budget-state as the reliable channel.
/v1/projects/{project_id}/webhookAuth requiredConfigure the webhook (requires project-edit permission)
{"url": "https://your-endpoint.example/agentcost", "secret": "whsec_..."}HTTPS required. {"url": null} disables the hook and clears the secret. When rotating a secret, restate the URL — a secret without a URL is rejected. The secret is write-only: GET on the same path returns the URL and whether a secret is set, never the secret itself.
/v1/projects/{project_id}/webhook/testAuth requiredSend a signed sample delivery to verify the wiring
Same payload shape and signature scheme as a live delivery; the event type is webhook.test. Returns whether the endpoint accepted it and the status code.
Verifying a delivery
Each POST carries X-AgentCost-Signature = HMAC-SHA256 over {timestamp}.{body} with your secret, and X-AgentCost-Timestamp. Reject stale timestamps before comparing digests — the timestamp is inside the signed string, so a captured delivery cannot be replayed with a fresh header.
import hashlib, hmac
def verify(secret: str, timestamp: str, body: str, signature: str) -> bool:
expected = hmac.new(
secret.encode(), f"{timestamp}.{body}".encode(), hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)Delivery rules: only a 2xx counts as delivered; redirects are not followed; non-public destination addresses are refused (self-hosted installs posting to internal listeners set WEBHOOK_ALLOW_PRIVATE_URLS=true).
Budget State & Metrics
/v1/projects/{project_id}/budget-stateAuth requiredCompact budget position for machine consumers (project API key auth)
Side-effect-free and shaped for polling: an enforcement point reads it every 15–60s and holds the answer as cached state. as_of and period_ends_at let a consumer reason about staleness and time remaining.
{
"project_id": "proj_abc123",
"enabled": true,
"mode": "warn",
"currency": "USD",
"budget": 500.0,
"spend_mtd": 390.0,
"remaining": 110.0,
"utilization_percent": 78.0,
"thresholds_crossed": [50, 75],
"exhausted": false,
"period_ends_at": "2026-09-01T00:00:00+00:00",
"as_of": "2026-08-15T10:30:45+00:00"
}/v1/metricsAuth requiredPrometheus exposition of the project's cost metrics
Windowed gauges (not monotonic counters — use max_over_time, not rate()): agentcost_calls, agentcost_cost_usd, agentcost_tokens, agentcost_cached_tokens, agentcost_errors, per-model and per-agent cost, plus budget utilization and remaining when a budget is set.
scrape_configs:
- job_name: agentcost
metrics_path: /v1/metrics
authorization:
credentials: <project_api_key>
static_configs:
- targets: ['api.agentcost.tech']/v1/pricing/importAuth requiredLoad the pricing catalogue from an uploaded LiteLLM bundle (admin only)
For air-gapped and egress-restricted deployments: fetch model_prices_and_context_window.json on a connected machine, review it, and upload it verbatim. Same parsing and sanity bounds as the network sync.
API Versioning
The API uses URL path versioning. The current version is v1.
| Version | Status | Notes |
|---|---|---|
| v1 | Current | Stable, recommended for production |
We follow semantic versioning. Breaking changes will result in a new major version. Deprecated endpoints will be announced at least 6 months before removal.