Skip to content
Esc
  • OverviewGuidesWhat exists today, and where to start.
  • QuickstartGuidesKey, domain, first send — in that order.
  • AuthenticationGuidesBearer keys, the mandatory User-Agent, and what each refusal means.
  • ErrorsGuidesThe whole vocabulary, with the status each name carries.
  • IdempotencyGuidesRetry a send without sending it twice.
  • PaginationGuidesCursors are item IDs, not page numbers.
  • Rate limitsGuidesTen a second per team, and the headers that tell you where you are.
  • Sending newslettersGuidesUse a campaign, or include an unsubscribe link.
  • EventsGuidesEvery event a webhook can carry, with one real payload each.
  • DomainsGuidesThe records, where they go at each registrar, and what the page does while you wait.
  • TrackingGuidesOpens and clicks: one record, two toggles, and what an open really means.
  • Sign-up formsGuidesPop-ups and embedded forms on your site, and the rules that pick one.
  • Website trackingGuidesrasket.js on your own site: the snippet, consent, three events and the check.
  • TemplatesGuidesFifty-seven starters sorted by goal, and how to keep a campaign as a template.
  • ReceivingGuidesInbound mail, and the Inbox: a webhook fires, you read it, you answer it.
  • InboxGuidesChannels, personal mailboxes and seats: who sees what, and where a reply goes.
  • Mail appsGuidesComing soon: your Rasket address in Apple Mail, Outlook and the Gmail app.
  • Inbox APIGuidesOpen, read and answer support conversations from your app, with the same key.
  • Support inbox in your appGuidesA Contact us form that opens a conversation your team answers, in five minutes.
  • Replies to your emailsGuidesOne field on a send, and the customer's reply opens a conversation.
  • Let an agent triageGuidesAn AI agent reads, notes and drafts over MCP; a person sends.
  • Node SDKGuidesThe rasket package: typed from the API's own document, retries only what is safe.
  • Python SDKGuidesThe rasket package on PyPI: the Node client's methods, in snake_case, over httpx.
  • MCP serverGuidesConnect Claude, ChatGPT or any MCP client: your scopes, no key.
  • AI assistGuidesSubject lines, drafts and diagnosis — in the dashboard and over the API, off until you allow it.
  • AgentsGuidesLet an AI agent set Rasket up: the skill, the rules file, MCP, and the recipe they share.
  • OAuthGuidesLet another app act for a team: register, authorize with PKCE, exchange, refresh.
  • Single sign-onGuidesOIDC login for your team, a domain proved by DNS, enforcement and break-glass.
  • IntegrationsGuidesEvery integration and its status, and Rasket on Vercel, Netlify and Cloudflare.
  • SMTPGuidesSend from anything that speaks SMTP: settings, setup guides, limits and replies.
  • ZapierGuidesSend email, add contacts and react to email events from a Zap, with no code.
  • n8nGuidesThe Rasket node and trigger for n8n workflows: install, connect, every operation.
  • VercelGuidesAdd Rasket on Vercel: a Sending key in each project as RASKET_API_KEY, no copying.
  • WordPressGuidesThe Rasket plugin: every email your site sends, through Rasket, with a safe fallback.
  • NotionGuidesTurn a Notion database of people into contacts in a segment, once or every hour.
  • Migrating to RasketGuidesBring contacts, unsubscribes, lists and templates from Mailchimp, Klaviyo and more.
  • PipedreamGuidesRasket actions and instant sources for Pipedream workflows and Connect apps.
  • FramerGuidesThe Rasket plugin: sign-up forms on a Framer site, and Framer forms into contacts.
  • MakeGuidesComing soon: send email and react to email events from a Make scenario.
  • monday.comGuidesTurn board items into contacts and send a template when a board changes.
  • ClickUpGuidesReady-made Zapier and Make automations between ClickUp tasks and Rasket.
  • SlackGuidesEmail alerts in the Slack channels you choose, and /rasket for numbers and look-ups.
  • Google SheetsGuidesComing soon: turn a sheet tab of people into contacts in a segment.
  • AirtableGuidesComing soon: turn an Airtable table of people into contacts in a segment.
  • ShopifyGuidesRasket for Shopify: customers with their consent, store events and ready-made flows.
  • SMS quickstartGuidesSend your first text to a US or Canadian phone.
  • Get a numberGuidesToll-free or local numbers, and the registration carriers require.
  • Consent and STOPGuidesWho you may text, the proof you keep, and replies like STOP.
  • SMS campaignsGuidesText a segment, at a local time, priced before you send.
  • Texts in automationsGuidesThe Send SMS step, and what happens to a contact without a phone.
  • SMS webhooksGuidesThe six sms events and what each one carries.
  • SMS pricingGuidesTexts included in each plan, the price per message, and number fees.
  • EmailsAPI referenceSend, batch, retrieve, list, reschedule, cancel, attachments.
  • DomainsAPI referenceAdd a domain, publish its records, verify it.
  • API keysAPI referenceCreate, list, rename and revoke credentials.
  • WebhooksAPI referencePayloads, signature verification, retries and replay.
  • SuppressionsAPI referenceAddresses we will not send to, and why.
  • LogsAPI referenceEvery request made with this team's credentials.
  • MetricsAPI referenceDelivery, bounce, complaint and engagement counts.
  • TemplatesAPI referenceVersioned email content with typed variables, addressed by ID or alias.
  • ContactsAPI referenceYour audience: contacts, their typed properties, segments and topic choices.
  • SegmentsAPI referenceAudiences defined by a filter, by hand, or both.
  • TopicsAPI referenceWhat contacts subscribe to, and the preference page's list.
  • CampaignsAPI referenceCampaigns, at /broadcasts: one message to a segment, from draft to results.
  • AutopilotAPI referenceStore campaigns Autopilot drafts for you, each waiting for a yes or a skip.
  • ImportsAPI referenceCSV uploads: column mapping, conflicts and counts.
  • AutomationsAPI referenceWorkflows that run per contact: the graph, its versions, and every run.
  • Custom eventsAPI referenceThe names your product fires, and what starts a workflow.
  • ReceivingAPI referenceMail sent to you: the message, its attachments, its raw source.
  • OAuthAPI referenceClient registration, the token endpoint, and the grants a team has given.
  • TeamAPI referenceThe team a credential belongs to: its plan, sender identity, AI flag and members.
  • BillingAPI referencePlan, usage, invoices and add-ons, and the hosted pages where a customer pays.
  • AI helpersAPI referenceSubject lines, a first draft, and why an email did what it did.
  • InboxAPI referenceChannels, conversations, messages, notes and search in your support inbox.
  • SMSAPI referenceSend, schedule and cancel texts, numbers, test phones and SMS consent.

