API Reference

Everything a Custom Website storefront needs to call ExiusCart directly — products, checkout, payment, customer accounts, and more. No ExiusCart login is required to read this page or to call any endpoint marked No auth.

Base URLhttps://api.exiuscart.com/api/v1

Every path below is appended directly to that base URL. Every endpoint is scoped to your store either by a {shop_slug} in the path, or by an opaque token (order number + email, a review link, a download code) — never by an ExiusCart seller login. CORS is open, so you can call these directly from the browser.

No authCustomer tokenOptional customer tokenGateway webhook only

Customer token and Optional customer token mean a JWT from Customer Auth below, sent as Authorization: Bearer <token>. Gateway webhook only means your storefront should never call it — it exists purely for Stripe/PayPal/Whop to call directly.

Categories

Your storefront’s category tree, for nav menus and category pages.

GET/public/store/{shop_slug}/categoriesNo auth

Storefront category tree, for nav/grid display.

Params/Body Query: channel (default "custom")
Response [{ id, name, slug, icon_url, parent_id }]

Products

Listing, detail, and reviews for products you’ve enabled on your Custom Website channel. A product only appears here once the seller has explicitly turned it on for this channel — nothing shows by default.

GET/public/store/{shop_slug}/productsNo auth

Product listing for a grid/search page.

Params/Body Query: search, featured, trending, category (slug)
Response Array of products: id, name, slug, description, currency, price, compare_at_price, in_stock, product_type, affiliate_url, affiliate_cta_text, faq[] ({question, answer}), shipping_note, shipping_steps[], seo_title, meta_description, seo_keywords[], highlights[] ({icon, label}), quantity, images[], video_url, videos[], tags[], variants[], category_id, category_slug, category_ids[], category_slugs[], quantity_tiers[], avg_rating, review_count, view_count, units_sold, custom_fields
GET/public/store/{shop_slug}/products/{slug}No auth

Single product detail page (PDP). Increments the product’s view count on each call.

Response Same shape as the listing, single object. category_ids/category_slugs carry every category the product belongs to — a product can be filed under more than one. product_type is "physical" | "digital" | "affiliate" — for "affiliate", render affiliate_cta_text (fallback "Buy Now") as a link straight to affiliate_url instead of an Add to Cart button; the checkout endpoint below rejects affiliate products server-side if one is submitted anyway. description is rich HTML from the dashboard editor — render it with dangerouslySetInnerHTML (or equivalent), not as plain text. It can include a seller-inserted pill row (a <div> of <span> tags for things like skills/features) that carries its own inline style="..." attributes rather than relying on any ExiusCart stylesheet — if you sanitize this HTML before rendering, make sure your sanitizer’s allowlist keeps the style attribute on div/span, or those pills will silently lose their styling and show as plain text. faq is an optional seller-written Q&A list to render below the description; shipping_note is an optional free-text shipping/returns blurb (physical products only); shipping_steps is an optional ordered string array — render as a connected arrow-flow, falling back to shipping_note when empty; seo_title and meta_description are optional, written for the Google result: when set, use them as-is for the page <title> and <meta name="description">; seo_keywords is an optional string array the seller is targeting — use it to build your own meta title/description and schema.org Product structured data, it is not meant to populate a raw <meta name="keywords"> tag; highlights is an optional array of {icon, label} short facts meant to render under the price (e.g. "1 Year Access", "Delivered by Email") — never sent for affiliate products — where icon is always one of a fixed set: clock, calendar, mail, truck, package, shield, download, check-circle, refresh-cw, star, gift, tag. All render only when non-empty, no fallback copy.
GET/public/store/{shop_slug}/products/{slug}/reviewsNo auth

Approved reviews for one product.

Response [{ id, customer_name, country, rating, comment, photo_url, photos[], video_url, verified_buyer, pinned, seller_reply, reply_from, submitted_at, source_label }], pinned first. customer_name is null when the buyer hid their name: show their country instead (2-letter code, e.g. with a flag). photos has every photo (up to 5); video_url is the buyer’s short video. seller_reply is the store’s public answer: show it under the review as “Reply from {reply_from}”. source_label is set on reviews imported from another marketplace.
GET/public/store/{shop_slug}/products/{slug}/questionsNo auth

