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.
Request Body:
{
"project_id": "proj_abc123",
"events": [
{
"agent_name": "router-agent",
"model": "gpt-4",
"input_tokens": 150,
"output_tokens": 80,
"total_tokens": 230,
"cost": 0.0093,
"latency_ms": 1234,
"timestamp": "2024-01-23T10:30:45.123Z",
"success": true,
"metadata": {"user_id": "user_123"}
}
]
}Response:
{
"status": "ok",
"inserted": 1
}/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": [ ... ]
}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.
Receive real-time notifications when events occur in your project.
cost.threshold.exceeded - Daily cost limit exceedederror.rate.high - Error rate above thresholdlatency.high - Average latency above thresholdproject.created - New project createdComing Soon: Webhooks are currently in development. Contact us if you need this feature for your use case.
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.