API referenceAutopilot

Autopilot

Autopilot watches your connected store and its content, plans campaigns, and has AI write them as ordinary drafts — an email, a text, or both. Every proposal waits for a person's yes until a kind of campaign earns sending on its own, and Autopilot is off until an admin turns it on.

Access

Every call takes a Full access API key; a Sending access key is 401 restricted_api_key. An OAuth token needs the scopes below.

What each Autopilot call needs
ToNeeds
Read the settings and proposalsautopilot:read.
Skip a proposalautopilot:write.
Approve a proposalautopilot:write and broadcasts:write, because approving schedules campaigns.
Read the playbookautopilot:read.
Read the campaign kindsautopilot:read.
Put a kind on Auto, or take it offautopilot:write, and an admin, as for the settings: a kind on Auto schedules campaigns with nobody's yes.
Cancel a scheduled proposalautopilot:write and broadcasts:write, because it cancels campaigns.
Use, dismiss, restore or reset the playbookautopilot:write, and an admin, as for the settings: it changes what Autopilot proposes and how it writes.
Change the settingsautopilot:write, and an admin: a team admin, or a Full access key an admin created. Anyone else is 403 invalid_permission. An OAuth token cannot change them.

Nothing on these routes calls an AI model. They read and decide what is already there.

Lifecycle

drafting → proposed → scheduled → sent, or skipped, expired or failed. Only a proposed proposal can be approved or skipped.

Proposal statuses
StatusMeans
draftingAutopilot is writing the campaigns. Nothing to decide yet.
proposedWaiting for a person. The only status you can approve or skip.
approvedThe moment between a yes and the schedule. An approval, a person's or Auto's, passes straight through it.
scheduledApproved, by a person or, with auto: true, by itself. Its campaigns are scheduled at the proposal's send time.
sentIts campaigns went out.
skippedSomeone said no, or every scheduled campaign was canceled.
expiredNobody decided before its send time, or before its expires_at. Its drafts are deleted.
failedIts campaigns could not be sent.

The brief's facts — product names, prices, stock, links, pictures — come from your store's catalogue, and the audience size, recipient_estimate, is counted by Rasket. None of them comes from the AI.

The drafts are broadcasts

  • email_broadcast_id and sms_broadcast_id are ordinary draft campaigns. Change one with PATCH /broadcasts/{id} before you approve, and the edit is what goes out.
  • Approving runs each draft through the gate and content checks of POST /broadcasts/{id}/send, together. If either fails, nothing is scheduled: the answer is 422 with the failing checks in details.checks, and acknowledge_checks: true schedules anyway.
  • Sending a proposal's draft yourself with POST /broadcasts/{id}/send counts as approving the proposal.
  • Canceling every scheduled campaign of an approved proposal ends it as skipped, with skip_reason canceled.
  • A proposal nobody decides before its send time, or its expires_at, becomes expired and its drafts are deleted. Rasket checks every five minutes.

Skipping

A skip deletes the drafts and keeps your reason and note, which Autopilot learns from.

Skip reasons
ReasonMeans
not_relevantNot something your customers care about.
wrong_timingRight idea, wrong day.
wrong_audienceThe wrong people.
off_brandIt does not sound like you.
wrong_factsSomething in it is wrong.
too_frequentToo many campaigns.
otherAnything else. Say what in note.

What it learns

Every week Autopilot looks at what its sent campaigns earned and how they were clicked, at what was changed in its drafts before they were approved, and at the reasons for skips. It proposes a new version of the store's playbook: how much each kind of campaign is worth to this store, the hour campaigns do best at, and the style to write in. A version changes nothing until an admin uses it.

  • Every number in it is worked out from the store's own results. Only the style notes and phrases are written by AI, from the edits.
  • A campaign that uses one of the live version's avoid_phrases fails its checks, as a wrong price does.
  • Each proposal records the version it was planned and written with.