Answered shopper questions for one product (Questions & answers under the reviews).

Response { questions_enabled, questions: [{ id, question, answer, asker_name, answered_by, answered_at }] }. When questions_enabled is false, hide your "Ask a question" button.
POST/public/store/{shop_slug}/products/{slug}/questionsNo auth

A shopper asks about a product.

Params/Body Body: name, email, question (5-500 characters)
Response { status: "received" }. The store is emailed; the shopper gets the answer by email, and it shows in the list above once the store publishes it. 400 when the store turned questions off. 5 calls a minute.
GET/public/store/{shop_slug}/products/{slug}/rating-schemaNo auth

Ready JSON-LD for Google star ratings. Put json_ld in a <script type="application/ld+json"> on the product page.

Response { has_rating, json_ld }. json_ld is a schema.org Product with aggregateRating and up to 5 reviews. Only the shop’s own buyers’ reviews count (Google does not allow reviews copied from other sites), so has_rating is false until the store has some. If your page already outputs Product data, copy aggregateRating and review into it instead of adding a second Product.
GET/public/product/{barcode}No auth

Product + active reservations lookup for a QR/barcode page. Not shop_slug-scoped.

Response name, sku, barcode, price, currency, stock, reserved, available, shop_name, image_url, category, reservations[]

Reviews

Submitting a review from a post-delivery email link, and the embeddable review widget.

GET/public/review/{token}No auth

Review-request landing page data, from the link in a post-delivery email.

Response product_name, product_image, shop_name, customer_name, already_submitted, rating, comment, allow_photos, allow_videos, max_photos, reward ({ text, for: "any" | "media" } when the store gives a thank-you code), reward_sent
POST/public/review/{token}/submitNo auth

Submit a review via the request-link token.

Params/Body Body: rating (1–5), comment?, name? (the name shown with the review), photo_urls? (up to 5, from the photo upload below), video_url? (from the video upload)
Response { submitted: true, reward_sent }. reward_sent: the buyer was emailed a thank-you discount code.
POST/public/review/{token}/photoNo auth

Upload a photo for that review.

Params/Body multipart file (≤ 8MB, jpg/jpeg/png/webp)
Response { url }
POST/public/review/{token}/videoNo auth

Upload one short video for that review, when the store takes video reviews.

Params/Body multipart file (≤ 60MB, mp4/mov/webm)
Response { url }
GET/public/products/{product_id}/reviewsNo auth

Approved reviews for the embeddable widget, by raw product id.

Response { avg_rating, count, reviews: [same shape as the store reviews above], questions_enabled, questions: [...], rating_schema }. rating_schema is aggregateRating + review for Google (null until the store has its own buyers’ reviews).
POST/public/products/{product_id}/questionsNo auth

Ask a question by raw product id (what the widget uses).

Params/Body Body: name, email, question
Response { status: "received" }. 5 calls a minute.
GET/widget/reviews.jsNo auth

Static embeddable script — drop it in and it auto-mounts any element with data-exiuscart-reviews.

Params/Body On the element: data-product-id (required); data-questions="off" hides Questions & answers; data-schema="on" adds Google star-rating JSON-LD to the page (data-product-name sets the product name in it). Shows photos, videos and the store’s replies.

Customer Auth

Signup/login for shoppers on your storefront. Returns a customer JWT (type: "customer") that’s only valid on the customer-scoped endpoints below — it’s a different token type than a seller session and can’t be used to access the ExiusCart dashboard.

POST/public/store/{shop_slug}/auth/signupNo auth

Create a storefront customer account.

Params/Body Body: name, email, password (min 8 chars), birthday? ("MM-DD"; a full date works, only month and day are kept), marketing_consent? (true only if they ticked an unticked "email me offers" box), visitor_id? (the id you send to /track, so their browsing joins them)
Response { token, customer: { id, name, email, birthday } }. 409 if the email already has an account, 422 for a short password or a birthday that is not a real day. Making an account is not a marketing yes: without marketing_consent: true they get no marketing emails.
POST/public/store/{shop_slug}/auth/loginNo auth

