v1 · Create storybooks programmatically from your CRM, a spreadsheet, or any tool.
Send your API key as a bearer token on every request. Generate the key in your dashboard under Account → API Access:
Authorization: Bearer asb_live_xxxxxxxxxxxx
Keep keys secret — anyone with a key can create storybooks on your account. Rate limit: 60 requests/minute.
Team and Brokerage. A key on any other plan authenticates fine and then gets 403 plan from the create endpoint, because the key is real and the entitlement is not — worth distinguishing when you are debugging, since 401 and 403 mean two different fixes.
Verify before you build: GET /api/v1/me returns the plan the key resolves to. If it does not say team or brokerage, creation will fail no matter how well-formed the body is.
60 requests per minute, per account, in a fixed window. Every response carries X-RateLimit-Limit and X-RateLimit-Remaining, so you can back off before you are cut off rather than after.
The window is fixed, not rolling: it resets on the minute rather than 60 seconds after your first call. A burst that straddles the boundary can therefore send up to 120 requests in a 60-second span without a 429. Do not build on that — it is an artifact, not a promise.
Send one POST with the client, the address and the dates. Everything else has a default, and the cover is generated for you.
POST https://agentstorybook.ai/api/v1/storybooks
Leave the cover to us. By default, when no clientPhotoUrl is supplied we feature the agent — illustrated from the headshot & sign saved in the account — standing proudly by the home (their own billboard). Provide a clientPhotoUrl to star the client instead, or set coverStar explicitly (agent, client, or home). Add a housePhotoUrl for the home (sellers fall back to Street View), or supply a finished coverImageUrl to skip generation entirely. Cover generation can take 30–60 seconds.
| Field | Required | Notes |
|---|---|---|
type | yes | "seller" or "buyer" |
address | yes | Full street address. Used for the auto cover. |
clients | no | Full display name, used as-is. e.g. "John & Sarah Smith" |
client1, client2 | no | Two owner names from your CRM. We format smartly: shared surname → "John & Sarah Smith"; different → "John Smith & Sarah Jones". Ignored if clients is set. |
agent | no | Agent name shown on the storybook |
brand | no | Brokerage / brand name. Printed on the SOLD sign only when you have not uploaded a yard-sign photo in Account. |
dates | no | Object. Seller: listing, list, prePhoto, photo, contract, closing. Buyer: consultation, seeHomes, contract, closing. ISO dates (2026-07-01). |
clientPhotoUrl | no | Public image URL of the client(s). We illustrate their likeness on the cover. Omit to feature the agent by default (or set coverStar). |
coverStar | no | Who the cover features: agent (from the account headshot + sign), client (requires clientPhotoUrl), or home. Defaults to client when a client photo is given, otherwise agent (falling back to home if no headshot is on file). |
housePhotoUrl | no | Public image URL of the home. Sellers fall back to Street View if omitted. |
coverImageUrl | no | Supply your own cover image instead of auto-generating. |
waitForCover | no | Default true (waits ~30–60s and returns when the cover is ready). Set false to return immediately with coverStatus:"generating" — recommended for Zapier and other timeout-sensitive callers. |
idempotencyKey | no | Reuse the same value to avoid duplicates on retries. |
curl -X POST https://agentstorybook.ai/api/v1/storybooks \
-H "Authorization: Bearer asb_live_xxxx" \
-H "Content-Type: application/json" \
-d '{
"type": "seller",
"address": "123 Main St, Dallas, TX 75201",
"clients": "John & Sarah Smith",
"agent": "Thomas Eaves",
"dates": { "listing": "2026-07-01", "closing": "2026-08-15" },
"idempotencyKey": "crm-deal-48213"
}'
{
"slug": "123-main-st",
"type": "seller",
"clientUrl": "https://agentstorybook.ai/stories/123-main-st?token=...",
"publicUrl": "https://agentstorybook.ai/stories/123-main-st",
"coverGenerated": true,
"coverStatus": "ready",
"status": "ready"
}
Four more endpoints, all read or delete — creation is the only write.
GET /api/v1/storybooks — list your storybooks.
GET /api/v1/storybooks?slug=123-main-st — fetch one.
DELETE /api/v1/storybooks?slug=123-main-st — delete one.
GET /api/v1/me — verify a key; returns your plan.
Send an idempotencyKey and reuse the exact same value on every retry of the same logical create. The first call does the work; any repeat within 24 hours returns the original response with idempotentReplay: true added, and creates nothing.
Use something stable from your own system — a CRM deal id, a spreadsheet row id — not a timestamp or a random value, which would make each retry look like a new storybook. This matters more than it sounds: a create can take 30 to 60 seconds while the cover generates, which is long enough for an HTTP client, a queue worker or a Zapier step to give up and try again on a request that in fact succeeded.
Because the cover is illustrated at create time, not pulled from a template. That is the slowest thing the endpoint does, and by default the request waits for it and returns coverStatus: "ready".
If your caller has a shorter timeout than that — Zapier actions cut off around 30 seconds — send waitForCover: false. The call returns immediately with coverStatus: "generating" and the storybook is created; the cover lands on its own a minute later. Poll GET /api/v1/storybooks?slug=... if you need to know when it is done. The client link works either way; it simply shows the cover once it exists.
Every failure returns the same shape — { "error": "code", "message": "..." } — so you can branch on error and show message to whoever is watching the integration.
| HTTP | error | What actually happened |
|---|---|---|
| 401 | unauthorized | No key, or a key that has been revoked. Check the Authorization header is Bearer plus the key, with nothing extra. |
| 400 | invalid | A required field is missing or the wrong shape. message names the field. |
| 403 | plan | The key is valid but the account is not on Team or Brokerage. See the plans question above. |
| 404 | not_found | No storybook with that slug on your account. A slug belonging to another account returns 404 rather than 403, on purpose — it does not confirm the storybook exists. |
| 405 | method_not_allowed | The endpoint does not take that verb. |
| 429 | rate_limited | More than 60 requests in the current minute. Wait for the window to roll over. |
| 500 | server_error | Our side. Safe to retry with the same idempotencyKey. |
Questions? Reply to your AgentStorybook welcome email.