Earned auto-send

A kind of campaign can earn sending without asking. GET /autopilot/kinds shows each kind's bars over the last 90 days: at least 10 decided, 80% approved, 70% of approvals with few edits, unsubscribes at most 0.5%, spam complaints at most 0.08%, and sales — or clicks, for a store with no sales yet — at least half its usual per recipient. A kind that clears every bar is ready.

  • Nothing goes on Auto by itself. With approval_mode earned, an admin puts a ready kind on Auto with PATCH /autopilot/kinds/{kind}.
  • A proposal of that kind whose checks and quality score passed then schedules itself, as long as its send is at least four hours away. It is scheduled with auto: true, and the people Autopilot tells get an email with a Cancel link.
  • POST /autopilot/proposals/{id}/cancel cancels it until 30 minutes before it sends. After that, cancel the campaign itself.
  • Every hour Autopilot checks each kind on Auto again. If unsubscribes, complaints or sales slip, one campaign draws a complaint spike, or two of its campaigns are canceled, the kind goes back to asking and the owners are told why. After a complaint spike, its campaigns that have not started are canceled too.

Events

Subscribe a webhook to these to hear about proposals as they change. Their data is the proposal without its brief; the events page has a payload for each.

Autopilot events
TypeMeans
autopilot.proposal.createdA proposal is ready for a decision.
autopilot.proposal.approvedIt was approved and its campaigns are scheduled.
autopilot.proposal.skippedSomeone skipped it, or its campaigns were canceled.
autopilot.proposal.expiredNobody decided in time.
autopilot.proposal.sentIts campaigns went out.

An agent can review proposals over MCP: get_autopilot_proposal shows who a proposal reaches, when, and its drafts, and approve_autopilot_proposal runs only with the confirmation token it returned.

Endpoints

Retrieve Autopilot's settings

GET /autopilot/settings

What Autopilot may do, for this project.

Request

curl -X GET "https://api.rasket.com/autopilot/settings" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "autopilot_settings",
  "enabled": true,
  "store_id": "0199d3a0-3b10-7c22-9d41-5e2f6a7b8c91",
  "channels": ["email", "sms"],
  "kinds_enabled": ["new_arrivals", "back_in_stock", "sale", "blog_post"],
  "max_emails_per_week": 2,
  "max_sms_per_week": 1,
  "min_hours_between_per_contact": 48,
  "quiet_hours_start": "21:00",
  "quiet_hours_end": "08:00",
  "time_zone": "America/New_York",
  "default_segment_id": "0199d2c1-5a32-7b44-9c55-7d6e8f9a0b12",
  "topic_id": null,
  "approval_mode": "ask",
  "monthly_credit_cap": 200,
  "notify_member_ids": ["0199c4e2-1f7a-7b30-8e52-3d9c6a1b2f48"],
  "voice_notes": "Warm and brief. Lead with the product, never with the discount.",
  "updated_at": "2026-09-08T16:30:12.004Z"
}
  • Before an admin saves anything this is the defaults: off, email only, every kind of campaign, and updated_at: null.

Update Autopilot's settings

PATCH /autopilot/settings

Turn Autopilot on or off and set its limits. Only the fields present are touched.

Body

  • enabledboolean

    Whether Autopilot plans campaigns. Defaults to false.

  • store_idstring | null

    The store it plans for. A store that is not this project's, or is disconnected, is 422.

  • channelsstring[]

    email, sms or both: what it may write. At least one. Defaults to email.

  • kinds_enabledstring[]

    The kinds of campaign it may propose: new_arrivals, price_drop, back_in_stock, low_stock, bestsellers, seasonal, sale or blog_post. At least one. Defaults to all of them.

  • max_emails_per_weekinteger

    0–14. Defaults to 2.

  • max_sms_per_weekinteger

    0–7. Defaults to 1.

10 more fields (min_hours_between_per_contact, quiet_hours_start, quiet_hours_end, time_zone, default_segment_id, topic_id, approval_mode, monthly_credit_cap, notify_member_ids, voice_notes)
  • min_hours_between_per_contactinteger

    The fewest hours between two Autopilot campaigns to one person, 0–336. Defaults to 48.

  • quiet_hours_startstring

    "HH:MM". When quiet hours start. Defaults to 21:00.

  • quiet_hours_endstring

    "HH:MM". When they end. Defaults to 08:00.

  • time_zonestring

    An IANA time zone name. Quiet hours are in each recipient's own time zone where it is known, and in this one otherwise. Defaults to UTC.

  • default_segment_idstring | null

    A segment of this project: the audience Autopilot plans for by default.

  • topic_idstring | null

    A topic of this project for its campaigns.

  • approval_modestring

    ask: every proposal waits for a person's yes. earned: the kinds an admin put on Auto (PATCH /autopilot/kinds/{kind}) schedule their campaigns themselves; every other kind still asks.

  • monthly_credit_capinteger

    AI credits Autopilot may spend a month, 0–100000. Defaults to 200. More than the project's own monthly AI credits is 422.

  • notify_member_idsstring[]

    Up to 20 members of this project, told when proposals are ready.

  • voice_notesstring | null

    Your own notes on how your campaigns should sound, up to 500 characters.