Log an existing customer in.

Params/Body Body: email, password
Response { token, customer: { id, name, email } }
POST/public/store/{shop_slug}/auth/login-codeNo auth

Sign in without a password: email the shopper a 6-digit code.

Params/Body Body: email, create_account? (true makes the account when the email is new), name?
Response { status: "code_sent", sent_to, expires_in } every time, whether or not the email has an account, so it never reveals who shops at the store. Guest-checkout customers can sign in this way too, and see their past orders. A second request within 45 seconds is not re-sent. Codes last 10 minutes. 6 calls a minute.
POST/public/store/{shop_slug}/auth/login-code/verifyNo auth

Swap the emailed code for a login token.

Params/Body Body: email, code
Response { token, customer: { id, name, email, birthday, has_password } }, the same token password login gives. Codes are single use; 5 wrong tries cancel the code. 400 for a wrong or expired code.

Send the token as Authorization: Bearer <token> on any endpoint below marked "customer" or "optional". A 401 means the session ended (expired, or the password was changed on another device): send the customer back to login.

Orders, Addresses & Returns

The shopper’s own order history, “buy it again”, saved addresses and returns. Every endpoint acts on the customer in the token, and only on their own orders.

GET/public/store/{shop_slug}/account/ordersCustomer token

Order history, newest first.

Params/Body Query: skip?, limit? (up to 50)
Response { orders: [{ order_number, status, payment_status, total, items_count, created_at, tracking_number, carrier }], total, currency }
GET/public/store/{shop_slug}/account/orders/{order_number}Customer token

One order, its parcels and its returns.

Response { …order, items: [{ order_item_id, product_name, quantity, unit_price, variant_size, variant_color, returnable_quantity }], shipments: [{ items, carrier, tracking_number, tracking_url, created_at }], returns: [...], return_window: { can_return, why_not, until } }. An order can ship in more than one parcel; each has its own tracking.
POST/public/store/{shop_slug}/account/orders/{order_number}/reorderCustomer token

“Buy it again”: the cart lines for this order at today’s prices and stock.

Response { items: [{ product_id, variant_id, product_name, slug, image_url, quantity, ordered_quantity, unit_price, price_changed, limited_by_stock }], unavailable: [{ product_name, reason }], currency }. Nothing is ordered here: add items to your cart, then checkout as usual.
GET/public/store/{shop_slug}/account/addressesCustomer token

Saved addresses, default first.

Response { addresses: [{ id, label, name, phone, line1, line2, city, region, postal_code, country, is_default }] }
POST/public/store/{shop_slug}/account/addressesCustomer token

Save an address. The first one becomes the default.

Params/Body Body: line1, label?, name?, phone?, line2?, city?, region?, postal_code?, country? (2-letter code), is_default?
Response { addresses, id }. Up to 20 per customer. 422 for a country that isn’t a 2-letter code.
PATCH/public/store/{shop_slug}/account/addresses/{id}Customer token

Change an address, or make it the default (is_default: true).

Response { addresses }
DELETE/public/store/{shop_slug}/account/addresses/{id}Customer token

Delete an address. If it was the default, the oldest one left takes over.

Response { addresses }
GET/public/store/{shop_slug}/return-policyNo auth

The store’s return rules and the reasons to offer.

Response { returns_enabled, return_window_days, policy, reasons: [{ key, label }] }
POST/public/store/{shop_slug}/account/returnsCustomer token

Start a return.

Params/Body Body: order_number, items: [{ order_item_id, quantity }], reason (a key from return-policy), note?, preferred_outcome? ("refund" | "exchange" | "store_credit")
Response { id, return_number, status: "requested", items, … }. Only paid, shipped orders inside the return window; never more units than were bought (minus earlier returns). The store is emailed; the shopper gets a confirmation and an email each time it moves on. 10 calls a minute.
GET/public/store/{shop_slug}/account/returnsCustomer token

