E2C Store Docs
Integrations & API

REST API Reference

Endpoints, authentication, pagination, rate limits, and errors for the E2C Store REST API.

The E2C Store API gives you access to your own store's data over HTTPS, returned as JSON. Use it to pull new orders into a spreadsheet, sync stock and prices with another system, add products from your inventory tool, or trigger a Zapier workflow when a sale comes in.

Most endpoints only read. Products and categories can also be created and edited by keys with the matching Add & edit permission. Nothing in the API deletes.

Base URL

https://e2c.store/api/v1/

All paths below are relative to this base URL.

Quick start

  1. In your dashboard, go to Settings → API keys and create a key. See API keys.
  2. Copy the key — it's shown once.
  3. Call GET /store, which works with any key:
export E2C_API_KEY="e2c_sk_your_key_here"

curl "https://e2c.store/api/v1/store" \
  -H "Authorization: Bearer $E2C_API_KEY"
{
  "data": {
    "name": "Maple & Thread",
    "currency": "CAD",
    "timezone": "America/Toronto",
    "api_key": { "scopes": ["orders:read", "products:read"] }
  }
}

In Zapier, use a Webhooks by Zapier GET step or an API Request action with the same URL and Authorization header.

Authentication

Every request needs an Authorization: Bearer <key> header. Keys start with e2c_sk_.

A key sent in the URL (as api_key, apikey, key, token, or access_token) is rejected with 401 invalid_api_key, even if the key is correct, because URLs end up in logs. Treat a key like a password. If one leaks, revoke it in Settings → API keys; revoking takes effect immediately.

A key stops working when it is revoked, when it expires, or if the store is no longer active.

Permissions (scopes)

Each key carries one or more permissions. Calling an endpoint without the permission it needs returns 403 insufficient_scope.

ScopeShown in settings asGrants
orders:readOrdersOrders, items, shipping, and tracking, including the buyer's name and shipping address.
products:readProductsPhysical and digital products, variants, stock, and their reviews.
services:readServicesServices and their reviews.
events:readEventsEvents, ticket types and sales, and their reviews.
customers:readCustomersThe customer list. Also adds the buyer's email and phone to order responses.
reports:readReportsSales, revenue trend, order status, top products, and traffic reports.
categories:writeAdd & edit categoriesCreate categories, and rename, recode, show, or hide them.
products:writeAdd & edit productsCreate physical products, and edit the details, price, stock, and visibility of physical and digital products.

Reading categories and reviews needs any one of products:read, services:read, or events:read, and only shows the listing types the key can read. Write permissions don't include read access.

Endpoints

MethodPathPermissionDescription
GET/storeAny keyStore name, currency, timezone, and this key's scopes
GET/ordersorders:readList orders
GET/orders/{order_number}orders:readOne order with items and tracking
GET/productsproducts:readList physical and digital products
GET/products/{code}products:readOne product with description, shipping, variants, and combinations
POST/productsproducts:writeCreate a physical product
PATCH/products/{code}products:writeEdit a physical or digital product
GET/servicesservices:readList services
GET/services/{code}services:readOne service with description
GET/eventsevents:readList events
GET/events/{code}events:readOne event with description and ticket types
GET/categoriesAny catalog readAll categories (never paginated)
GET/categories/{code}Any catalog readOne category
POST/categoriescategories:writeCreate a category
PATCH/categories/{code}categories:writeRename, recode, show, or hide a category
GET/reviewsAny catalog readPublished reviews
GET/customerscustomers:readOne entry per customer email
GET/reports/salesreports:readSales summary
GET/reports/revenue-trendreports:readDaily revenue
GET/reports/order-statusreports:readOrder count per status
GET/reports/top-productsreports:readBest sellers by revenue
GET/reports/trafficreports:readSessions, visitors, page views, and funnel

Orders

GET /orders returns orders newest first. It includes every checkout status — abandoned and failed checkouts too, not just completed sales. List entries don't include items or tracking; retrieve the order for those.

ParameterTypeDescription
statusstringOnly orders with this status code, e.g. CONF, SHIP, DEL.
created_sincedateISO 8601 date or date-time. Inclusive.
created_beforedateISO 8601 date or date-time. Exclusive.
limitinteger1–100, default 25.
cursorstringThe next_cursor from the previous page.