Request

curl -X PATCH "https://api.rasket.com/autopilot/settings" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0" \
  -H "Content-Type: application/json" \
  -d '{
  "enabled": true,
  "store_id": "0199d3a0-3b10-7c22-9d41-5e2f6a7b8c91",
  "channels": ["email", "sms"],
  "time_zone": "America/New_York"
}'

Response 200

{
  "object": "autopilot_settings",
  "enabled": true,
  "store_id": "0199d3a0-3b10-7c22-9d41-5e2f6a7b8c91",
  "channels": ["email", "sms"],
  "kinds_enabled": ["new_arrivals", "back_in_stock", "sale", "blog_post"],
  "max_emails_per_week": 2,
  "max_sms_per_week": 1,
  "min_hours_between_per_contact": 48,
  "quiet_hours_start": "21:00",
  "quiet_hours_end": "08:00",
  "time_zone": "America/New_York",
  "default_segment_id": "0199d2c1-5a32-7b44-9c55-7d6e8f9a0b12",
  "topic_id": null,
  "approval_mode": "ask",
  "monthly_credit_cap": 200,
  "notify_member_ids": ["0199c4e2-1f7a-7b30-8e52-3d9c6a1b2f48"],
  "voice_notes": "Warm and brief. Lead with the product, never with the discount.",
  "updated_at": "2026-09-08T16:30:12.004Z"
}
  • Admins only: a team admin, or a Full access key an admin created. Anyone else is 403 invalid_permission, and an OAuth token cannot change the settings.
  • An empty body is 422. A store, segment, topic or member that is not this project's is 422 invalid_parameter.
  • The response is the whole settings object, as saved.

List proposals

GET /autopilot/proposals

The campaigns Autopilot suggested, newest first.

Query parameters

  • statusstring

    Only proposals in this status: drafting, proposed, approved, scheduled, sent, skipped, expired or failed.

  • kindstring

    Only proposals of this kind: new_arrivals, price_drop, back_in_stock, low_stock, bestsellers, seasonal, sale or blog_post.

  • limitinteger

    How many items to return, 1–100. Defaults to 20.

  • afterstring

    Return the page that follows this item ID. Mutually exclusive with before.

  • beforestring

    Return the page that precedes this item ID. Mutually exclusive with after.

Request

curl -X GET "https://api.rasket.com/autopilot/proposals?status=proposed" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "object": "autopilot_proposal",
      "id": "0199d2f1-4c4e-7a20-9c31-6f2b8a0e5d17",
      "kind": "new_arrivals",
      "status": "proposed",
      "audience": {
        "segment_id": "0199d2c1-5a32-7b44-9c55-7d6e8f9a0b12",
        "description": "Newsletter subscribers who bought in the last year",
        "recipient_estimate": 4210
      },
      "channels": ["email", "sms"],
      "send_at": "2026-09-11T15:00:00.000Z",
      "email_broadcast_id": "0199d3b1-7b20-7c31-8d42-6e5f7a8b9c01",
      "sms_broadcast_id": "0199d3b1-7b20-7c31-8d42-6e5f7a8b9c02",
      "created_at": "2026-09-09T08:12:44.512Z"
    }
  ]
}
  • Each item is the whole proposal, brief included; the example is cut short. status=proposed is the ones waiting for a decision.
  • The list is empty until Autopilot has proposed something.

Retrieve a proposal

GET /autopilot/proposals/{id}

One proposal, with its brief, audience and draft campaigns.

Path parameters

  • idstringRequired

    The proposal's ID.

Request

curl -X GET "https://api.rasket.com/autopilot/proposals/0199d2f1-4c4e-7a20-9c31-6f2b8a0e5d17" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "autopilot_proposal",
  "id": "0199d2f1-4c4e-7a20-9c31-6f2b8a0e5d17",
  "kind": "new_arrivals",
  "status": "proposed",
  "store_id": "0199d3a0-3b10-7c22-9d41-5e2f6a7b8c91",
  "signal_ids": ["0199d3b0-2d10-7a44-8c21-4b3a2c1d0e9f"],
  "brief": {
    "kind": "new_arrivals",
    "facts": {
      "products": [
        {
          "id": "8123456789",
          "title": "Linen overshirt",
          "url": "https://shop.acme.example/products/linen-overshirt",
          "image_url": "https://shop.acme.example/images/linen-overshirt.jpg",
          "price": "68.00",
          "previous_price": null,
          "currency": "USD",
          "stock": 42
        }
      ],
      "collection": {
        "id": "4012345678",
        "title": "Autumn arrivals",
        "url": "https://shop.acme.example/collections/autumn"
      },
      "sale": null,
      "blog_post": null,
      "holiday": null
    },
    "angle": "The autumn collection landed this week, led by the linen overshirt.",
    "audience_description": "Newsletter subscribers who bought in the last year",
    "channels": ["email", "sms"],
    "offer": {
      "type": "none"
    },
    "send_at": "2026-09-11T15:00:00.000Z",
    "must_say": [],
    "must_not_say": []
  },
  "audience": {
    "segment_id": "0199d2c1-5a32-7b44-9c55-7d6e8f9a0b12",
    "description": "Newsletter subscribers who bought in the last year",
    "recipient_estimate": 4210
  },
  "channels": ["email", "sms"],
  "send_at": "2026-09-11T15:00:00.000Z",
  "expires_at": null,
  "email_broadcast_id": "0199d3b1-7b20-7c31-8d42-6e5f7a8b9c01",
  "sms_broadcast_id": "0199d3b1-7b20-7c31-8d42-6e5f7a8b9c02",
  "expected": {
    "low": 640,
    "high": 1180,
    "currency": "USD",
    "basis": "Your last three new-arrival campaigns earned $590–$1,240 each."
  },
  "checks": [
    {
      "name": "prices_match_store",
      "passed": true,
      "message": null
    },
    {
      "name": "links_live",
      "passed": true,
      "message": null
    }
  ],
  "judge_score": 8.6,
  "skip_reason": null,
  "skip_note": null,
  "decided_at": null,
  "auto": false,
  "sent_at": null,
  "created_at": "2026-09-09T08:12:44.512Z",
  "updated_at": "2026-09-09T08:13:02.877Z"
}
  • Every fact in brief.facts — product names, prices, stock, links, pictures — comes from your store's catalogue, and audience.recipient_estimate is counted by Rasket. None of them comes from the AI.
  • email_broadcast_id and sms_broadcast_id are ordinary draft broadcasts: read or edit them at /broadcasts/{id}. Either is null when the proposal has no campaign on that channel.
  • expected is what the campaign is expected to earn, from your store's own history, with basis saying how; null when there is no history to go on.
  • Another project's proposal is 404.