The shopper’s returns.

Response { returns: [{ id, return_number, order_number, status, reason, seller_note, refund_amount, items, created_at }] }. status: requested, approved, declined, received, refunded or cancelled.
POST/public/store/{shop_slug}/account/returns/{id}/cancelCustomer token

Cancel a return the store hasn’t handled yet.

Response The return, now cancelled. 400 once the store has approved or declined it.

Customer Account

ExiusCart is the backend for your storefront’s customers: their profile, password and payout details are stored here, so your site keeps no customer data of its own. Every endpoint acts on the customer in the token. Never send a customer id. Errors come back as { "detail": "message" }, written to show to the shopper as-is.

GET/public/store/{shop_slug}/account/profileCustomer token

The logged-in customer’s profile.

Response { customer: { id, name, email, phone, birthday }, pending_email: { sent_to, expires_at } | null }. pending_email is a new email still waiting for its code. Use it to show the "enter code" step again if the customer reloads.
PATCH/public/store/{shop_slug}/account/profileCustomer token

Update name, email, phone or birthday. Send only the fields that changed.

Params/Body Body: name?, email?, phone?, birthday? ("MM-DD"; "" removes it), current_password (required only when email changes)
Response { customer, email_change: { status: "code_sent", sent_to, expires_at } | null }. Name and phone save straight away. A new email does NOT: ExiusCart emails a 6-digit code to the new address, and customer.email stays the old one until the code is confirmed. 400 if the password is missing or wrong, 409 if the email belongs to another account, 422 if it isn’t a valid email.

Sending the same new email again sends a fresh code (use it as "resend").

POST/public/store/{shop_slug}/account/email/verifyCustomer token

Confirm a new email with the code that was sent to it. Only now does the email change.

Params/Body Body: { code }
Response { customer } with the new email. The old address gets an alert email. 400 if the code is wrong, expired (codes last 15 minutes) or nothing is waiting. After 5 wrong codes the change is cancelled and the customer must ask again. 409 if someone else took the email in the meantime.
POST/public/store/{shop_slug}/account/passwordCustomer token

Change the password. This signs the customer out on every other device.

Params/Body Body: current_password, new_password (min 8 chars)
Response { token, customer }. Replace your stored token with this new one, because the old token stops working. 400 if current_password is wrong, 422 if the new one is too short or the same as the old one.
GET/public/store/{shop_slug}/account/payout-methodCustomer token

Where this customer’s wallet withdrawals are sent, plus the payout methods this store accepts.

Response { payout_method: { method, method_label, account, account_name, paypal_email, changed_at, updated_at, withdrawals_unlock_at } | null, paypal_email, pending_payout_method: { method, method_label, account, account_name, sent_to, expires_at } | null, withdrawals_enabled, methods: [{ key, label, kind }] }. kind "email" means account is an email address; "text" (bank transfer) means free-form bank details. pending_payout_method is a new payout email still waiting for its code.
PUT/public/store/{shop_slug}/account/payout-methodCustomer token

Save or change payout details. Needs the current password. A new payout email (PayPal, Payoneer, Skrill, Wise) must be confirmed with a code sent to that address. Bank details save straight away.

Params/Body Body: method ("paypal" | "payoneer" | "skrill" | "wise" | "bank_transfer", only ones listed in methods above), account, account_name?, current_password. Shorthand: { paypal_email, current_password } is the same as method "paypal".
Response 202 { payout_method, paypal_email, verification: { status: "code_sent", sent_to, expires_at } } when a code was sent. payout_method is still the OLD details (or null) until the code is confirmed. 200 { payout_method, paypal_email, verification: null } when saved straight away (bank details, or only the name changed).
POST/public/store/{shop_slug}/account/payout-method/verifyCustomer token

Confirm a new payout email with the code that was sent to it. Only now are withdrawals sent there.