GET /orders/{order_number} uses the order number shown in your dashboard.

{
  "data": {
    "order_number": 1042,
    "created_at": "2026-09-01T14:03:00Z",
    "status": "SHIP",
    "status_label": "Shipped",
    "currency": "CAD",
    "subtotal": 39.98,
    "shipping_price": 5.5,
    "total": 45.48,
    "paid": true,
    "payment_method": "stripe",
    "shipping_method": "Standard",
    "item_types": ["product"],
    "note": "",
    "customer": { "name": "Ada Lovelace", "email": "[email protected]", "phone": "555-0100" },
    "shipping_address": { "line1": "1 Main St", "line2": "", "city": "Toronto", "state": "ON", "postal_code": "M5V 1A1", "country": "Canada" },
    "items": [
      { "type": "product", "product_code": "linen-tee", "product_name": "Linen Tee", "variant": "Size: M", "quantity": 2, "unit_price": 19.99, "shipping_per_item": 2.75 }
    ],
    "tracking": { "carrier": "UPS", "tracking_number": "1Z999", "tracking_details": "", "ready_for_pickup": false, "updated_at": "2026-09-02T09:00:00Z" }
  }
}
  • customer.email and customer.phone only appear when the key also has customers:read.
  • item_types and each item's type are product, digital, service, or event. Event tickets are always ordered on their own.
  • An item's product_code is null if the listing has since been deleted. variant is null for products without variants. tracking is null until you add shipping details.
  • payment_method is stripe, paypal, cash, interac, or zelle.

Order status codes

CodeLabelCodeLabel
ABORTAbortedRFPReady For Pickup
PPPending PaymentCAPCash at Pickup
PROC-PAYProcessing PaymentDELDelivered
PAY-FAILPayment FailedREFUNDRefunded
CONFConfirmedRETURNReturned
PROC-ORDProcessing OrderCANCELCancelled
SHIPShippedCLOSEClosed
COMPCompleteSCHEDULEDScheduled

ABORT, PP, PROC-PAY, and PAY-FAIL are checkouts that never became a sale.

Building a "new order" automation? Poll GET /orders without a status filter and skip those four checkout states. Filtering on CONF alone misses cash orders (CAP), pay-after-service bookings (SCHEDULED), and orders that moved on before your next poll.

Products, services, and events

GET /products, GET /services, and GET /events share the same parameters and return newest-created first. Deleted listings are never returned. Events are sorted by when they were created, not by event date.

ParameterTypeDescription
statusstringactive, inactive, or incomplete.
updated_sincedateISO 8601. Covers creation and edits made in the dashboard editor. Stock changes from sales and quick status toggles don't count as updates.
categorystringOnly listings in the category with this code.
qstringSearch by name or code (up to 100 characters).
limitinteger1–100, default 25.
cursorstringThe next_cursor from the previous page.

A product looks like this (list entries leave out description, keywords, shipping, variants, and combinations):

{
  "data": {
    "code": "linen-tee",
    "name": "Linen Tee",
    "sku": "LT-001",
    "brand": "Maple & Thread",
    "category": { "code": "tops", "name": "Tops" },
    "status": "active",
    "currency": "CAD",
    "price": 19.99,
    "compare_at_price": 24.99,
    "short_description": "Breathable everyday tee.",
    "created_at": "2026-08-12T16:20:00Z",
    "updated_at": "2026-09-03T10:45:00Z",
    "stock": 42,
    "is_digital": false,
    "gtin": "",
    "mpn": "",
    "condition": "new",
    "condition_other": "",
    "local_pickup": false,
    "keywords": ["linen", "summer"],
    "description": "Cut from 100% European linen...",
    "shipping": [
      { "method": "Standard Shipping", "delivery_time": "7-14 business days", "price": 5.5 }
    ],
    "variants": [
      { "name": "Size", "options": ["S", "M", "L"] }
    ],
    "combinations": [
      { "options": ["M"], "price": 21.99, "stock": 8 }
    ]
  }
}
  • code identifies a listing everywhere in the API. You can change a product's code in the dashboard, so update any stored codes if you rename one.
  • compare_at_price is the struck-through "was" price, or null.
  • condition is new, refurbished, used_like_new, used_very_good, used_good, used_acceptable, or other — null for digital products.
  • description is the HTML from your product editor. A combination's price or stock is null when not set for that combination.

