Quickstart
Add Amdahl to Claude or ChatGPT as a connector, connect a coding agent in one command, or get an API key and have Amdahl write a message from what you know about the recipient, with no data connected. Once your conversations sync, check what the workspace holds, ask it a question, and grade a draft against your own customers.
Your first result needs no data: tell Amdahl who you are writing to and what you know about them, and it writes the message in your own voice. Pick the path that fits you.
Pick your path
You use the Claude or ChatGPT app. Add Amdahl as a connector with the URL https://app.amdahl.ai/mcp and sign in with your work email. The steps are on Connect your agent. Then open a new chat and say who you want to write to and why.
You use a coding agent (Claude Code, Codex, Cursor). Run one command. It tells you how to sign in from the client, with your work email. There is no key to copy.
npx @amdahl/cli install claude-codeUse codex or cursor in place of claude-code for those clients. Then paste this into the client:
Load and follow https://docs.amdahl.ai/skills/amdahl-optimizer/SKILL.mdOther clients are on Connect your agent. Onboard with an agent has the longer first run.
You are writing code or running a server. Get an API key and follow the steps below.
You have no workspace yet. Sign in at console.amdahl.ai/try with your work email and try the optimizer on your own recipients with 25 free runs. Your first run also puts you on the beta waitlist. When we let you in, create your workspace at console.amdahl.ai/new. If your company already uses Amdahl, ask an admin there to add you.
The API, step by step
Every call below goes to one base URL and sends your key in the X-API-Key header (Authorization: Bearer works the same):
https://app.amdahl.ai/api/platform/v1Every successful JSON response is wrapped in a data envelope, so the field you want is always one level in: .data.results, not .results. Error shapes are on Errors.
Get a key
One command, approved in the console. Access:
Customer agent.amdahl keys create
Write a message
What you know about the recipient in, a message out. No data needed; usually 1 to 2 minutes.
messages.optimize
Check what is there
Once synced. Row counts and dates. Works on any workspace, even an empty one.
search.overview
Ask a question
Once synced. Plain language in, rows and the SQL it ran back out.
search.query
Grade a draft
Once synced. A pass or fail per rubric line, with the customer quotes behind it.
evals.run
Not sure which call fits your job? See Which call when. The API reference also sends any request on this page from your browser.
Step 1: Get an API key
npx @amdahl/cli login
npx @amdahl/cli keys create --name "Quickstart" --preset agentlogin signs you in through your browser. keys create shows a short code and opens the console. Type the code there and click Approve, and the terminal prints the key once. See Command line (CLI).
Prefer the console? Open your workspace at console.amdahl.ai, go to Settings, then Developer, and click Create key. Keep Access at Customer agent (the default). Copy the key when it is shown; it is not shown again.
You need Customer agent access: it can optimize (messages:execute) and run evals. A Read only key can run steps 3 and 4 but gets 403 on steps 2 and 5. See Authentication.
export AMDAHL_KEY="amdhl_your_key_here"
export AMDAHL_BASE="https://app.amdahl.ai/api/platform/v1"To confirm the key can optimize before you start:
curl -s "$AMDAHL_BASE/setup/status" -H "X-API-Key: $AMDAHL_KEY" | jq '.data.optimize'allowed: true means step 2 will work. Otherwise blocker names the one thing to fix (missing_scope, role_too_low or quota_exhausted). The full response is on Connections.
Step 2: Write a message (no data needed)
This is the fastest first result, and it works on a brand-new workspace. Tell the optimizer who the recipient is, why you write now, what you offer and what you want them to do, one fact per entry. It writes the message from that, scores each version it tries, and returns the one that graded best. A draft is optional: see Context.
curl -s -X POST "$AMDAHL_BASE/messages/optimize" \
-H "X-API-Key: $AMDAHL_KEY" \
-H "Content-Type: application/json" \
--max-time 310 \
-d '{
"channel": "email",
"context": {
"facts": [
"Sam Ortiz is Head of RevOps at Brightpath, a 200-person logistics software company.",
"Brightpath posted three SDR roles last week.",
"We sell Relay, which drafts first-touch emails from CRM notes. A 150-person team using it cut SDR ramp from 90 to 45 days."
],
"rules": ["Ask for a 20-minute call next week."],
"max_words": 90
},
"include_tries": true
}' | jqIt usually takes 1 to 2 minutes and returns on the same call; there is nothing to poll. Let the call wait up to 310 seconds before giving up. The response has this shape (an illustration, not a real run; run_id, notes and tries left out):
{
"data": {
"ok": true,
"outcome": "rewritten",
"message": "Hi Sam,\n\nSaw Brightpath posted three SDR roles last week. Ramping three new reps at once is a lot of first emails to learn.\n\nRelay drafts first-touch emails from your CRM notes. A 150-person team cut SDR ramp from 90 to 45 days with it.\n\nWorth a 20-minute call next week?",
"lift": null,
"evidence": { "requested": "off", "used": false, "reason": "not_requested" },
"on_unsupported": "flag",
"unsupported_claims": null,
"context": { "rules": 1, "voice_examples": 0, "facts": 3, "max_words": 90 }
}
}- The message is only as good as the facts. Every specific line above comes from a fact. Send one thin fact and you get a generic message. Context says what to send.
- Check what arrived.
contextcounts the facts and rules the optimizer received. Iffactsis 0 or 1, add more before you judge the message. triesshows the path.include_tries: trueadds every version the run scored, each with its Optimizerscore,human_tone, what itaimed_at, and whether it was the onereturned. Those scores come from the optimizer's own grader during the search, so they show the path, not proof. See Readingtries.- Use
messageas returned. Do not polish it with another model. To change it, add a rule or a fact and run it again, or edit it yourself. - It is a style pass. With no
evidence, the message is judged on how it reads and how clearly it asks. Nothing checked its claims against your customers, which is whyunsupported_claimsisnull. - Say the ask in a rule. Without one, the optimizer may close with a question the reader can answer in one line.
- A
messagefield is the starting point. If your code sends a draft withfactsorrules, the message starts from it and keeps what it says. A draft sent alone is improved, and the response carries adraft_improved_no_contextnote. Put what your code knows into thefactsofcontext. ok: falsemeans nothing was written and there is nomessagefield. Readreason, orerror.codeforquota_exceeded: API keys and connected agents get 1,000 optimizer calls per workspace per month, then429. On success,omittedlists what the message left out because your context could not back it, each with why;[]means nothing.- Once your conversations have synced, add
"evidence": "workspace"to also grade the message against what your customers said (slower). On a workspace with recipient evidence turned on, also sendrecipient, and Amdahl adds what it finds about them in your own data. See Recipient fields.
Every field of this response, read end to end, is in Optimize a cold email; the reference is Message Optimizer.
Once your conversations are synced
Steps 3 to 5 read your own customer conversations. On a workspace with nothing synced yet they return empty results and not_applicable, so connect a CRM or call recorder in the console first and wait for the first sync. Step 3 tells you whether it has landed.
Step 3: Check what is there
This call needs nothing but a key, and it tells you whether the rest of this page has anything to read:
curl -s "$AMDAHL_BASE/search/overview" -H "X-API-Key: $AMDAHL_KEY" | jq{
"data": {
"surfaces": [
{ "surface": "interactions", "row_count": 41822, "earliest_at": "2025-01-06T15:02:11.000Z", "latest_at": "2026-09-27T21:40:03.000Z", "time_field": "timestamp" },
{ "surface": "deals", "row_count": 612, "earliest_at": "2024-11-30T00:00:00.000Z", "latest_at": "2026-12-31T00:00:00.000Z", "time_field": "close_date" },
{ "surface": "deal_qualification", "row_count": 188, "earliest_at": "2026-06-02T08:15:40.000Z", "latest_at": "2026-09-28T06:01:12.000Z", "time_field": "computed_at" }
]
}
}- Every
row_countis0: your workspace has not synced any conversations yet. Search will return empty results and the eval in step 5 will come backnot_applicablewithempty_corpus. Connect a CRM or call recorder in the console, wait for the first sync, and come back. There is no shared demo workspace today. - A
row_countisnull: that read failed. It is not a zero. Retry. latest_atoninteractionsis your newest conversation. Ondealsit is the latest close date, which can be in the future.
Also data-free: GET /evals/prompt-and-message-eval returns the eval you will run in step 5, with its input fields.
Step 4: Ask a question
POST /search/query runs 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:
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{
"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
}
}
}
}Past parameter validation this lane returns success: true and reports problems as typed fields: detail.internal.status is ok or failed. compiled.sql is the query as you asked for it; the gate adds your workspace filter before running it. Typed filters, meaning-based matching and exact-phrase search go through the same endpoint; see Search.
Step 5: Grade a draft
This is the call the product is built around. Send a drafted message, and optionally the prompt behind it; the eval grades it against quotes from your own conversations and writes an improved version:
curl -s -X POST "$AMDAHL_BASE/evals/run" \
-H "X-API-Key: $AMDAHL_KEY" \
-H "Content-Type: application/json" \
-d '{
"eval": "prompt-and-message-eval",
"inputs": {
"prompt": "Write a short first-touch email to a VP of Engineering about our rollout tooling.",
"message": "Hi Dana - saw Northwind is scaling fast. We help engineering teams ship 10x faster with best-in-class rollout tooling. Worth a quick 15 minutes next week?"
}
}' | jqIf the draft leans on something you know first-hand that no recorded conversation holds (what the recipient told you at a dinner, say), add it beside inputs so the grade does not mark a true claim as invented:
"context": {
"facts": ["Met Dana at the GTM dinner on Sep 24; she said her SDRs rewrite every AI draft."]
}A fact licenses exactly what it says and no more, and is never counted as a customer quote. See What you know.
It returns a handle at once, not a verdict:
{
"data": {
"success": true,
"run_id": "b1c2d3e4-...",
"reused": false,
"status": "queued",
"eval_slug": "prompt-and-message-eval",
"eval_version": "2.38.0",
"resource": "eval-run://b1c2d3e4-...",
"console_url": "https://console.amdahl.ai/..."
}
}A full run takes a few minutes. Wait for it with wait_ms, which makes the server block for up to 30 seconds per read instead of you polling in a tight loop:
RUN_ID="b1c2d3e4-..."
until s=$(curl -s "$AMDAHL_BASE/eval-runs/$RUN_ID?wait_ms=30000" -H "X-API-Key: $AMDAHL_KEY" | jq -r .data.status);
[[ "$s" =~ ^(complete|failed|canceled)$ ]]; do :; done
echo "$s"The finished status is complete, never completed. Then read the report card:
curl -s "$AMDAHL_BASE/eval-runs/$RUN_ID/report" -H "X-API-Key: $AMDAHL_KEY" | jq -r .data.report.markdownThe card leads with how many rubric lines your draft passed, the quotes it was graded against, and the improved prompt. Three things to know when you read it:
- Quote the checks fraction (
headline.submitted.checkson the report, for example 1 of 5). It is the only number on the card about your writing. - The improved message is an illustration, marked
illustration_only. The improved prompt is what to keep. not_applicableis not a fail. It means nothing was graded, andnot_applicable_reasonsays why.
In a pipeline, send "mode": "gate" inside inputs and read GET /eval-runs/{id}/gate for a send/hold bit. See Gate before send.
Step 6: Hand it to an agent
With Amdahl connected to your AI client (see Pick your path), paste:
Load and follow https://docs.amdahl.ai/skills/amdahl-optimizer/SKILL.mdThe agent finds a few people you are about to write to in the tools it can reach, gathers what it knows about each one, and has Amdahl write each message. It shows you each message and every try it scored, and changes nothing until you approve. After that, "write to this person" works for any recipient.
A coding agent such as Codex or Claude Code can use the key instead: set AMDAHL_KEY in its environment, not in the chat. Onboard with an agent has the full tour, which goes on to search and evals once your data is synced.
What's next
- Context: what to send so the message is specific.
- Optimize a cold email: one optimizer response, every field read end to end.
- Optimize a campaign and Optimize before you send: the Optimizer on a whole spreadsheet of recipients, and in front of the code that sends them.
- Message Optimizer: the fields, the monthly cap, and what happens when it cannot write a message.
- Primitives: the Optimizer, the two calls over your corpus, and the orchestration layer on top.
- Patterns: cohort then voice, gate before send, pinned-evidence A/B.
- Grade a cold email: one eval run read end to end.
- Chat: hand a multi-step investigation to a server-side agent and poll for the answer.
- Read the docs over MCP: point your coding agent at
https://docs.amdahl.ai/mcpand it reads these pages itself. Public, no key.
If something breaks, start with Errors, then Rate limits if you see 429.