Docs / API
HTTP API reference
One endpoint. POST an alarm, logs and recent changes. Get back a ranked root cause with cited evidence, a drafted fix with commands, an incident timeline and a postmortem.
Bearer token
Every request to the Ember API must include a valid API key in the Authorization header using the Bearer scheme. Keys are workspace-scoped and are created in your dashboard under Settings > API keys.
Keep your key server-side. Never expose it in client code or commit it to a repository. Rotate it from the dashboard at any time; the old key stops working immediately.
Authorization: Bearer emb_live_xxxxxxxxxxxxxxxxxxxx
API keys carry the prefix emb_live_ for production and emb_test_ for test-mode requests. Test-mode requests run the full analysis pipeline but do not count against your monthly incident quota.
POST /v1/incidents
Submit telemetry for analysis. Ember reads the alarm, the logs and traces around it, and any recent changes, then returns a structured result with a ranked root cause, a drafted fix, an incident timeline and a postmortem.
The endpoint is model-agnostic. A thin routing layer picks a provider and model per task and fails over automatically during an incident. Scale workspaces can register their own model endpoints; the analysis runs on models you control without leaving your perimeter.
Base URL: https://api.emberoncall.com
Request body
Send a JSON object. alarm and logs are required. Supplying changes substantially improves deploy-correlation accuracy. service is inferred from the alarm text if omitted.
{
"alarm": string, // required: alert text from your pager or alerting tool
"logs": string, // required: logs, traces, metrics; newline-separated
"changes": string, // optional: recent deploys, config changes, feature flags
"service": string // optional: Ember infers the service name if omitted
}curl example
The example below submits a checkout-api 5xx alarm with Redis pool saturation logs and a recent deploy. The response excerpt is shown in section 04.
curl -X POST https://api.emberoncall.com/v1/incidents \
-H "Authorization: Bearer emb_live_xxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"alarm": "PagerDuty: [SEV2] checkout-api 5xx rate 8.4% (threshold 1%) for 4m, p95 latency 2.9s",
"logs": "15:02:11 checkout-api ERROR pool timeout acquiring connection after 5000ms (redis)\n15:02:12 checkout-api WARN retrying charge idempotency-key=ck_9a3b attempt=3\n15:02:12 checkout-api ERROR upstream payments 503 after 3 retries\n15:02:12 redis INFO connected_clients=200 blocked_clients=147 maxclients=200\n15:01:58 checkout-api INFO deploy v2025.7.3 healthy, warmup complete",
"changes": "14:58 deploy checkout-api v2025.7.3 (PR #4821: swap Redis client, raise pool to 200)\n14:31 flag promo_engine_v2 enabled at 25%",
"service": "checkout-api"
}'IncidentResult shape
A successful request returns HTTP 200 with a JSON body that exactly mirrors the IncidentResult type from the Ember SDK. All fields are always present; string fields are empty strings rather than null when data is thin.
causes is sorted most-likely first (2 to 4 items). Each cause carries a confidence from 0 to 100 derived from how well the evidence supports it, not from the model's self-reported certainty. When evidence is thin, confidence is low and the rationale states what to check next.
fix.steps are ordered fastest-safe-path first: typically a rollback or mitigation, then the durable fix, then a verify step. Steps with a runnable command carry a concrete shell, kubectl or SQL command. A human runs anything that touches production.
// Mirrors lib/incident IncidentResult exactly
type Severity = "SEV1" | "SEV2" | "SEV3";
type FixKind = "rollback" | "mitigation" | "fix" | "verify";
type TimelineKind = "alarm" | "triage" | "cause" | "mitigation" | "resolved";
{
summary: string, // one-sentence plain-language description of the incident
severity: Severity, // SEV1=outage/data-loss, SEV2=degraded, SEV3=contained
service: string, // affected service name (inferred if not supplied)
causes: [
{
title: string, // short cause label
confidence: number, // 0-100
rationale: string, // plain-language explanation
evidence: string[] // quoted or paraphrased log lines / signals used
}
], // 2-4 items, most likely first
fix: {
headline: string, // one-sentence summary of the recovery path
steps: [
{
kind: FixKind, // rollback | mitigation | fix | verify
title: string,
detail: string,
command: string // concrete shell / kubectl / SQL command, if applicable
}
] // 2-5 items, fastest-safe-path first
},
timeline: [
{
offset: string, // e.g. "00:00", "+2m", "+11m"
kind: TimelineKind,
label: string
}
], // 3-6 entries
postmortem: string, // 120-200 words: what happened, impact, root cause, action items
model: string, // model identifier used to produce this result
offline: boolean // true when no model key is configured (offline fallback mode)
}Checkout-api Redis pool exhaustion
Full response for the curl example in section 02. A deploy raised the Redis client pool to the instance's hard maxclients limit, filling every slot within 4 minutes of warmup and triggering a 5xx cascade on charge requests.
{
"summary": "checkout-api is rejecting charge requests because the Redis connection pool is exhausted following the v2025.7.3 deploy. Rolling back restores capacity immediately.",
"severity": "SEV2",
"service": "checkout-api",
"causes": [
{
"title": "Redis connection pool exhausted after v2025.7.3 deploy",
"confidence": 87,
"rationale": "The deploy raised REDIS_POOL_SIZE to 200, matching the Redis instance maxclients limit. Every slot was filled by checkout-api within 4 minutes of warmup completing, leaving no headroom for retries or other clients.",
"evidence": [
"15:02:11 checkout-api ERROR pool timeout acquiring connection after 5000ms (redis)",
"15:02:12 redis INFO connected_clients=200 blocked_clients=147 maxclients=200",
"14:58 deploy checkout-api v2025.7.3 (PR #4821: swap Redis client, raise pool to 200)"
]
},
{
"title": "Retry storm amplifying pool demand",
"confidence": 44,
"rationale": "With every charge request timing out, the payment client retried 3 times each, tripling pool demand. Secondary to the root cause but extends the recovery window if not addressed.",
"evidence": [
"15:02:11 checkout-api WARN retrying charge idempotency-key=ck_9a3b attempt=3"
]
}
],
"fix": {
"headline": "Roll back checkout-api to v2025.7.2, then cap the pool below the Redis maxclients limit.",
"steps": [
{
"kind": "rollback",
"title": "Roll back checkout-api",
"detail": "Revert to v2025.7.2 to immediately reduce pool size and stop the timeout cascade.",
"command": "kubectl rollout undo deploy/checkout-api"
},
{
"kind": "verify",
"title": "Confirm recovery",
"detail": "Watch error rate and p95 latency return to baseline for 5 minutes before standing down.",
"command": "watch -n5 'kubectl -n prod get deploy checkout-api'"
},
{
"kind": "fix",
"title": "Cap pool below Redis maxclients",
"detail": "Set REDIS_POOL_SIZE to 160 (80% of maxclients=200) to leave headroom for retries and other clients, then re-deploy.",
"command": "kubectl set env deploy/checkout-api REDIS_POOL_SIZE=160"
},
{
"kind": "verify",
"title": "Add a saturation alert",
"detail": "Alert when Redis connected_clients exceeds 160 so the ceiling is visible before it is hit."
}
]
},
"timeline": [
{ "offset": "00:00", "kind": "alarm", "label": "PagerDuty fired: checkout-api 5xx rate 8.4%, p95 2.9s" },
{ "offset": "+40s", "kind": "triage", "label": "Ember read logs, traces, and deploy feed" },
{ "offset": "+2m", "kind": "cause", "label": "Ranked cause: Redis pool exhausted after v2025.7.3 deploy (87%)" },
{ "offset": "+3m", "kind": "mitigation", "label": "Rollback initiated: kubectl rollout undo deploy/checkout-api" },
{ "offset": "+7m", "kind": "resolved", "label": "Error rate back to baseline, p95 210ms" }
],
"postmortem": "At 15:02 UTC, checkout-api began rejecting charge requests with Redis pool timeout errors. The v2025.7.3 deploy at 14:58 raised REDIS_POOL_SIZE to 200, matching the Redis instance maxclients limit. With all connection slots held by checkout-api, retries and other clients were blocked immediately. The retry backoff on the payment client tripled effective pool demand, worsening the cascade. Rolling back to v2025.7.2 at +3m reduced pool size; the error rate reached baseline at +7m. MTTR: 7 minutes. Impact: elevated 5xx rate on POST /charge for approximately 5 minutes, affecting an estimated 3,200 checkout attempts. Action items: cap REDIS_POOL_SIZE at 80% of maxclients in the deploy checklist; add a connected_clients saturation alert at the 80% threshold; add a staging smoke test that asserts pool headroom after deploy.",
"model": "ember-router-v1",
"offline": false
}Limits, quotas and error codes
Rate limits
Rate limits apply per workspace and per API key. The default limits by plan are:
- Free: 10 incident analyses per calendar month, at 1 request per minute.
- Team: unlimited incident analyses, at 10 requests per minute.
- Scale: custom quota, up to 60 requests per minute by default, configurable on request.
Rate limit state is returned in response headers on every request:
X-RateLimit-Limit-Month: 500 X-RateLimit-Remaining-Month: 484 X-RateLimit-Limit-Minute: 10 X-RateLimit-Remaining-Minute: 9 X-RateLimit-Reset: 1753315200
Model-agnostic routing
The model field in the response reflects which model the router selected for this request. The router picks a provider and model per task based on latency, availability and cost, and fails over during an incident. You do not specify a model in the request body.
On Scale, register your own endpoints in the dashboard under Settings > Model endpoints. When at least one custom endpoint is registered and healthy, the router sends analysis traffic there first. The model field will reflect your endpoint's identifier.
Error codes
Ember returns standard HTTP status codes. The body is always a JSON object with an error string.
// 401 Unauthorized
// Missing or invalid API key.
{ "error": "invalid_api_key" }
// 402 Payment Required
// Free-tier monthly incident limit reached (10/month).
// Upgrade to Team or wait for the quota reset.
{ "error": "quota_exceeded", "quota": "incidents_per_month", "limit": 10, "reset": 1753315200 }
// 422 Unprocessable Entity
// Request body is missing a required field or is malformed JSON.
{ "error": "validation_error", "field": "alarm", "message": "required" }
// 429 Too Many Requests
// Per-minute rate limit exceeded. Retry after the value in Retry-After.
{ "error": "rate_limited", "retry_after": 14 }
// 502 Bad Gateway
// All model providers returned errors or timed out. The incident was not analyzed.
// Retry with exponential backoff. Ember will attempt a different provider on retry.
{ "error": "model_unavailable" }A 502 is rare: the router tries multiple providers before surfacing the error. If you see sustained 502 responses, check status.emberoncall.com for an active provider incident.
Questions about the API or custom limits for Scale? Contact us or email support@emberoncall.com.
Ready to wire in the API?
Create a free account, generate a key and run your first incident analysis in under five minutes.