Quickstart
From nothing to your first live answers in under five minutes - mint a key, ask your conversations a plain-language question, pull a deterministic slice, and hand a bigger question to an agent.
Get from nothing to your first live answers in under five minutes: mint an API key, ask your warehouse a plain-language question, pull a deterministic filtered slice, and hand a bigger question to an agent — three real calls against your own data.
All consumer endpoints live under one base URL:
https://app.amdahl.ai/api/platform/v1Every request sends your API key in the X-API-Key header (sending it as Authorization: Bearer works identically — one key, both transports). Everything below is plain HTTPS with JSON bodies, so the same flow works from a CLI, a backend service, or a long-running agent.
Every successful JSON response is wrapped in a data envelope. The body is { "data": { … } }, so the field you want is always one level in — .data.results, not .results. Response samples on these pages show the envelope. Four kinds of response are deliberately NOT enveloped, because they are not a single JSON document: the file downloads GET /evals/export.csv and GET /evals/export.jsonl (a streamed file body under Content-Disposition), the event streams (any stream_url, and any endpoint you call with Accept: text/event-stream, which returns text/event-stream frames instead of a JSON body), and GET /openapi.json (the raw OpenAPI document). Errors are a further exception and do not all share one shape; see Pagination and errors before you write your error handling.
Get a key
Settings, Developer, Create key. Permissions:
Customer agent.console
Ask a question
Plain language in, rows plus the SQL it ran back out.
search.query
Pull a slice
Typed predicates, deterministic, no model in the path.
mode=filter
Ask for more
Too big for one call? Hand it off and poll.
POST /chat
Prerequisites
curl, and optionallyjqfor readable output.- A workspace with synced data. Every call below reads your conversation corpus, so a workspace still finishing its first CRM and call sync has nothing to answer with — and it will not say so, because the search lane returns
success: trueeither way. The tell isdetail.coverageon the response:total_rows: 0withlatest_event_at: nullmeans the workspace is empty and no query will return a row. A non-zerototal_rowswith a reallatest_event_atmeans the data is there, and an emptyresultsis then a genuine "nothing matched". (coverage: nullmeans only that the freshness probe itself failed — it is not a verdict either way.)
Step 1: Get an API key
Go to console.amdahl.ai, open the workspace you want to operate against, and head to Settings -> Developer -> Create key. The dialog asks for a name, a Permissions level, and an expiration — permissions are named bundles, not free-form scopes, so you pick one from the list rather than typing a scope string.
Pick Customer agent, which is the default. It is the only self-serve bundle that covers all four steps below: Read only carries data:read and is enough for steps 2 and 3, but it omits both conversations:write and external_search:execute, so step 4 cannot start a Chat at all — and a Chat that does start without the latter is confined to your own corpus and never reaches the market fan-out this page tells you to expect. (Internal agent and Full admin only appear if you are a workspace admin, and neither adds anything the quickstart needs.)
Copy the plaintext value the instant it is shown — the server stores only a hash and will never display it again; a lost key must be replaced, not recovered. Full details (OAuth, JWT, the scope matrix) are in Authentication.
Export it so the rest of the examples stay short:
export AMDAHL_KEY="amdhl_your_key_here"
export AMDAHL_BASE="https://app.amdahl.ai/api/platform/v1"Step 2: Ask your first question
POST /search/query is the fast lane: one blocking call over your warehouse. Ask in plain language with "mode": "fuzzy" and it turns the ask into SQL, runs it, and returns the rows and the SQL it ran. It needs only data:read.
curl -s -X POST "$AMDAHL_BASE/search/query" \
-H "X-API-Key: $AMDAHL_KEY" \
-H "Content-Type: application/json" \
-d '{ "query": "how many calls did we have in the last 30 days?", "mode": "fuzzy" }' \
| jqExpected response shape (trimmed — the Search endpoint guide documents every field):
{
"data": {
"success": true,
"mode_ran": "fuzzy",
"results": [{ "calls": 214 }],
"compiled": {
"sql": "SELECT COUNT(DISTINCT interaction_id) AS calls FROM interactions WHERE …"
},
"detail": {
"internal": { "status": "ok", "rows": [{ "calls": 214 }], "row_count": 1 },
"message": "Found 1 row(s) in your workspace data.",
"coverage": {
"latest_event_at": "2026-07-21T05:01:50Z",
"total_rows": 41822,
"days_behind": 1,
"is_stale": false
}
}
}
}Three things to notice: past parameter validation the lane always returns success: true (every failure mode is a typed field like detail.internal.status, never a raw error); compiled.sql is the receipt for the query you asked for — the gate then resolves the table to its warehouse name and injects your tenant filter and any data-scope predicate before running it, so it is a record of intent rather than a statement you can paste back verbatim; and detail.coverage tells you how current your warehouse data is. A 401 here means the key is missing or wrong; a 403 with not_on_public_api means the key reached an operation that is not on the public surface.
Step 3: Pull a deterministic slice
When you want predicates instead of prose — the same slice every run, no model interpreting your wording — use the same endpoint with typed filters instead of a question. The open pipeline over $50k, largest first:
curl -s -X POST "$AMDAHL_BASE/search/query" \
-H "X-API-Key: $AMDAHL_KEY" \
-H "Content-Type: application/json" \
-d '{
"surface": "deals",
"filters": [
{ "field": "deal_stage_status", "op": "eq", "value": "open" },
{ "field": "deal_amount", "op": "gte", "value": 50000 }
],
"order_by": { "field": "deal_amount", "dir": "desc" },
"limit": 10
}' | jq{
"data": {
"success": true,
"mode_ran": "filter",
"results": [
{
"deal_id": "9214…",
"deal_name": "Northwind expansion",
"deal_amount": 180000,
"deal_stage_status": "open"
}
],
"compiled": {
"sql": "SELECT … FROM deals WHERE `deal_stage_status` = 'open' AND `deal_amount` >= 50000 …"
}
}
}Field names are never guessed: GET /search/fields lists every filterable field per surface with its type and operators, and a wrong one comes back as a typed invalid_argument naming the allowed set. The full filter DSL (plus the fuzzy and semantic lanes behind the same door) is in the Search endpoint guide.
Step 4: Hand off a bigger question
Steps 2 and 3 block for one answer. When the ask needs several steps — decomposition, outside signal, a written deliverable — start a Chat instead. It never answers inside one call: you get handles back immediately and poll.
curl -s -X POST "$AMDAHL_BASE/chat" \
-H "X-API-Key: $AMDAHL_KEY" \
-H "Content-Type: application/json" \
-d '{ "input": "What are the top objections in enterprise deals this quarter?" }' | jqThe response carries chat_id, run_id, status: "queued" and a read_url. Poll that URL until status settles (complete, awaiting_input, failed), or stream it — both, plus answering a pause, are in the Chat guide.
What's next
You now have working auth and three live reads against your own data. Common next stops:
- Endpoints — the whole synchronous surface and how its three lanes differ.
- Chat — hand a multi-step, server-side investigation to an agent and poll for the answer.
- Routines — schedule a Chat to run itself on a cadence.
- API reference — the operation catalog and OpenAPI spec.
If something breaks, start with Pagination and errors to decode the error envelope, then check Rate limits if you are seeing rate_limited.