Params/Body Body: { code }
Response { payout_method, paypal_email, verification: null }. The customer gets an alert email. Changing existing details (not adding them the first time) pauses withdrawals for the store’s waiting time, shown as withdrawals_unlock_at. Show it to the customer. 400 if the code is wrong, expired (15 minutes) or nothing is waiting; 5 wrong codes cancel the change.

Checkout & Orders

Placing an order, guest or logged-in, and tracking it afterward.

POST/public/store/{shop_slug}/checkoutOptional customer token

Create a pending order and get back the payment gateway’s request parameters. Works for a guest (just name/email) or a logged-in customer. Stock is validated here but not yet decremented — that happens once payment is confirmed.

Params/Body Body: items [{ product_id, quantity, variant_id? }], name, email, phone?, shipping_address? (plain text, or an object { name, address, city, province, zip, country_code } — send the object if you want paid orders sent to your supplier automatically), use_wallet_amount? (logged-in only), return_url? / cancel_url? (Stripe & PayPal)
Response { order_number, total, payment: { gateway, order_id, ...gateway-specific fields — see Payment below } }
GET/public/store/{shop_slug}/orders/{order_number}No auth

Guest order lookup/tracking — matched by order number + the email it was placed with.

Params/Body Query: email (required)
Response { order_number, status, payment_status, total, items: [{ product_name, quantity, unit_price, total_price, variant_size, variant_color }], created_at, tracking_number, carrier, shipped_at, estimated_delivery }. status is "pending" | "confirmed" | "processing" | "shipped" | "delivered" | "cancelled" — moves to "confirmed" automatically once payment_status becomes "paid"; every status after that is set manually by the seller from their dashboard. tracking_number/carrier/shipped_at are null until the seller marks the order shipped — treat null as "not shipped yet", not an error.
GET/public/store/{shop_slug}/payment-return/paypalNo auth

PayPal redirects the shopper back here after approval; this captures the payment server-to-server, then redirects onward to your site.

Params/Body Query: order_number, redirect_to, token (PayPal order id)
Response 302 redirect to {redirect_to}?payment=success|failed&order_number=...

Payment

Stripe, PayPal, and Whop are supported. The gateway is configured per-seller in their ExiusCart dashboard; checkout’s response tells you which one is active and what to do with it.

POST/public/payment-webhook/{shop_slug}Gateway webhook only

Server-to-server only — the payment gateway calls this directly, your storefront never does. Signature-verified before anything is trusted; marks the order paid, decrements stock, credits wallet cashback, and triggers digital delivery if applicable.

Wallet

Store credit earned as cashback on paid orders, spendable at checkout. If the store is on the Growth or Scale plan and the owner turns withdrawals on, customers can also ask for their balance as money. The owner pays them by hand (PayPal, Payoneer, Skrill, Wise or bank) and marks it paid in their dashboard.

GET/public/store/{shop_slug}/wallet-settingsNo auth

The store’s cashback rate and withdrawal rules. Use it for "5% back" labels and to decide whether to show a Withdraw button.

Response { is_enabled, cashback_percent, withdrawals: { enabled, min_amount, currency, methods: [{ key, label, kind }], payout_change_hold_hours } }
GET/public/store/{shop_slug}/walletCustomer token

Balance and the last 50 transactions for the logged-in customer.

Response { balance, on_hold, currency, withdrawals_enabled, transactions: [{ type, amount, description, payout_request_id, created_at }] }. balance is what can be spent or withdrawn now. on_hold is already taken out of it while a withdrawal waits.
POST/public/store/{shop_slug}/wallet/payout-requestsCustomer token

Ask to withdraw part or all of the balance. The amount goes on hold straight away.

Params/Body Body: { amount }. Nothing else is read. The customer, balance, currency and payout address all come from the token and the saved payout method, so extra fields are ignored.
Response 201 { id, amount, currency, method, method_label, account, paypal_email, status: "pending", reject_reason, payment_reference, can_cancel, created_at, approved_at, paid_at, rejected_at, cancelled_at }. 403 if the store has withdrawals off. 400 if there are no payout details, the amount is under the minimum or over the balance, or withdrawals are paused after a payout-details change. 409 if a withdrawal is already in progress (one at a time).
GET/public/store/{shop_slug}/wallet/payout-requestsCustomer token

