API Documentation
Everything you need to run MyFast AI Answer Engine Optimization reports programmatically for your clients. Don't have a key yet? Create an enterprise account.
Overview
The Enterprise API lets agencies and platforms run AEO reports for any number of clients from their own systems. You send one authenticated request per client; we queue it, run the full analysis across every AI engine, and deliver a PDF report plus a companion transcript file. You can optionally request a developer bundle of ready-to-publish fix files. All endpoints are rooted at:
https://aeo.myfast.ai/api/enterprise/v1Requests and responses are JSON. Reports never run inline: a request returns immediately with a 202 Accepted and a job you can poll or receive by webhook. Behind the scenes we run up to five reports concurrently, and within your own account your jobs are worked in the order you submit them (per-account FIFO) — so a burst from one client can never starve another account, and your own reports keep their order.
Three ways to generate reports
You do not need to write any code to run reports. Every account has three ways to generate them, and all three tag and organize each report by client in your back office — so you can come back at any time and add a developer pack to a past report, then hand the correct download link to the right client.
Best for platforms and agencies with engineering resources. Send one authenticated request per client from your own systems. Everything on this page documents this path.
No code required. In your back office, open Generate Reports → Individual, key in the client’s business details, optionally tick “also generate the developer pack,” and submit.
Run many clients at once. In Generate Reports → Bulk CSV, drag & drop, choose a file, or paste CSV text. We validate every row, then queue and tag one report per row. Format is documented below.
Whichever path you use, finished reports and developer packs collect in the Client Deliveries list in your back office, grouped by yourclientTag. Each delivered report also carries an Add developer pack action so you can purchase and attach the pack later without re-running the report.
Bulk CSV format
The bulk uploader accepts a standard comma-separated file. The first row must be a header row naming the columns (case-insensitive; common aliases are accepted). Each following row is one client report. You can upload up to 500 rows at a time. Onlydomain, businessName and category are required — every other column is optional.
| Column | Required? | Description |
|---|---|---|
| domain | required | Client website domain, e.g. acmeplumbing.com. |
| businessName | required | Business name as it should appear in searches. Wrap in "quotes" if it contains a comma. |
| category | required | Business category or type, e.g. Plumber or SaaS CRM. |
| keywordCount | optional (default 100) | Buyer keywords to test, 100–1000, rounded to the nearest 100. |
| clientTag | optional | Your own label for the client, e.g. client2175. Used to group deliveries in your back office. |
| wantDevBundle | optional (default false) | Put true / yes / y / 1 / x to also generate the developer pack for this row. |
| deliverEmail | optional | Email address to send this client’s finished report to. |
| targetAudience | optional | Ideal customer, to sharpen keyword generation. |
| location | optional | City / region for local businesses. |
| radiusMiles | optional | Service radius for local businesses. |
| competitors | optional | Known competitors, as free text in a single cell (wrap in "quotes" if it contains commas). |
| contactName | optional | Client contact name stored with the report. |
| contactEmail | optional | Client contact email stored with the report. |
| contactPhone | optional | Client contact phone stored with the report. |
Example file
Wrap any value that contains a comma in double quotes. A minimal file needs only the three required columns; here is a fuller example that also tags clients and requests developer packs:
domain,businessName,category,keywordCount,clientTag,wantDevBundle,deliverEmail
acmeplumbing.com,Acme Plumbing,Plumber,100,client2175,true,[email protected]
brightsmiledental.com,"Bright Smile Dental, PC",Dentist,200,client2176,,
summitcrm.io,Summit CRM,SaaS CRM,300,client2177,yes,[email protected]The back office also has a Download template button that gives you this exact header row to fill in. When you submit, we show you how many rows were accepted, how many were skipped, and the reason for each invalid row so you can fix and re-upload just those.
Authentication
Every request must carry your secret API key. Once your card is on file, open your back office and click Generate & reveal my API key — the key is shown exactly once, so copy it into your secret manager right away. Send it in an Authorization header using the Bearer scheme (an x-api-key header is also accepted):
Authorization: Bearer ent_live_your_key_hereKeys are secret. Store them server-side in a secret manager and never expose them in browser code. If a key is lost or compromised, rotate it — the old key is revoked immediately and a new one issued.
Create a report
/api/enterprise/v1/reportsQueues a new AEO report for one client. Returns immediately with a job you can track. Your account's jobs are worked in submission order, and the response tells you your current queue position and an estimated wait. Pass an Idempotency-Key header to make retries safe (see Idempotency).
Request body
| Field | Type | Description |
|---|---|---|
| domain | string (required) | The client website domain, e.g. "acmeplumbing.com". |
| businessName | string (required) | The client business name as it should appear in searches. |
| category | string (required) | The business category or type, e.g. "Plumber", "SaaS CRM". |
| keywordCount | number (optional, default 100) | How many buyer keywords to test. 100–1000, rounded to the nearest 100. This is a report-depth control only — it does not change the price. Every report is a flat $49 for your first 5, then $24 each after that. |
| clientTag | string (optional) | Your own label for the client this report belongs to — any value you like, e.g. "acme-plumbing" or "client2175". Echoed back on the job, printed on delivery emails, tagged onto delivered files, and used to group deliveries by client in your back office so you always know whose report is whose. |
| wantDevBundle | boolean (optional, default false) | Also generate a developer bundle for this report. The developer pack is $199 for your first 5 packs, then $99 each after that — billed the first time you buy the pack for a given business, and every later report for that same business gets the pack free (no charge). |
| deliverEmail | string (optional) | Email address to send the finished report and companion file to (in addition to the status URL). |
| webhookUrl | string (optional) | Override your account-level completion webhook for this one report. When set, we POST a signed report.completed / report.failed event to this URL. Leave unset to use your saved account webhook (if any). |
| targetAudience | string (optional) | Describe the ideal customer to sharpen keyword generation. |
| location | string (optional) | City / region for local businesses. |
| radiusMiles | number (optional) | Service radius for local businesses. |
| competitors | string[] (optional) | Known competitors to factor into the analysis. |
| contactName / contactEmail / contactPhone | string (optional) | Client contact details stored with the report. |
Example request
curl -X POST https://aeo.myfast.ai/api/enterprise/v1/reports \
-H "Authorization: Bearer ent_live_your_key_here" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1c2e-acme-2026-01-05" \
-d '{
"domain": "acmeplumbing.com",
"businessName": "Acme Plumbing",
"category": "Plumber",
"keywordCount": 100,
"clientTag": "acme-plumbing",
"wantDevBundle": false,
"deliverEmail": "[email protected]"
}'Example response (202)
{
"jobId": "clx...",
"status": "queued",
"type": "report",
"keywordCount": 100,
"clientTag": "acme-plumbing",
"wantDevBundle": false,
"devBundleAlreadyOwned": false,
"devBundleCharge": "$0",
"estimatedCharge": "$49",
"queuePosition": 3,
"queueDepth": 3,
"estimatedWait": "6 min",
"concurrency": 5,
"webhookUrl": "https://your-app.com/hooks/myfast",
"statusUrl": "/api/enterprise/v1/reports/clx...",
"message": "Report queued. It will be processed in request order and delivered by email and via this status URL."
}queuePosition is your job's 1-based place in the global queue (null once it starts), queueDepth is how many jobs are waiting, and estimatedWait is a human-readable ETA based on recent average run times and the {5} concurrent slots. webhookUrl echoes where we'll POST the completion event, if configured.
When wantDevBundle is true, the response also returns devBundleAlreadyOwned and devBundleCharge. If you already bought the developer pack for this business, devBundleAlreadyOwned is true, devBundleCharge is $0, and estimatedCharge covers only the report — the pack is regenerated and delivered free.
Check job status
/api/enterprise/v1/reports/:jobIdPoll this endpoint (or use the statusUrl returned at creation) to track a job. Status moves through queued → processing → completed (or failed). When complete, result holds the delivery links.
Example response (completed)
{
"jobId": "clx...",
"type": "report",
"status": "completed",
"domain": "acmeplumbing.com",
"businessName": "Acme Plumbing",
"clientTag": "acme-plumbing",
"wantDevBundle": false,
"reportId": "rep_...",
"result": {
"pdfUrl": "https://.../report.pdf",
"companionUrl": "https://.../what-ai-said.html",
"bundleUrl": null
},
"error": null,
"attempts": 1,
"durationMs": 394000,
"queuePosition": null,
"queueDepth": 2,
"estimatedWait": null,
"createdAt": "2026-01-05T14:00:00.000Z",
"startedAt": "2026-01-05T14:00:07.000Z",
"completedAt": "2026-01-05T14:06:41.000Z"
}durationMs is the wall-clock processing time once a job completes. While a job is still queued, queuePosition and estimatedWait are populated; both become null once it moves to processing.
List jobs
/api/enterprise/v1/reportsReturns your 50 most recent jobs (reports and bundles), newest first, each in the same shape as the status response. Useful for reconciling deliveries or building a dashboard.
{ "jobs": [ { "jobId": "clx...", "type": "report", "status": "completed", ... } ] }Request a developer bundle
/api/enterprise/v1/bundleGenerate a developer bundle for an existing completed report. (To get the bundle at the same time as the report, set wantDevBundle: true on the report request instead.) The bundle contains a custom llms.txt, JSON-LD schema, answer & FAQ pages, an updated sitemap and an install guide — tagged to your client. The developer pack is a one-time purchase per business: you are billed $199 for your first 5 packs (then $99 each) the first time you buy it for a given business, and every later bundle for that same business is free. The response includes devBundleAlreadyOwned so you know whether this call will be charged.
Request body
{
"reportId": "rep_...", // required — must belong to your account and be completed
"clientTag": "acme-plumbing", // optional — defaults to the report's tag
"deliverEmail": "[email protected]" // optional
}Example response (202)
{
"jobId": "clx...",
"status": "queued",
"type": "dev_bundle",
"reportId": "rep_...",
"devBundleAlreadyOwned": false,
"estimatedCharge": "$199",
"queuePosition": 2,
"queueDepth": 2,
"estimatedWait": "6 min",
"concurrency": 5,
"statusUrl": "/api/enterprise/v1/reports/clx..."
}Queue & throughput
Reports are processed by a pool that runs up to five reports at the same time. Within a single account, jobs start in the order you submitted them (per-account FIFO), so your own reports never jump ahead of each other, and no single account can monopolise the pool at the expense of others.
- Position & ETA. Creating a report returns
queuePosition,queueDepthand a human-readableestimatedWait. The same fields appear when you poll a queued job, so you can show clients an accurate “time remaining”. - Automatic retries. A job that hits a transient error is retried automatically with back-off (up to three attempts). The current count is on every job as
attempts. - Self-healing. A maintenance sweep runs continuously in the background to re-queue any job whose worker was interrupted, so nothing gets stuck. You never have to intervene.
- Live metrics. Your back office shows live concurrency, queued and processing counts, 24-hour completions, average run time and an estimated queue-drain time.
Idempotency
Network timeouts and retries are a fact of life. To make a create-report or create-bundle call safe to retry, send an Idempotency-Key header with a value that is unique to that logical request (a UUID, or something like client2175-2026-01-05). You may also pass it in the JSON body as idempotencyKey.
- The first request with a given key creates the job and returns
202 Accepted. - Any later request with the same key (per account) returns the same job with
200 OKand"idempotent": true— no duplicate report is ever queued, and you are never double-charged. - Keys are scoped to your account. Concurrent retries with the same key are handled safely — exactly one job is created.
# Retrying with the same key returns the original job, not a new one
curl -X POST https://aeo.myfast.ai/api/enterprise/v1/reports \
-H "Authorization: Bearer ent_live_your_key_here" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: client2175-2026-01-05" \
-d '{ "domain": "acmeplumbing.com", "businessName": "Acme Plumbing", "category": "Plumber" }'
# -> 200 OK { "jobId": "clx...", "idempotent": true, ... }Completion webhooks
Instead of polling, you can have us call you when a job finishes. Set a Webhook URL in your back office (applies to every report), or pass webhookUrl on a single request to override it just for that job. We send an HTTP POST with a JSON body the moment a job reaches completed or failed.
Events
report.completed and report.failed. The event name is also sent in an X-MyFast-Event header.
Payload
POST https://your-app.com/hooks/myfast
X-MyFast-Event: report.completed
X-MyFast-Signature: sha256=<hex hmac of the raw body>
Content-Type: application/json
{
"event": "report.completed",
"createdAt": "2026-01-05T14:06:41.500Z",
"data": {
"jobId": "clx...",
"type": "report",
"status": "completed",
"domain": "acmeplumbing.com",
"businessName": "Acme Plumbing",
"clientTag": "acme-plumbing",
"reportId": "rep_...",
"result": { "pdfUrl": "https://...", "companionUrl": "https://...", "bundleUrl": null },
"error": null,
"attempts": 1,
"durationMs": 394000,
"completedAt": "2026-01-05T14:06:41.000Z"
}
}Verifying the signature
Each delivery is signed so you can trust it came from us. Compute an HMAC-SHA256 of the raw request body using your webhook signing secret (shown in your back office, prefixed whsec_), hex-encode it, and compare it to the X-MyFast-Signature header (which is prefixed sha256=). Use a constant-time comparison.
// Node.js (Express raw body)
import crypto from 'crypto';
const signature = req.headers['x-myfast-signature']; // "sha256=abc123..."
const expected = 'sha256=' + crypto
.createHmac('sha256', process.env.MYFAST_WEBHOOK_SECRET) // whsec_...
.update(req.rawBody) // the exact bytes we sent
.digest('hex');
const ok = crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
if (!ok) return res.status(400).send('bad signature');Respond with any 2xx status to acknowledge. Webhooks are best-effort notifications — the job status URL is always the source of truth, so if you miss a delivery you can still poll or use the deliveries list in your back office.
Delivery & billing
- Deliverables. Every report is delivered as a PDF plus a separate companion file with the verbatim “what AI said, word for word” transcript. Files are delivered as secure links (by email if you set
deliverEmail, and always on the job's status URL). - Who we email. You control this from your back office under Delivery Preferences. Choose “We handle delivery” and we email each client their links directly under your brand; choose “I fulfill my clients” and we never contact your clients — every link is sent to you instead and listed in your back office, grouped by
clientTag, so you deliver them yourself. You can also email yourself a single digest of all client links, or the links for one client, at any time. - Developer pack entitlement. The developer pack is a one-time purchase per business. Once you buy it for a business, every future report for that same business can include the pack free. A different business requires its own one-time purchase.
- Pricing (preferred-client tiers). Reports are a flat $49 each for your first 5, then $24 each after that. Developer packs are $199 for your first5, then $99 each — charged once per business, free on every later report for that same business. There is no setup fee and the tier is worked out per account.
- Pay-as-you-go. There is no weekly run-up and no batch invoice. Each report or developer pack is charged to your saved card at the moment you use it, so your balance is always current.
- Failed payments. If a charge cannot be collected, your API key is paused (requests return
402 api_key_paused) and we email you a link to update your card. As soon as payment succeeds the key unlocks instantly and queued work resumes — nothing is lost.
Error codes
Errors return a JSON body with a human-readable error and a stable code you can branch on.
| HTTP | code | Meaning |
|---|---|---|
| 401 | missing_api_key | No key supplied. Send it as an Authorization: Bearer header. |
| 401 | invalid_api_key | The key does not match any active key, or it was revoked. |
| 403 | account_suspended | The enterprise account has been suspended. Contact support. |
| 402 | billing_inactive | No active payment method on file. Add a card in your back office to activate billing — there is no setup fee. |
| 402 | api_key_paused | The key is paused because a pay-as-you-go charge could not be collected. Update your card in your back office to unlock instantly. |
| 429 | daily_cap_reached | Your account's daily report cap has been reached. Try again after the UTC day rolls over, or raise the cap in your back office. |
| 400 | invalid_request | Required fields are missing or malformed. |
| 404 | not_found | The requested job or report does not belong to this account. |
| 409 | report_not_ready | A bundle was requested for a report that has not finished yet. |
How to implement it into your system
- 1. Store your key securely. Put it in your server-side secret manager or environment variables. Never ship it to a browser or mobile client.
- 2. Trigger a report from your workflow. Whenever you onboard a client or want a fresh audit, POST to
/v1/reportswith that client's details and your ownclientTag. Persist the returnedjobIdagainst the client in your database. - 3. Track completion. Either poll the
statusUrlevery minute or so untilstatusiscompleted, or setdeliverEmailand let us email the finished links. Both the PDF and companion file URLs arrive inresult. - 4. Offer the bundle when you're ready. If a client wants the done-for-you fixes, POST the report's
reportIdto/v1/bundleand hand them the resulting link. - 5. Handle the paused state. Treat a
402 api_key_pausedorbilling_inactiveas “action needed on billing” in your UI, and retry once the invoice is settled.
Ready to build?
Create your enterprise account