Approve a proposal

POST /autopilot/proposals/{id}/approve

Schedule its draft campaigns, through the same checks as a send.

Path parameters

  • idstringRequired

    The proposal's ID.

Body

  • send_atstring

    A new send time: ISO 8601 or a phrase such as "tomorrow at 9am", read as UTC. Between one minute and thirty days out. Omit it — or send no body at all — to keep the proposal's own send_at.

  • acknowledge_checksboolean

    Schedule even though a content check fails. Without it, a failing check is 422 with the failing checks in details.checks.

Request

curl -X POST "https://api.rasket.com/autopilot/proposals/0199d2f1-4c4e-7a20-9c31-6f2b8a0e5d17/approve" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0" \
  -H "Content-Type: application/json" \
  -d '{
  "send_at": "2026-09-12T15:00:00Z"
}'

Response 200

{
  "object": "autopilot_proposal",
  "id": "0199d2f1-4c4e-7a20-9c31-6f2b8a0e5d17",
  "status": "scheduled",
  "send_at": "2026-09-12T15:00:00.000Z",
  "decided_at": "2026-09-09T09:40:11.204Z"
}
  • Needs broadcasts:write as well as autopilot:write: approving schedules campaigns.
  • Each draft goes through the compliance gate and content checks of POST /broadcasts/{id}/send, in one transaction. If either draft fails, the approval is refused — 422, the message saying which campaign, the failing checks in details.checks — and nothing changes. A text refused by content policy cannot be acknowledged.
  • Once the proposal's own send time has passed, send_at is required: without it the call is 409 resource_locked, asking you to pick a new time.
  • Approving a proposal that is already approved or scheduled returns it unchanged. One that was skipped, expired, sent or failed is 409 resource_locked, and so is one whose drafts were all deleted.
  • The response is the whole proposal, now scheduled; the example is cut short.

Skip a proposal

POST /autopilot/proposals/{id}/skip

Say no. Its drafts are deleted and the reason is kept.

Path parameters

  • idstringRequired

    The proposal's ID.

Body

  • reasonstringRequired

    not_relevant, wrong_timing, wrong_audience, off_brand, wrong_facts, too_frequent or other. Autopilot learns from it.

  • notestring

    Anything else, up to 500 characters.

Request

curl -X POST "https://api.rasket.com/autopilot/proposals/0199d2f1-4c4e-7a20-9c31-6f2b8a0e5d17/skip" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0" \
  -H "Content-Type: application/json" \
  -d '{
  "reason": "wrong_timing",
  "note": "We have a sale going out that day."
}'

Response 200

{
  "object": "autopilot_proposal",
  "id": "0199d2f1-4c4e-7a20-9c31-6f2b8a0e5d17",
  "status": "skipped",
  "email_broadcast_id": "0199d3b1-7b20-7c31-8d42-6e5f7a8b9c01",
  "sms_broadcast_id": "0199d3b1-7b20-7c31-8d42-6e5f7a8b9c02",
  "skip_reason": "wrong_timing",
  "skip_note": "We have a sale going out that day.",
  "decided_at": "2026-09-09T09:41:30.090Z"
}
  • Only a proposed proposal can be skipped. Skipping one already decided — a second skip included — is 409 resource_locked.
  • The draft broadcasts are deleted, so their IDs answer 404 from then on.
  • skip_reason can also read canceled, which Rasket writes itself when every campaign of an approved proposal is canceled. You cannot send it.

Reschedule a proposal

POST /autopilot/proposals/{id}/reschedule

Move its send time before anyone decides.

Path parameters

  • idstringRequired

    The proposal's ID.

Body

  • send_atstringRequired

    The new send time: ISO 8601 or a phrase such as "tomorrow at 9am", read as UTC. Between one minute and thirty days out.

Request