Services add service_type (hourly, daily, project, recurring, or in_person), price_label, location (type is remote, in_person, or both), delivery_estimate, and pay_after_service.

Events add starts_at, ends_at (both UTC; ends_at may be null), timezone, location (type is venue, online, or tba), min_age, age_policy, event_policy, and contact. GET /events/{code} also returns ticket_types, each with name, description, price, capacity, sold, and available. capacity and available are null for unlimited tickets; sold includes tickets held in checkouts still in progress.

Categories

GET /categories returns all categories sorted by name. A store has at most 25 categories, so has_more is always false.

{
  "data": [
    { "code": "tops", "name": "Tops", "status": "active", "counts": { "products": 14, "services": 0, "events": 0 } }
  ],
  "has_more": false,
  "next_cursor": null
}

counts only includes the listing types your key can read.

Reviews

GET /reviews returns published reviews, newest first.

ParameterTypeDescription
product_codestringOnly reviews for this product, service, or event.
created_sincedateISO 8601 date. Compared by day.
limit, cursorAs above.

Each review has product_code, product_name, reviewer_name, rating (average of the three ratings), ratings (quality, price, value, each 1–5), summary, content, and posted_on (a date without time).

Customers

GET /customers returns one entry per customer email, newest customers first, with email, name, phone, order_count, first_order_at, and last_order_at. Name and phone come from the customer's most recent order. Takes limit and cursor.

Reports

These return the same numbers you see on your dashboard.

PathParametersReturns
/reports/salesdays 1–365, default 7period_days, total_orders, pending_orders, total_sales, total_refunds, total_revenue
/reports/revenue-trenddays 1–90, default 30currency and a days array of date, revenue, order_count (days with no orders included as zeros)
/reports/order-status—Array of status, status_label, count, across all time
/reports/top-productsdays 1–365, default 30; limit 1–50, default 10Array of product_code (null if deleted), name, units_sold, revenue
/reports/trafficdays 1–90, default 30period_days, sessions, unique_visitors, page_views, and a funnel with product_views, cart_visits, checkout_starts, checkout_completes, product_view_to_order_rate (a percentage)

Writing data

Create with POST and edit with PATCH, sending a JSON object with Content-Type: application/json (up to 64 KB). A PATCH only changes the fields you send. Send null (or "") to clear an optional field. Unknown fields are rejected, so a typo never fails silently.

A successful POST returns 201, a successful PATCH returns 200, both with the saved record in data, exactly as a GET returns it. Writes count toward the same rate limit and appear in your store's audit history under the API key's name.

The API never deletes. To hide a product or category, set its status to inactive; delete it in the dashboard.

Product fields

FieldTypeRules
namestringUp to 50 characters. Unique across your products, services, and events. Required on create.
pricenumberIn your store's currency, up to two decimals. Required on create.
codestringUp to 50 lowercase letters, numbers, and single hyphens. Made from the name if left out.
compare_at_pricenumberThe "was" price. null or 0 removes it.
stockinteger0–1,000,000.
statusstringactive or inactive. New products are active unless you send inactive.
categorystringA category code. null removes the product from its category.
sku, brand, gtin, mpnstringOptional identifiers.
conditionstringOne of the condition values above. Physical products only.
condition_otherstringRequired when condition is other.
short_descriptionstringUp to 150 characters of plain text.
descriptionstringUp to 20,000 characters. Plain text or simple HTML (paragraphs, line breaks, bold, italic, underline, strikethrough, headings, lists, blockquotes, and https/mailto links). Other tags are removed but their text is kept.
keywordsstring[]Replaces the existing list; [] clears it.
shippingobject[]Up to 3 options, each with method, delivery_time, and price (0 for free). One must be Standard Shipping, and each method can appear once. Replaces the existing list. Physical products only.
local_pickupbooleanWhether buyers can pick up locally. Physical products only.