This customer’s withdrawals, newest first.

Response Array of the same shape as above. status is "pending" | "approved" | "paid" | "rejected" | "cancelled". Show reject_reason when rejected and payment_reference when paid.
POST/public/store/{shop_slug}/wallet/payout-requests/{id}/cancelCustomer token

Cancel a withdrawal that is still pending. The amount goes back into the wallet.

Response The updated request. 409 once the store has approved or acted on it.

Spend the balance by passing use_wallet_amount in the checkout body above. It is capped server-side at the real balance, and only ever debited from the authenticated customer’s own account, never a guest email. Money on hold for a withdrawal can’t be spent. The customer gets an email at every step of a withdrawal, and the store owner gets one for each new request.

Digital Delivery

The download gate for digital products — the real file link is only ever revealed after a code sent by email is verified.

GET/public/download/{token}No auth

Download gate landing page — reveals the product/shop name only, never the file.

Response { product_name, shop_name }
POST/public/download/{token}/verifyNo auth

Verify the emailed access code. Rate-limited to 10 attempts/hour/token.

Params/Body Body: code
Response { file_url, file_name }

Blog

GET/public/store/{shop_slug}/blogNo auth

Published post list (omits the full content field, for a lighter list view).

Params/Body Query: tag?
Response [{ id, title, slug, excerpt, cover_image_url, status, published_at, author_name, tags[], cta_text, cta_url, view_count, created_at, updated_at }]
GET/public/store/{shop_slug}/blog/{slug}No auth

Single post detail. Increments view count.

Response Same shape, plus content.

Visitor Tracking & Email Marketing

What powers the Website Traffic chart, the product funnel and the store’s Eevo Flows emails (browse abandonment, back in stock, birthday...). Keep one random visitor_id per browser (8-64 letters, digits, - or _, e.g. in localStorage) and send it on every call. Knowing someone’s email is not permission to market to them: send marketing_consent: true only when the shopper ticked an unticked "email me offers" box. Without it, a new shopper gets only the emails they asked for (like back in stock), never marketing. All of these return 404 for an unknown or inactive store, and 429 when over the rate limit.

POST/public/store/{shop_slug}/trackNo auth

One visitor event. On every page load send ONE of: page (any non-product page, with path) or view (a product page, with product_id). Also send search (with query) and add_to_cart (with product_id). 60 a minute per IP.

Params/Body Body: { event: "page" | "view" | "search" | "add_to_cart", product_id?, query?, path?, visitor_id?, email? (when signed in), marketing_consent? }
Response { ok: true }. 422 for an unknown event, a view/add_to_cart without product_id, or a search without query.
POST/public/store/{shop_slug}/identifyNo auth

The shopper gave their email (newsletter box, checkout email field, sign-in). Ties the visitor_id to them, and their last 30 days of views and add-to-carts join their profile. 20 a minute per IP.

Params/Body Body: { visitor_id, email, name?, birthday? ("MM-DD"), marketing_consent? }
Response { ok: true, marketing_consent, needs_confirmation }. marketing_consent is whether this person may now get marketing emails. needs_confirmation is true when the store uses double opt-in and a confirmation email was sent. 422 for a bad visitor_id, email or birthday.

marketing_consent: true subscribes them to the store’s email list and keeps proof (time, IP, browser, page). false or missing never removes an earlier yes. The unsubscribe link in every email does that. On sign-out, start a new visitor_id so the next person on a shared computer is not mixed up with them.

POST/public/store/{shop_slug}/subscribeNo auth

Newsletter signup by store slug. Subscribing is the marketing yes. 20 a minute per IP.

Params/Body Body: { email, name?, birthday? ("MM-DD"), visitor_id?, source? ("website_form" | "popup" | "storefront"), popup_id?, page_url? }
Response { status: "ok", discount_code, prize, needs_confirmation }. discount_code is the popup’s code when popup_id is one of the store’s popups (that is how a "10% off for signing up" box gets its code), else null. needs_confirmation: true means double opt-in is on, so show "check your inbox" rather than the code.
POST/public/email-list/{shop_id}/subscribeNo auth