curl -X POST "https://api.rasket.com/autopilot/proposals/0199d2f1-4c4e-7a20-9c31-6f2b8a0e5d17/reschedule" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0" \
  -H "Content-Type: application/json" \
  -d '{
  "send_at": "2026-09-13T09:00:00Z"
}'

Response 200

{
  "object": "autopilot_proposal",
  "id": "0199d2f1-4c4e-7a20-9c31-6f2b8a0e5d17",
  "status": "proposed",
  "send_at": "2026-09-13T09:00:00.000Z"
}
  • Only a proposal waiting for a decision can be rescheduled; one already approved, skipped, expired, sent or failed is 409 resource_locked. To move an approved campaign, reschedule its broadcast.
  • Nothing is scheduled yet: approving afterwards schedules the campaigns at the new time.
  • The response is the whole proposal; the example is cut short.

Cancel a proposal

POST /autopilot/proposals/{id}/cancel

Stop a scheduled proposal's campaigns before they send.

Path parameters

  • idstringRequired

    The proposal's ID.

Request

curl -X POST "https://api.rasket.com/autopilot/proposals/0199d2f1-4c4e-7a20-9c31-6f2b8a0e5d17/cancel" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "autopilot_proposal",
  "id": "0199d2f1-4c4e-7a20-9c31-6f2b8a0e5d17",
  "status": "skipped",
  "skip_reason": "canceled",
  "auto": true
}
  • For a scheduled proposal, whether it scheduled itself on Auto (auto: true) or a person approved it. Its campaigns are canceled and it ends skipped with the reason canceled, which counts against the kind's readiness.
  • Allowed until 30 minutes before send_at; after that it is 409 resource_locked, and the campaign itself can still be stopped with POST /broadcasts/{id}/cancel.
  • Needs broadcasts:write as well as autopilot:write. The response is the whole proposal; the example is cut short.

List campaign kinds

GET /autopilot/kinds

Whether each kind of campaign has earned sending on its own.

Request

curl -X GET "https://api.rasket.com/autopilot/kinds" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "object": "autopilot_kind",
      "kind": "price_drop",
      "enabled": true,
      "auto": false,
      "paused": false,
      "state": "ready",
      "bars": [
        {
          "name": "decided",
          "value": 12,
          "limit": 10,
          "pass": true
        },
        {
          "name": "measured",
          "value": 5,
          "limit": 3,
          "pass": true
        },
        {
          "name": "approved",
          "value": 0.91667,
          "limit": 0.8,
          "pass": true
        },
        {
          "name": "light_edits",
          "value": 0.81818,
          "limit": 0.7,
          "pass": true
        },
        {
          "name": "unsubscribes",
          "value": 0.002,
          "limit": 0.005,
          "pass": true
        },
        {
          "name": "complaints",
          "value": 0.0002,
          "limit": 0.0008,
          "pass": true
        },
        {
          "name": "revenue",
          "value": 1.18,
          "limit": 0.5,
          "pass": true
        }
      ],
      "decided": 12,
      "measured": 5,
      "auto_since": null,
      "last_change": null
    }
  ]
}
  • All eight kinds, each with its state — auto, fell_back, not_enough_data, not_ready or ready — and the bars it is measured against over the last 90 days: at least 10 decided, 80% approved, 70% of approvals with few edits, unsubscribes at most 0.5%, spam complaints at most 0.08%, and sales (or clicks, for a store with no sales yet) at least half its usual per recipient.
  • A kind on Auto is taken off by itself when its results slip, a single campaign draws a complaint spike, or two of its campaigns are canceled; last_change says why. It cannot be Ready again for 7 days.

Put a kind on Auto

PATCH /autopilot/kinds/{kind}

Let a Ready kind schedule its campaigns itself, or take it off.

Path parameters

  • kindstringRequired

    The kind of campaign: new_arrivals, price_drop, back_in_stock, low_stock, bestsellers, seasonal, sale or blog_post.

Body

  • autobooleanRequired

    true puts the kind on Auto; false takes it off.

Request

curl -X PATCH "https://api.rasket.com/autopilot/kinds/{kind}" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0" \
  -H "Content-Type: application/json" \
  -d '{
  "auto": true
}'

Response 200

{
  "object": "autopilot_kind",
  "kind": "price_drop",
  "enabled": true,
  "auto": true,
  "paused": false,
  "state": "auto",
  "bars": [
    {
      "name": "decided",
      "value": 12,
      "limit": 10,
      "pass": true
    },
    {
      "name": "measured",
      "value": 5,
      "limit": 3,
      "pass": true
    },
    {
      "name": "approved",
      "value": 0.91667,
      "limit": 0.8,
      "pass": true
    },
    {
      "name": "light_edits",
      "value": 0.81818,
      "limit": 0.7,
      "pass": true
    },
    {
      "name": "unsubscribes",
      "value": 0.002,
      "limit": 0.005,
      "pass": true
    },
    {
      "name": "complaints",
      "value": 0.0002,
      "limit": 0.0008,
      "pass": true
    },
    {
      "name": "revenue",
      "value": 1.18,
      "limit": 0.5,
      "pass": true
    }
  ],
  "decided": 12,
  "measured": 5,
  "auto_since": "2026-09-12T10:00:00.000Z",
  "last_change": null
}
  • A team admin's. auto: true is 422 unless the kind is ready (the failing bars are in details.bars), enabled in kinds_enabled, and approval_mode is earned.
  • On Auto, a proposal of the kind whose checks and quality score passed schedules itself when its send is at least four hours away, and the people Autopilot tells get an email with a Cancel link.

