Docs

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:

code
https://app.amdahl.ai/api/platform/v1

Every 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.

What you are about to do
  1. Get a key

    Settings, Developer, Create key. Permissions: Customer agent.

    console

  2. Ask a question

    Plain language in, rows plus the SQL it ran back out.

    search.query

  3. Pull a slice

    Typed predicates, deterministic, no model in the path.

    mode=filter

  4. Ask for more

    Too big for one call? Hand it off and poll.

    POST /chat

Prerequisites

  • curl, and optionally jq for 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: true either way. The tell is detail.coverage on the response: total_rows: 0 with latest_event_at: null means the workspace is empty and no query will return a row. A non-zero total_rows with a real latest_event_at means the data is there, and an empty results is then a genuine "nothing matched". (coverage: null means 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:

bash
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.

bash
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" }' \
  | jq

Expected response shape (trimmed — the Search endpoint guide documents every field):

json
{
  "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:

bash
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
json
{
  "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.

bash
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?" }' | jq

The 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.