Complete REST API documentation for the AgentCost backend
https://api.yourdomain.comAll API endpoints are relative to this base URL
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.
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/logoutAuthInvalidate current session
Response (204 No Content)
Session is invalidated. The access token will no longer be valid.
/v1/healthCheck if the backend is running and healthy
Response:
{
"status": "ok",
"version": "0.1.0",
"timestamp": "2024-01-23T10:30:45.123Z"
}/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/meAuthGet 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}AuthGet 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}AuthUpdate 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}AuthDelete a project
Response (200 OK)
{ "status": "deleted" }Warning: Deleting a project removes all associated events and analytics.
/v1/projects/{project_id}/api-key/rotateAuthRotate 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."
}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}/membersAuthList 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}/membersAuthInvite 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/pendingAuthGet 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/acceptAuthAccept a project invitation
Response:
{
"status": "accepted",
"project_id": "proj_abc123",
"role": "member"
}/v1/projects/{project_id}/invitations/declineAuthDecline a project invitation
Response (204 No Content)
/v1/projects/{project_id}/members/{user_id}AuthUpdate a member's role (Admin only)
Request Body:
{
"role": "admin"
}Response:
{
"status": "updated",
"new_role": "admin"
}/v1/projects/{project_id}/members/{user_id}AuthRemove a member from the project (Admin only)
Response (204 No Content)
/v1/projects/{project_id}/leaveAuthLeave a project voluntarily
Response (204 No Content)
Project owners cannot leave. They must transfer ownership or delete the project.
/v1/events/batchAuthIngest 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_tokens is the part of input_tokens served from the provider's prompt cache — it changes cost materially on cache-heavy workloads and is priced at real cache rates.event_id makes delivery idempotent: a replay returns 200 with events_duplicate incremented and stores nothing, even under concurrent retries.trace_id accepts up to 64 characters, so UUIDs minted by an external orchestrator fit. outcomes may be sent with an empty events list — a run denied by a policy layer still gets its ending recorded.metadata.user_id and metadata.session_id become 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/eventsAuthGet 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 |
/v1/analytics/overviewAuthGet 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/agentsAuthGet 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/modelsAuthGet 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/timeseriesAuthGet 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/fullAuthGet complete analytics response (overview + agents + models + timeseries)
Response:
{
"overview": { ... },
"agents": [ ... ],
"models": [ ... ],
"timeseries": [ ... ]
}/v1/analytics/by/{dimension}AuthCost 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/cacheAuthPrompt-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
}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/workflowsAuthCost 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/stepsAuthCost 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/toolsAuthLLM 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-workAuthIdentical 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/outcomesAuthCost 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/distributionAuthDistribution 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/tracesAuthIndividual 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}AuthEvery 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
}
]
}/v1/optimizationsAuthGet 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/summaryAuthGet summary of potential savings
Response:
{
"total_potential_savings_monthly": 125.50,
"total_potential_savings_percent": 35.2,
"suggestion_count": 5,
"high_priority_count": 2
}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 |
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.
Official SDK for integrating AgentCost into your applications:
For OpenAI, Anthropic, Gemini, and LangChain applications
pip install agentcostComing Soon: JavaScript/TypeScript SDK, Go SDK, and REST client libraries for other languages.
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}/webhookAuthConfigure 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/testAuthSend 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.
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).
/v1/projects/{project_id}/budget-stateAuthCompact 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/metricsAuthPrometheus 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/importAuthLoad 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.
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.