Retrieve the playbook

GET /autopilot/playbook

What Autopilot learned from this store's results and edits.

Request

curl -X GET "https://api.rasket.com/autopilot/playbook" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "autopilot_playbook",
  "live": null,
  "proposed": {
    "object": "autopilot_playbook_version",
    "id": "0199e1a4-2c30-7d41-8e52-6f7a8b9c0d13",
    "version": 3,
    "status": "proposed",
    "source": "learned",
    "store_id": "0199d3a0-3b10-7c22-9d41-5e2f6a7b8c91",
    "kind_weights": {
      "price_drop": 1.7,
      "bestsellers": 0.83,
      "new_arrivals": 0.96
    },
    "best_hours": {
      "time_zone": "America/New_York",
      "email": [10],
      "sms": []
    },
    "style_notes": ["Lead with the product's material, not with a greeting."],
    "avoid_phrases": ["don't miss out"],
    "prefer_phrases": ["made to last"],
    "suggested_caps": null,
    "evidence": {
      "currency": "USD",
      "channel": "email",
      "baseline_revenue_per_recipient": 0.2,
      "baseline_click_rate": 0.05,
      "kinds": [
        {
          "kind": "price_drop",
          "campaigns": 3,
          "recipients": 300,
          "revenue_per_recipient": 0.6,
          "click_rate": 0.05,
          "skips": 0,
          "decided": 3,
          "performance": 1.7,
          "weight": 1.7
        }
      ],
      "hours": {
        "email": [
          {
            "hour": 10,
            "sends": 6,
            "click_rate": 0.05
          }
        ],
        "sms": []
      },
      "edits": {
        "total": 6,
        "changed": 4,
        "heavy": 1
      },
      "too_frequent_skips": 0
    },
    "based_on": {
      "outcomes": 9,
      "edits": 6,
      "skips": 1,
      "since": "2026-07-01T05:24:00.000Z",
      "until": "2026-09-29T05:24:00.000Z"
    },
    "restored_from_id": null,
    "decided_at": null,
    "created_at": "2026-09-29T05:24:03.117Z"
  },
  "history": [
    {
      "object": "autopilot_playbook_version",
      "id": "0199e1a4-2c30-7d41-8e52-6f7a8b9c0d13",
      "version": 3,
      "status": "proposed",
      "source": "learned",
      "store_id": "0199d3a0-3b10-7c22-9d41-5e2f6a7b8c91",
      "kind_weights": {
        "price_drop": 1.7,
        "bestsellers": 0.83,
        "new_arrivals": 0.96
      },
      "best_hours": {
        "time_zone": "America/New_York",
        "email": [10],
        "sms": []
      },
      "style_notes": ["Lead with the product's material, not with a greeting."],
      "avoid_phrases": ["don't miss out"],
      "prefer_phrases": ["made to last"],
      "suggested_caps": null,
      "evidence": {
        "currency": "USD",
        "channel": "email",
        "baseline_revenue_per_recipient": 0.2,
        "baseline_click_rate": 0.05,
        "kinds": [
          {
            "kind": "price_drop",
            "campaigns": 3,
            "recipients": 300,
            "revenue_per_recipient": 0.6,
            "click_rate": 0.05,
            "skips": 0,
            "decided": 3,
            "performance": 1.7,
            "weight": 1.7
          }
        ],
        "hours": {
          "email": [
            {
              "hour": 10,
              "sends": 6,
              "click_rate": 0.05
            }
          ],
          "sms": []
        },
        "edits": {
          "total": 6,
          "changed": 4,
          "heavy": 1
        },
        "too_frequent_skips": 0
      },
      "based_on": {
        "outcomes": 9,
        "edits": 6,
        "skips": 1,
        "since": "2026-07-01T05:24:00.000Z",
        "until": "2026-09-29T05:24:00.000Z"
      },
      "restored_from_id": null,
      "decided_at": null,
      "created_at": "2026-09-29T05:24:03.117Z"
    }
  ]
}
  • live is the version Autopilot plans and writes with; proposed is the one waiting for an admin. Each is null until there is one, and history lists the ten newest versions.
  • kind_weights multiply the planner's own weight for each kind, between 0.25 and 2.0. Every number — weights, hours, the evidence — is worked out from the store's own campaign results; only style_notes and the phrases are written by AI, from the edits made before approving.
  • A campaign that uses one of the live version's avoid_phrases fails its checks and is rewritten or dropped.

Use a proposed version

POST /autopilot/playbook/versions/{id}/use

Make the waiting version the one Autopilot follows.

Path parameters

  • idstringRequired

    The playbook version's ID.

Request

curl -X POST "https://api.rasket.com/autopilot/playbook/versions/0199d2f1-4c4e-7a20-9c31-6f2b8a0e5d17/use" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "autopilot_playbook",
  "live": {
    "object": "autopilot_playbook_version",
    "id": "0199e1a4-2c30-7d41-8e52-6f7a8b9c0d13",
    "version": 3,
    "status": "live"
  },
  "proposed": null
}
  • A team admin's, or a Full access key an admin created; anyone else is 403 invalid_permission.
  • The version that was live is archived. Only the waiting version can be used: once a newer one replaced it, the call is 409 resource_locked.
  • The response is the whole playbook; the example is cut short.

