API & webhooks

Eden (Creator Platform) API reference

Every store API endpoint, with sign-in by API key or OAuth, for developers and Zapier's review team.

·11 min read

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.

  1. In Eden, go to Settings → API and click Generate key with Full access picked, or Custom with Store on Read or Edit.
  2. 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 for read write, the same access as Eden's MCP connectors, for the workspace steps.
  • Authorize: GET https://oauth.eden.so/oauth/authorize with response_type=code, client_id, redirect_uri, state, scope, code_challenge, and code_challenge_method=S256.
  • Token: POST https://oauth.eden.so/oauth/token, form encoded. Send grant_type=authorization_code with code, redirect_uri, client_id, client_secret, and code_verifier.
  • Refresh: the same token URL with grant_type=refresh_token and refresh_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.

RouteFiltersWhat each row is
GET /store-app/v1/salesproduct, email, status (paid, refunded, or free)One sale line, from Eden checkout or your own checkout
GET /store-app/v1/customersproduct, 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-contactsstatus (subscribed, unsubscribed, bounced, complained), tagOne person on your email list
GET /store-app/v1/productsstatus (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: the next_cursor from the page before, for the next page.
  • created_after and created_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 an id from 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.

RouteWhat it does
GET /store-app/v1/custom-aisThe Custom AIs and courses this store can sell, with product_id once one has a product
POST /store-app/v1/productsMake 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>/publishPut it live. People who already bought it get the new version.
POST /store-app/v1/products/<id>/unpublishStop sales. Buyers keep what they have.
FieldWhat it is
title, slugThe name and the end of its web address
tagline, headline, subheadlineThe short line under the name, and the big lines at the top of the page
price_centsThe price in cents of your store's currency. 0 is free.
billingone-time, monthly, or yearly
yearly_price_centsWith monthly, a yearly price buyers can pick instead
compare_at_centsA crossed-out "was" price
trial_daysFree 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_waitlistSell a set number, show how many are left, and take a waitlist once sold out
waitlist_mode, waitlist_opens_atTake waitlist emails instead of selling, with an optional YYYY-MM-DD open day
included_creditsAI credits in the price: 0, 100, 250, 500, or 1000
chat_enabledfalse turns the AI off, making it a course or a download
checkout_mode, checkout_urleden, or external with your own checkout link
body_markdown, faq_markdown, creator_bio, refund_policyPage copy
welcome_markdownThe Start here page buyers see after they buy
hero_image_url, video_url, theme, accent, cta_label, sign_offThe look and the buy button
whats_includedUp to 12 lines for the offer card. null brings back Eden's own.
sectionsShow 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.

FieldRequiredWhat it is
eventYessale, renewal, cancel, end, or refund
productYesA product id from the products list
emailYesThe email the buyer paid with
nameNoThe buyer's name
order_idNoThe checkout's order or payment ID. The same ID twice counts once.
subscription_idNoThe checkout's subscription ID, for memberships
paid_untilNoWhen the paid time ends (ISO date or milliseconds)
ends_atNoWhen 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.

FieldRequiredWhat it is
productYesA product id from the products list
emailYesThe customer's email
nameNoThe customer's name
send_emailNofalse 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>.

TriggerRouteList key
New CustomerGET /store-app/v1/customers/recentcustomers
New Membership PaymentGET /store-app/v1/memberships/paymentspayments
Membership Cancelled or EndedGET /store-app/v1/memberships/changeschanges
New RefundGET /store-app/v1/refunds/recentrefunds
New Email SignupGET /store-app/v1/email-signups/recentsignups
New ReviewGET /store-app/v1/reviews/recentreviews
New Cohort SignupGET /store-app/v1/cohort-signups/recentsignups
Form CompletedGET /store-app/v1/quiz-responses/recentresponses
New BookingGET /store-app/v1/calls/bookedcalls
Booking CancelledGET /store-app/v1/calls/canceledcalls
Booking RescheduledGET /store-app/v1/calls/movedcalls
New Coaching ApplicationGET /store-app/v1/coaching-applications/recentapplications
New AffiliateGET /store-app/v1/affiliates/recentaffiliates
New Affiliate ApplicationGET /store-app/v1/affiliates/applicationsapplications
New Affiliate SaleGET /store-app/v1/affiliates/salessales
New TestimonialGET /store-app/v1/testimonials/recenttestimonials
Lesson CompletedGET /store-app/v1/lessons/completedcompletions
New Lesson QuestionGET /store-app/v1/lesson-questions/recentquestions
New Paid QuestionGET /store-app/v1/paid-questions/recentquestions
  • New Customer covers sales, free claims, own-checkout sales, imports, and people added by hand. how is bought, free, own_checkout, or added.
  • New Membership Payment covers the first payment and every renewal. payment is first or renewal.
  • Membership Cancelled or Ended has change set to cancelled (access runs until access_ends_at) or ended (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_email is empty when Eden can't match the review to a customer.
  • Form Completed takes ?quiz=<form id> instead of a product. marketing_consent says whether the person ticked the email box.
  • Bookings take ?call_type=<call type id> instead of a product; GET /store-app/v1/call-types lists them. A booking waiting on payment isn't listed until it's paid. Each move is its own Booking Rescheduled event, with previous_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_course set to true on the lesson that finishes the course.
  • New Paid Question covers questions buyers send through an Ask me product. answer_due_at is when your promised reply time runs out, and answer_url opens 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.

RouteWhat it does
GET /store-app/v1/forms/<form id>/applicationsThe people who applied. status: pending (default), accepted, declined, or all
POST /store-app/v1/forms/<form id>/applications/acceptAccept one person
POST /store-app/v1/forms/<form id>/applications/declineDecline 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/hooks with { "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_url must be a zapier.com address. A 410 answer 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.

RouteWhat it does
GET /store-app/v1/webhook-eventsEvery event type, grouped
GET /store-app/v1/webhooksYour webhooks
POST /store-app/v1/webhooksAdd 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-secretA new secret. The old one stops working.
POST /store-app/v1/webhooks/<id>/testSends a sample now, optional type. Answers delivered, response_code, response.
GET /store-app/v1/webhooks/<id>/deliveriesEach try's result, newest first, paged
POST /store-app/v1/deliveries/<id>/resendSends that event again
GET /store-app/v1/eventsEvery 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.

RouteExtra fieldsWhat it does
POST /store-app/v1/email-contactsnameAdds the person to your email list if new, then adds the tags.
POST /store-app/v1/email-contacts/remove-tagsNoneRemoves 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 of name, subscribed: false (unsubscribes them), add_tags, and remove_tags. Eden never turns emails back on for someone who unsubscribed, bounced, or marked spam, so subscribed: true answers 409 for 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.

RouteExtra fieldsWhat it does
POST /store-app/v1/customers/remove-accessNoneSwitches off access. An Eden checkout membership stops billing.
POST /store-app/v1/customers/access-emailNoneSends the access email again. One per person per minute.
POST /store-app/v1/customers/cohortcohort (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.

FieldRequiredWhat it is
productYesA product id. It must have a price and use Eden checkout.
kindYespercent or amount
valueYesPercent off (1 to 100), or dollars off
codeNo3 to 24 letters, numbers, or dashes. Empty makes a random one.
max_usesNoHow many times it can be used
expires_atNoWhen 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.

StepEndpoint
Workspace dropdownGET https://api.eden.so/me
Board dropdownGET https://api.eden.so/workspaces/<id>/mobile/items?type=canvas
Custom AI dropdownGET https://api.eden.so/custom-ai?workspaceId=<id>
Schedule dropdownGET https://scheduling.eden.so/mcp/schedules?workspaceId=<id>
Save LinkPOST https://api.eden.so/workspace-items/link, then POST /items/<id>/reference for a board
Create NotePOST https://api.eden.so/workspace-items/markdown
Schedule a PostPOST https://scheduling.eden.so/mcp/schedule-post or /mcp/drafts
Post media uploadPOST https://api.eden.so/scheduling/media/prepare, then a PUT to the upload URL
Ask a Custom AIPOST https://ai.eden.so/zapier/ask
New Item SavedGET https://api.eden.so/workspaces/<id>/mobile/library
Post PublishedGET https://scheduling.eden.so/mcp/posts?status=posted (and partial)
Post FailedGET https://scheduling.eden.so/mcp/posts?status=failed (and partial)
Routine FinishedGET 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. message says 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 event or 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.

Still stuck?

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]