The same signup by numeric shop id (used by the popup and form widgets).

Params/Body Body: { email, name?, source?, popup_id?, page_url? }
Response As above.
POST/public/store/{shop_slug}/product-alertsNo auth

"Notify me when it’s back", "Tell me if the price drops" or a wishlist save. The Back in stock / Price drop flows email them, even without marketing consent, because they asked for it. 20 a minute per IP.

Params/Body Body: { product_id, email, kind?: "back_in_stock" | "price_drop" | "wishlist", name?, visitor_id?, marketing_consent? }
Response 201 { ok: true, token, kind }. Keep the token to remove the request later.
DELETE/public/store/{shop_slug}/product-alerts/{token}No auth

Remove a product alert or wishlist save.

Response 204

Privacy, for your privacy policy: ExiusCart stores the events above with the visitor_id, the page path or product, and the visitor’s country (looked up from the IP, which is then thrown away). Browsing is kept against the visitor_id for 90 days, then only as anonymous counts (deleted after about two years). A store can erase any shopper on request from Customers > Erase a shopper. That removes their profile, browsing, carts, consent history and leads, and anonymises their customer record. Orders stay for the store’s tax records, and their email stays only as "unsubscribed" so they are never emailed again.

Popups

Announcement, exit-intent, email-capture, and countdown popups the seller has built.

GET/public/popups/{shop_id}No auth

Active popups for the embed script. Note: shop_id, not slug.

Response { popups: [{ id, popup_type, title, message, button_text, button_link, discount_code, image_url, delay_seconds }] }
POST/public/popups/{popup_id}/trackNo auth

Track an impression or click.

Params/Body Query: event ("impression" | "click")
Response { ok: true }
GET/widget/popup.jsNo auth

Static embeddable script that renders the popups above.

Signup Forms & Lead Capture

Seller-built lead forms, plus a way to mirror submissions from a form that already exists on your own site.

GET/public/signup-forms/{shop_id}No auth

Active custom-built signup/inquiry forms.

Response { forms: [{ id, title, description, fields[], delay_seconds }] }
POST/public/signup-forms/{form_id}/trackNo auth

Track a form impression.

Response { ok: true }
POST/public/signup-forms/{form_id}/submitNo auth

Submit answers — creates a Lead in the seller’s CRM if an email field is present.

Params/Body Body: { answers: { field_id: value } }
Response { status, success_message, discount_code }
POST/public/capture-form/{shop_id}/submitNo auth

Mirrors a submission of your own existing HTML form (tagged data-exiuscart-capture) into the Lead CRM, without changing how that form behaves.

Params/Body Body: { fields: [{ name?, id?, type?, value? }], url? }
Response { status: "ok" }
GET/widget/signup-form.jsNo auth

Static embeddable script — renders custom forms and passively mirrors tagged existing forms.

Misc & Utility

GET/public/check-ref/{code}No auth

Check whether an affiliate referral code is active.

Response { valid: boolean }
GET/public/reservation/{reservation_id}No auth

Reservation info for a QR-code public page.

Response id, customer_name, customer_phone, product_name, quantity, reservation_type, status, advance_amount, notes, expires_at, shop_name, currency, product_stock, product_reserved
GET/public/quotation/{token}No auth

A client views a shared B2B quotation.

Response quote_number, shop_name, items, subtotal, discount, tax, total, status, valid_until, and more
POST/public/quotation/{token}/respondNo auth

Client accepts or rejects a quotation.

Params/Body Body: { action: "accept" | "reject", name? }
Response { status }
GET/shops/exchange-ratesNo auth

Live currency conversion rates (12h cache). Not shop-scoped — a general utility.

Params/Body Query: base (default "USD")
Response { base, rates, cached, stale? }
Questions about integrating? Reach out at dropshipping@exiuscart.com.