Dismiss a proposed version

POST /autopilot/playbook/versions/{id}/dismiss

Say no to the waiting version; what is live stays.

Path parameters

  • idstringRequired

    The playbook version's ID.

Request

curl -X POST "https://api.rasket.com/autopilot/playbook/versions/0199d2f1-4c4e-7a20-9c31-6f2b8a0e5d17/dismiss" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "autopilot_playbook",
  "live": null,
  "proposed": null
}
  • A team admin's. The version is kept as rejected, and Autopilot does not propose the same again until at least three more campaigns' results are in.
  • Only the waiting version can be dismissed: 409 resource_locked otherwise.

Restore a version

POST /autopilot/playbook/versions/{id}/restore

Go back to an earlier version.

Path parameters

  • idstringRequired

    The playbook version's ID.

Request

curl -X POST "https://api.rasket.com/autopilot/playbook/versions/0199d2f1-4c4e-7a20-9c31-6f2b8a0e5d17/restore" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "autopilot_playbook",
  "live": {
    "object": "autopilot_playbook_version",
    "version": 4,
    "status": "live",
    "source": "restored",
    "restored_from_id": "0199d9f0-1b20-7c31-8d42-5e6f7a8b9c02"
  }
}
  • A team admin's. A copy of the version becomes the live one, as the next version number, with source: "restored"; the version live before it is archived. A version waiting for a decision stays waiting.
  • Restoring the version that is already live is 409 resource_locked; an unknown ID is 404.

Reset to the defaults

POST /autopilot/playbook/reset

Stop following any learned version.

Request

curl -X POST "https://api.rasket.com/autopilot/playbook/reset" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "autopilot_playbook",
  "live": null
}
  • A team admin's. The live version is archived, so Autopilot plans and writes with its defaults until a version is used again. With nothing live it changes nothing.

List feeds

GET /autopilot/sources

The blog and RSS feeds Autopilot reads for its store.

Request

curl -X GET "https://api.rasket.com/autopilot/sources" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "list",
  "store_id": "0199d3a0-3b10-7c22-9d41-5e2f6a7b8c91",
  "shopify": false,
  "needs_blog_permission": false,
  "shopify_apps_url": null,
  "data": [
    {
      "object": "autopilot_source",
      "id": "0199d3c4-2a10-7d22-8e41-6f2a7b8c9d03",
      "kind": "rss",
      "url": "https://journal.acme.example/feed",
      "title": "The Acme Journal",
      "status": "active",
      "last_error": null,
      "last_fetched_at": "2026-09-10T07:23:04.118Z",
      "latest": {
        "title": "How we dye linen",
        "url": "https://journal.acme.example/how-we-dye-linen",
        "published_at": "2026-09-09T12:00:00.000Z"
      }
    }
  ]
}
  • The feeds belong to the store Autopilot watches (store_id in the settings). A new post on one becomes a blog_post proposal.
  • A Shopify store's own blog is read without being added, once the store has approved the app's read_content permission; until then needs_blog_permission is true and shopify_apps_url opens Rasket in the store's admin, where Shopify asks for it. Both are false and null for any other store.

Add a feed

POST /autopilot/sources

Read an RSS or Atom feed once and watch it for new posts.

Body

  • urlstringRequired

    The feed's https:// address, such as https://journal.example.com/feed. A web page is refused: feeds usually end in /feed, /rss or .xml.

Request

curl -X POST "https://api.rasket.com/autopilot/sources" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://journal.acme.example/feed"
}'

Response 201

{
  "object": "autopilot_source",
  "id": "0199d3c4-2a10-7d22-8e41-6f2a7b8c9d03",
  "kind": "rss",
  "url": "https://journal.acme.example/feed",
  "title": "The Acme Journal",
  "status": "active",
  "last_error": null,
  "last_fetched_at": "2026-09-10T07:23:04.118Z",
  "latest": {
    "title": "How we dye linen",
    "url": "https://journal.acme.example/how-we-dye-linen",
    "published_at": "2026-09-09T12:00:00.000Z"
  }
}
  • Rasket reads the feed before adding it: https only, public addresses only, at most three redirects, 1 MB and 10 seconds. What it shows then is the baseline, so only posts published afterwards become proposals.
  • A team admin's, or a Full access key an admin made. At most five feeds per store (422), and twenty checks an hour per project (429 rate_limited).
  • A feed that cannot be read is 422 with the reason in message.

Remove a feed

DELETE /autopilot/sources/{id}

Stop reading a feed.

Path parameters

  • idstringRequired

    The feed's ID.

Request

curl -X DELETE "https://api.rasket.com/autopilot/sources/0199d2f1-4c4e-7a20-9c31-6f2b8a0e5d17" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "autopilot_source",
  "id": "0199d3c4-2a10-7d22-8e41-6f2a7b8c9d03",
  "deleted": true
}
  • Its posts not yet proposed are no longer news. The Shopify blog is not a feed you can remove: switch off the blog_post kind instead.