Eden (Creator Platform) API reference
Every store API endpoint, with sign-in by API key or OAuth, for developers and Zapier's review team.
Every endpoint of Eden's store API. Your own code can call them with an API key from Settings → API, and the Eden (Creator Platform) app on Zapier calls the same ones. You don't need this page to use the Zapier app.
Version 1.0 of the app has the sign-in, the products list, and Record a sale. Everything else on this page belongs to version 1.1.
The whole store API is also in one OpenAPI file you can load into Postman or a code generator: https://api.eden.so/store-app/v1/openapi.json.
Sign in with an API key
For your own code, a checkout, or a script.
- In Eden, go to Settings → API and click Generate key with Full access picked, or Custom with Store on Read or Edit.
- Send it on every call as
Authorization: Bearer <key>.
A key works on one store. If you run more than one, pick it when you make the key; otherwise it uses your main store. You need edit access to that store. A key with Store on Read (or a Read-only key) can make GET calls only. A key with Store off can't use the store API. Store calls need Starter or above. See Use the Eden API for a first call.
Sign in with OAuth
For apps other people connect, like the Zapier app. Eden uses OAuth 2.0 with PKCE.
- Scope: 1.0 asks for
store, which can only call/store-app/v1/*. 1.1 asks forread write, the same access as Eden's MCP connectors, for the workspace steps. - Authorize:
GET https://oauth.eden.so/oauth/authorizewithresponse_type=code,client_id,redirect_uri,state,scope,code_challenge, andcode_challenge_method=S256. - Token:
POST https://oauth.eden.so/oauth/token, form encoded. Sendgrant_type=authorization_codewithcode,redirect_uri,client_id,client_secret, andcode_verifier. - Refresh: the same token URL with
grant_type=refresh_tokenandrefresh_token.
Send the token on every call as Authorization: Bearer <access_token>.
Who is signed in
GET https://api.eden.so/me (the connection test, any Eden account) returns the user and their workspaces. GET https://api.eden.so/store-app/v1/me returns the store owner:
{
"ok": true,
"id": "user_123",
"email": "[email protected]",
"handle": "yourname",
"store": { "handle": "yourname", "name": "Your Store", "workspace_id": "ws_123" }
}
store is the store this sign-in reaches.
The seller's products
GET https://api.eden.so/store-app/v1/products
Newest first, own-checkout products on top. Archived products are left out.
{
"ok": true,
"products": [
{
"id": "prod_123",
"name": "My Course (monthly)",
"title": "My Course",
"billing": "monthly",
"live": true,
"ownCheckout": true
}
]
}
Full lists: sales, customers, email contacts, products
For syncing your store into a sheet, a CRM, or your own app. The Zapier app doesn't use these. Each list is newest first and comes in pages.
| Route | Filters | What each row is |
|---|---|---|
GET /store-app/v1/sales | product, email, status (paid, refunded, or free) | One sale line, from Eden checkout or your own checkout |
GET /store-app/v1/customers | product, has_access (true or false), tag (a tag name or id) | One person, with every product they have in products |
GET /store-app/v1/email-contacts | status (subscribed, unsubscribed, bounced, complained), tag | One person on your email list |
GET /store-app/v1/products | status (draft, live, or off) | One product, drafts included. Send any paging field or status to get this list. |
Every list also takes:
limit: 1 to 100 rows a page. Defaults to 25.starting_after: thenext_cursorfrom the page before, for the next page.created_afterandcreated_before: an ISO date or milliseconds.
{
"ok": true,
"data": [{ "id": "purchase:abc123", "status": "paid", "buyer_email": "[email protected]" }],
"has_more": true,
"next_cursor": "WzE3NTk2MDAwMDAwMDAsInB1cmNoYXNlOmFiYzEyMyJd"
}
Keep asking while has_more is true. A page filtered by tag can come back short, or even empty, with has_more still true.
Sales rows look like the New Sale feed below, plus status. Refunds from your own checkout don't change its sale; they show up in New Refund.
To get one row:
GET /store-app/v1/sales/<sale id>, using anidfrom the list.GET /store-app/v1/customers/<email>.GET /store-app/v1/email-contacts/<email>.
Make and edit products
Make a draft, change it, and put it live, with the same rules as the store editor. Build what's inside (the Custom AI, lessons, files) in Eden first. Covers and files can't be uploaded through the API.
| Route | What it does |
|---|---|
GET /store-app/v1/custom-ais | The Custom AIs and courses this store can sell, with product_id once one has a product |
POST /store-app/v1/products | Make a draft: custom_ai_id plus any fields below. Answers 201. |
GET /store-app/v1/products/<id> | One product, every field |
PATCH /store-app/v1/products/<id> | Change fields. Leave one out to keep it, send null to clear it. |
POST /store-app/v1/products/<id>/publish | Put it live. People who already bought it get the new version. |
POST /store-app/v1/products/<id>/unpublish | Stop sales. Buyers keep what they have. |
| Field | What it is |
|---|---|
title, slug | The name and the end of its web address |
tagline, headline, subheadline | The short line under the name, and the big lines at the top of the page |
price_cents | The price in cents of your store's currency. 0 is free. |
billing | one-time, monthly, or yearly |
yearly_price_cents | With monthly, a yearly price buyers can pick instead |
compare_at_cents | A crossed-out "was" price |
trial_days | Free days before a membership's first charge |
payment_plan | { "count": 3, "amount_cents": 1700 }: pay a one-time price in monthly parts |
quantity_limit, quantity_show_left, quantity_waitlist | Sell a set number, show how many are left, and take a waitlist once sold out |
waitlist_mode, waitlist_opens_at | Take waitlist emails instead of selling, with an optional YYYY-MM-DD open day |
included_credits | AI credits in the price: 0, 100, 250, 500, or 1000 |
chat_enabled | false turns the AI off, making it a course or a download |
checkout_mode, checkout_url | eden, or external with your own checkout link |
body_markdown, faq_markdown, creator_bio, refund_policy | Page copy |
welcome_markdown | The Start here page buyers see after they buy |
hero_image_url, video_url, theme, accent, cta_label, sign_off | The look and the buy button |
whats_included | Up to 12 lines for the offer card. null brings back Eden's own. |
sections | Show or hide parts of the page, like { "faq": false }, and their order |
Every answer has product, can_publish, and publish_checklist, which lists what's left before it can go live.
Publishing has the same checks as the editor: a paid product needs Stripe connected or your own checkout link, and every product needs your support email. A product with waitlist_mode on can go live without Stripe. Setting it to false opens sales, and a paid product needs Stripe then. Answers also carry waitlist_waiting, how many people are on the waitlist. Products need an API key; the Zapier connection can't change them.
Record a sale, renewal, cancel, end, or refund
POST https://api.eden.so/store-app/v1/sales with a JSON body.
| Field | Required | What it is |
|---|---|---|
event | Yes | sale, renewal, cancel, end, or refund |
product | Yes | A product id from the products list |
email | Yes | The email the buyer paid with |
name | No | The buyer's name |
order_id | No | The checkout's order or payment ID. The same ID twice counts once. |
subscription_id | No | The checkout's subscription ID, for memberships |
paid_until | No | When the paid time ends (ISO date or milliseconds) |
ends_at | No | When a cancelled membership's access ends |
What each event does:
- sale and renewal give the buyer access, or extend it on a membership, and send the access email on a first sale.
- cancel keeps access until the paid time runs out. Memberships only.
- end stops access now. Memberships only.
- refund takes access away for that order.
A good answer:
{ "ok": true, "status": "granted" }
status is one of granted, restored, renewed, cancelled, revoked, or duplicate.
When Eden turns a sale away, it says why:
{
"ok": false,
"status": "rejected",
"reason": "product_not_found",
"message": "That product isn't in this Eden store. Pick it again in the Eden step of your Zap."
}
New sales (the New Sale trigger)
GET https://api.eden.so/store-app/v1/sales/recent, optionally ?product=<product id>
Up to 50 paid sales from the last 30 days, newest first, from Eden checkout and the seller's own checkout. Free claims, refunds, and test sales are left out. Zapier keeps the ids it hasn't seen.
utm_*, fbclid, and referrer come from the link an Eden checkout buyer used. They are null when the link had none, and always null for own-checkout sales.
{
"ok": true,
"sales": [
{
"id": "purchase:abc123",
"product_id": "prod_123",
"product_title": "My Course",
"buyer_email": "[email protected]",
"buyer_name": "Sam Buyer",
"amount": 49,
"amount_cents": 4900,
"currency": "usd",
"billing": "one-time",
"source": "eden_checkout",
"order_id": "order_123",
"discount_code": null,
"utm_source": "facebook",
"utm_medium": "paid_social",
"utm_campaign": "spring-launch",
"utm_content": null,
"utm_term": null,
"fbclid": "IwAR0abc123",
"referrer": "facebook.com",
"created_at": "2026-09-27T17:00:00.000Z"
}
]
}
Add a customer (the Add Customer action)
POST https://api.eden.so/store-app/v1/customers with a JSON body. Gives someone free access to a live product without counting a sale.
| Field | Required | What it is |
|---|---|---|
product | Yes | A product id from the products list |
email | Yes | The customer's email |
name | No | The customer's name |
send_email | No | false adds them without the access email. Defaults to true. |
Answers "status": "added" or "already_has_access". It never turns back on access the seller switched off ("reason": "access_off").
More triggers
Each works like New Sale: up to 50 events from the last 30 days, newest first. Most take ?product=<product id>.
| Trigger | Route | List key |
|---|---|---|
| New Customer | GET /store-app/v1/customers/recent | customers |
| New Membership Payment | GET /store-app/v1/memberships/payments | payments |
| Membership Cancelled or Ended | GET /store-app/v1/memberships/changes | changes |
| New Refund | GET /store-app/v1/refunds/recent | refunds |
| New Email Signup | GET /store-app/v1/email-signups/recent | signups |
| New Review | GET /store-app/v1/reviews/recent | reviews |
| New Cohort Signup | GET /store-app/v1/cohort-signups/recent | signups |
| Form Completed | GET /store-app/v1/quiz-responses/recent | responses |
| New Booking | GET /store-app/v1/calls/booked | calls |
| Booking Cancelled | GET /store-app/v1/calls/canceled | calls |
| Booking Rescheduled | GET /store-app/v1/calls/moved | calls |
| New Coaching Application | GET /store-app/v1/coaching-applications/recent | applications |
| New Affiliate | GET /store-app/v1/affiliates/recent | affiliates |
| New Affiliate Application | GET /store-app/v1/affiliates/applications | applications |
| New Affiliate Sale | GET /store-app/v1/affiliates/sales | sales |
| New Testimonial | GET /store-app/v1/testimonials/recent | testimonials |
| Lesson Completed | GET /store-app/v1/lessons/completed | completions |
| New Lesson Question | GET /store-app/v1/lesson-questions/recent | questions |
| New Paid Question | GET /store-app/v1/paid-questions/recent | questions |
- New Customer covers sales, free claims, own-checkout sales, imports, and people added by hand.
howisbought,free,own_checkout, oradded. - New Membership Payment covers the first payment and every renewal.
paymentisfirstorrenewal. - Membership Cancelled or Ended has
changeset tocancelled(access runs untilaccess_ends_at) orended(access is over). A cancel and the later end are two events. - New Email Signup fires once per email address. It has no product filter.
- New Review leaves out imported testimonials.
reviewer_emailis empty when Eden can't match the review to a customer. - Form Completed takes
?quiz=<form id>instead of a product.marketing_consentsays whether the person ticked the email box. - Bookings take
?call_type=<call type id>instead of a product;GET /store-app/v1/call-typeslists them. A booking waiting on payment isn't listed until it's paid. Each move is its own Booking Rescheduled event, withprevious_starts_at. - New Affiliate Sale has one event per sale or renewal, with the affiliate's
commission. Refunded sales drop out. - New Testimonial covers testimonials sent through your testimonial link, not imported ones.
- Lesson Completed has
finished_courseset to true on the lesson that finishes the course. - New Paid Question covers questions buyers send through an Ask me product.
answer_due_atis when your promised reply time runs out, andanswer_urlopens your Paid questions inbox.
Accept or decline applications
For an application form set to review each one. Form ids come from GET /store-app/v1/quizzes.
| Route | What it does |
|---|---|
GET /store-app/v1/forms/<form id>/applications | The people who applied. status: pending (default), accepted, declined, or all |
POST /store-app/v1/forms/<form id>/applications/accept | Accept one person |
POST /store-app/v1/forms/<form id>/applications/decline | Decline one person |
Name the person with response_id (an id from the list, or the Form Completed trigger's id) or email (their newest application on that form). The form's accept or decline email goes out and its tags are added, unless you send "send_email": false. The answer has changed (false when they were already decided that way) and email (queued, off, not_set_up, no_email, or skipped).
Instant triggers
New Sale, New Customer, New Membership Payment, Membership Cancelled, New Refund, New Email Signup, Form Completed, the three booking triggers, New Coaching Application, New Affiliate Sale, and New Paid Question can push instead of poll.
- Subscribe:
POST /store-app/v1/hookswith{ "event": "new_sale", "target_url": "<your Zapier hook>" }. Answers{ "ok": true, "id": "<hook id>" }. Events already in the feed never fire. - Unsubscribe:
DELETE /store-app/v1/hooks/<hook id>. - About every 15 seconds, Eden POSTs
{ "event": "new_sale", "ids": [...] }to the hook. Fetch those ids from the trigger's route above. target_urlmust be azapier.comaddress. A410answer deletes the hook.
Webhooks
Eden posts store events to your own addresses, signed and retried. Setup, the event list, and how to check signatures are in Get events with webhooks.
| Route | What it does |
|---|---|
GET /store-app/v1/webhook-events | Every event type, grouped |
GET /store-app/v1/webhooks | Your webhooks |
POST /store-app/v1/webhooks | Add one: url, events (types, or ["*"]), optional description. Answers the secret once. |
PATCH /store-app/v1/webhooks/<id> | Change url, events, description, or enabled |
DELETE /store-app/v1/webhooks/<id> | Delete it |
POST /store-app/v1/webhooks/<id>/roll-secret | A new secret. The old one stops working. |
POST /store-app/v1/webhooks/<id>/test | Sends a sample now, optional type. Answers delivered, response_code, response. |
GET /store-app/v1/webhooks/<id>/deliveries | Each try's result, newest first, paged |
POST /store-app/v1/deliveries/<id>/resend | Sends that event again |
GET /store-app/v1/events | Every event from the last 30 days, paged, type to filter |
GET /store-app/v1/events/<event id> | One event |
The Zapier app's instant triggers (/store-app/v1/hooks below) are separate and unchanged.
Email contacts
Each takes email, plus tags: a list of tag names or ids.
| Route | Extra fields | What it does |
|---|---|---|
POST /store-app/v1/email-contacts | name | Adds the person to your email list if new, then adds the tags. |
POST /store-app/v1/email-contacts/remove-tags | None | Removes the tags. Never adds the person. result is removed, not_tagged, or not_found. |
POST /email-contacts makes any tag name that doesn't exist yet. GET /store-app/v1/email-tags lists your tags. GET /store-app/v1/email-contacts/tagged is the Contact Tagged feed, with ?tag=<tag id> to filter.
Change or remove one person by email:
PATCH /store-app/v1/email-contacts/<email>takes any ofname,subscribed: false(unsubscribes them),add_tags, andremove_tags. Eden never turns emails back on for someone who unsubscribed, bounced, or marked spam, sosubscribed: trueanswers409for them.DELETE /store-app/v1/email-contacts/<email>takes them off your list. Someone who unsubscribed or marked spam is kept ("kept": true), so a later purchase can't email them again.
To start a sequence for someone, add the tag the sequence starts on. GET /store-app/v1/email-sequences lists your sequences, with starts_when showing each one's tags or products.
Find a customer
GET https://api.eden.so/store-app/v1/customers/find?email=<email>, optionally &product=<product id>
An empty customers list means no match. A match has has_access, products_with_access, and an access list with one line per product.
Customer actions
Each takes a JSON body with product (a product id) and email, plus the fields listed.
| Route | Extra fields | What it does |
|---|---|---|
POST /store-app/v1/customers/remove-access | None | Switches off access. An Eden checkout membership stops billing. |
POST /store-app/v1/customers/access-email | None | Sends the access email again. One per person per minute. |
POST /store-app/v1/customers/cohort | cohort (a cohort id), over_cap (boolean) | Moves an Eden checkout buyer into that cohort. |
GET /store-app/v1/products/<product id>/cohorts lists a product's cohorts for the cohort dropdown.
Create a discount code
POST https://api.eden.so/store-app/v1/discount-codes with a JSON body.
| Field | Required | What it is |
|---|---|---|
product | Yes | A product id. It must have a price and use Eden checkout. |
kind | Yes | percent or amount |
value | Yes | Percent off (1 to 100), or dollars off |
code | No | 3 to 24 letters, numbers, or dashes. Empty makes a random one. |
max_uses | No | How many times it can be used |
expires_at | No | When it stops working (ISO date or milliseconds) |
The answer includes the code and a checkout_url with the code already filled in.
Workspace steps
Same endpoints as Eden's MCP connectors. Each takes a workspace, and defaults to your main one.
| Step | Endpoint |
|---|---|
| Workspace dropdown | GET https://api.eden.so/me |
| Board dropdown | GET https://api.eden.so/workspaces/<id>/mobile/items?type=canvas |
| Custom AI dropdown | GET https://api.eden.so/custom-ai?workspaceId=<id> |
| Schedule dropdown | GET https://scheduling.eden.so/mcp/schedules?workspaceId=<id> |
| Save Link | POST https://api.eden.so/workspace-items/link, then POST /items/<id>/reference for a board |
| Create Note | POST https://api.eden.so/workspace-items/markdown |
| Schedule a Post | POST https://scheduling.eden.so/mcp/schedule-post or /mcp/drafts |
| Post media upload | POST https://api.eden.so/scheduling/media/prepare, then a PUT to the upload URL |
| Ask a Custom AI | POST https://ai.eden.so/zapier/ask |
| New Item Saved | GET https://api.eden.so/workspaces/<id>/mobile/library |
| Post Published | GET https://scheduling.eden.so/mcp/posts?status=posted (and partial) |
| Post Failed | GET https://scheduling.eden.so/mcp/posts?status=failed (and partial) |
| Routine Finished | GET https://ai.eden.so/zapier/routine-runs?workspace_id=<id> |
Ask a Custom AI answers at once with chat_id and chat_url, runs the chat in the background on normal AI credits, then posts { "ok": true, "answer": "..." } to Zapier's callback_url. Eden only posts to Zapier hook addresses. Leave out custom_ai_id to ask Eve.
Routine Finished returns each routine's latest run from the last day, with the answer.
Errors
- 400: a list or product field is wrong, like
limit=500.messagesays which. - 401: the sign-in expired, or the key was revoked. Reconnect Eden or make a new key.
- 402: selling with your own checkout needs a paid Eden plan (Starter or higher).
- 403: this key can't do that, like a Zapier connection changing a product, or a key with Store on Read sending a change (
code: "api_key_access"). - 404: no product, sale, or person in this store has that id.
- 409: it already exists, like a second product for one Custom AI.
- 422: a field is missing or not allowed, like an unknown
eventor a price below the minimum. - 429: too many sales at once. Try again in a minute.
- 500: something broke on Eden's side. Try again.
A turned-away sale answers 200 with "ok": false and a reason. The app and a product's sale address share one rate limit, one daily cap, and one event log, so the same order ID sent both ways counts once.
Run your store from code
Give buyers access from any checkout, pull every sale and customer, and make products with Eden's store API.
Use the Eden app on Zapier
Connect your store and workspace to thousands of apps, and give buyers access when they pay on any checkout Zapier supports.
Use your own checkout
Sell a product through PayPal, Gumroad, Paystack, or another checkout you already use, and give buyers access on Eden automatically.
Email us. A real person reads every message.
Tell us what you tried and where you got stuck. We answer within one business day.
[email protected]