Shipping method is one of: Standard Shipping, Economy Shipping, Express Shipping, Expedited Shipping, Standard Air Shipping, Standard Sea Shipping. delivery_time is one of: 1-2 business days, 1-7 business days, 7-14 business days, 14-21 business days, 21-28 business days, 1-2 months.

curl -X POST "https://e2c.store/api/v1/products" \
  -H "Authorization: Bearer $E2C_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Linen Tee", "price": 19.99, "stock": 42, "category": "tops", "shipping": [{ "method": "Standard Shipping", "delivery_time": "7-14 business days", "price": 5.5 }] }'

Things to know when writing products:

  • POST /products only creates physical products. Images, variants, and digital files are added in the dashboard, so digital products, services, and events can't be created through the API.
  • A new product is live straight away, so it needs at least one shipping option or "local_pickup": true — unless you send "status": "inactive".
  • PATCH returns 409 conflict if it would leave an active physical product with no shipping and no local pickup, or if you set price or stock on a product whose variants have their own prices or stock (change those in the dashboard).
  • To change a product's code, send the new one as code; the old code stops working right away.
  • PATCH on a service or event code returns 404. condition, condition_other, shipping, and local_pickup can't be set on digital products.
  • A store holds at most 100 listings; past that, POST returns 409 limit_reached.

Category fields

FieldTypeRules
namestringUp to 25 characters. Unique in your store, and can't be "All". Required on create.
codestringUp to 25 lowercase letters, numbers, and single hyphens. Made from the name if left out.
statusstringactive shows it on your storefront; inactive hides it.

Products in a category stay in it when its code changes. A store has at most 25 categories; past that, POST returns 409 limit_reached.

Pagination

List endpoints return up to limit items (default 25, max 100), newest first:

{
  "data": [ { "order_number": 1042 } ],
  "has_more": true,
  "next_cursor": "ml-k3Q58S106wVawzKYPjKh88VSVcUhMVINaOHqpYMiBAA"
}

When has_more is true, pass next_cursor as the cursor parameter to get the next page. On the last page, next_cursor is null. Cursors are opaque: pass them back exactly as received and don't store them long-term. New items never shift existing pages, so polling the first page reliably catches new orders.

Rate limits

Each key can make 60 requests per minute. Responses that pass authentication and the permission check include:

HeaderMeaning
X-RateLimit-LimitRequests allowed per minute
X-RateLimit-RemainingRequests left in the current window
X-RateLimit-ResetWhen the window resets, as Unix time

Over the limit you get 429 rate_limited with a Retry-After header in seconds.

Dates and money

  • Times are ISO 8601 in UTC, like 2026-09-26T14:03:00Z. Date filters accept a date (2026-09-01, meaning midnight UTC) or a full date-time with an offset.
  • Order and product times recorded before June 20, 2026 were stored in US Eastern time and may read up to 5 hours early.
  • Money values are numbers with up to two decimals, with a currency code on the same object.

Errors

Errors use standard HTTP status codes and a JSON body:

{ "error": { "code": "insufficient_scope", "message": "This API key needs the 'products:read' permission." } }
StatusCodeMeaning
400invalid_parameterA query parameter or body field is missing or out of range. The message names it.
400invalid_bodyThe request body is not a JSON object.
400prohibited_contentThe text contains content that isn't allowed on E2C stores.
401invalid_api_keyThe key is missing, wrong, revoked, or expired — or was sent in the URL.
403insufficient_scopeThe key lacks the permission this endpoint needs.
404not_foundThe endpoint, order, product, or category doesn't exist in your store.
405method_not_allowedThe endpoint doesn't support this method. The Allow header lists the ones it does.
409conflictThe change clashes with your store as it is now, such as a name or code already in use.
409limit_reachedYour store already has the most categories or listings it can hold.
413payload_too_largeThe request body is larger than 64 KB.
415unsupported_media_typeA write was sent without Content-Type: application/json.
429rate_limitedToo many requests. Wait for the seconds in Retry-After.
500internal_errorSomething went wrong on our side. Retry later.

Good to know

On this page