MENU navbar-image

Introduction

Read and write store data from your own systems: products, variants, inventory, orders and webhooks.

This API is served over HTTPS and returns JSON. Every request needs an Authorization: Bearer <token> header.

Keep the token on your server

A token is scoped to the whole store, not to one shopper or one record. Anything its scopes allow, it allows across every order, product and customer the store has. Treat it as a server-side credential: never ship it to a browser, a mobile app or any other client you do not control.

This matters most for shopper-facing pages such as order tracking. Call this API from your own backend, verify there that the requester owns the record, and return only that record to the page.

Pagination

List endpoints are cursor-paginated. Pass limit (1–250, default 50) and read meta.next_cursor from the response; request the next page by sending that value back as cursor. When meta.has_more is false you have reached the end. Cursors are opaque — do not construct or modify them.

Keeping data in sync

products, variants and orders accept updated_since (ISO-8601). Combined with cursor pagination this gives you an incremental feed of everything that changed since your last poll.

The product feed also reports removals, and deleted_at is the signal: drop any record that carries one, whatever its status. Archiving a product in the admin unpublishes it, so it arrives here with deleted_at set and belongs out of your copy too — a shopper cannot buy it. A product removed permanently arrives collapsed to "status": "deleted" and six fields.

Webhooks and this feed cover different ground. After a missed delivery the incremental feed is the first place to look, and for most changes it returns the record. It is not a complete substitute: some admin actions, and some values that are derived rather than stored, raise an event without moving the timestamp the feed filters on, so a missed delivery can fail to resurface. Reconcile against a full listing on a schedule rather than treating updated_since as complete.

A bundle variant's stock is one of those derived values — it is computed from its components rather than stored on the bundle row — so read it from available_stock on GET /variants. The stock field carries a placeholder for a bundle.

The events for customers, subscriptions, subscription charges, discounts, categories and abandoned carts have no endpoint behind them, so no polling reconciles one you never received and the loss is permanent. Those are the deliveries to alert on.

Deliveries are retried on failure, so the same event can reach you more than once. The envelope carries no event or delivery id, so make your receiver idempotent on the payload itself. What identifies a record differs by family: a product payload carries id; an order payload name and guid; a customer payload customer_email; a category payload slug; a subscription or subscription-charge payload name together with order_id; an abandoned-cart payload guid; and a discount payload name, since its code is null on an automatic discount. An inventory_updated payload carries no identifier at all — it names the variant by sku and product_name, and a SKU is not unique within a store — so treat one as a signal to re-read GET /variants?sku= rather than a value to apply directly.

An event whose retries are all exhausted is dropped, and nothing here reports that: there is no delivery-log endpoint, and a webhook's last_dispatched_at records the moment CartGenie queued the delivery, not that you received it. Alert on missing deliveries from your own side.

Rate limits

120 requests per minute and 4 per second, counted per token. Responses carry X-RateLimit-Limit and X-RateLimit-Remaining; exceeding either limit returns 429.

Errors

Failures return a JSON body with a message field. 401 means the token is missing or invalid, 403 means the token or its owner lacks the required scope or permission, 404 means the record does not exist, the store is archived, or the Public API is not enabled for the store, 422 means validation failed (with an errors object), and 409 means the request conflicts with the current state — a stale expected_stock, or an order status that does not allow the action.

Three causes share the 404, and the message tells them apart. A record that does not exist, or belongs to another store, carries Resource not found. as its message. An empty message means the Public API is not enabled for the store; every endpoint answers that way, including GET /store, so it is the first thing to check when a brand-new token gets 404 everywhere. An archived store answers 404 on every endpoint too, with This store is archived. as the message.

Requests are validated strictly: any parameter that an endpoint does not define is rejected with 422 and The <field> field is not supported. rather than ignored. Send only the documented fields — echoing an object back with extra keys will fail.

Authenticating requests

To authenticate requests, include an Authorization header with the value "Bearer {YOUR_API_TOKEN}".

All authenticated endpoints are marked with a requires authentication badge in the documentation below.

Tokens are created in the CartGenie dashboard under Settings → Public API by a team member with the Manage store settings permission.

Each token carries a fixed set of scopes and inherits the permissions of the member who created it, so a request succeeds only when the token scope and that member's store role both allow it. Revoking the token, or removing the member from the store, immediately ends access.

The Public API is enabled per store. Until it is switched on for a store, every endpoint answers 404.

Store

Get store information

requires authentication

Returns the store the token belongs to. Useful as a connection check: a 200 here means the token is valid, the Public API is enabled for the store, and the token's owner still has access to it.

A 404 with an empty message has one cause here: the Public API is not enabled for the store. Reach for the store settings before suspecting the token. An archived store answers 404 as well, carrying This store is archived. as the message.

Example request:
curl --request GET \
    --get "https://api.cartgenie.com/public/v1/store" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.cartgenie.com/public/v1/store"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.cartgenie.com/public/v1/store';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200):


{
    "name": "Example Store",
    "domain": "https://example.webflow.io",
    "currency": "usd",
    "timezone": "UTC",
    "created_at": "2025-11-02T08:31:00.000000Z"
}
 

Example response (404, Public API is not enabled for this store):


{
    "message": ""
}
 

Example response (404, Store is archived):


{
    "message": "This store is archived."
}
 

Request   

GET public/v1/store

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Products

List products

requires authentication

Returns the store's products, oldest update first. Pass updated_since to receive an incremental feed: the result then also carries the products the store has removed, so a mirrored catalog can drop them.

deleted_at is the removal signal — drop any record that carries one, whatever its status. Removal reaches the feed in two shapes. A product archived in the admin keeps the full product shape and its own status. A product removed permanently collapses to "status": "deleted" and carries only id, name, slug, status, deleted_at and updated_at — no price, stock, variants, images, url or created_at. Read deleted_at and status before any other field.

Archiving is a removal here by design: it unpublishes the product, so a shopper cannot buy it and it belongs out of any catalog you mirror from this feed. Restoring the product in the admin returns it to a later page with deleted_at back to null, carrying whatever status the restoring action set: un-archiving returns it as draft, publishing straight out of the archive returns it as published. It is buyable again only once it is published.

Reconcile against a full listing on a schedule: a permanent removal is not guaranteed to reach the feed.

Example request:
curl --request GET \
    --get "https://api.cartgenie.com/public/v1/products?limit=50&cursor=eyJpZCI6MzY0fQ&search=tee&updated_since=2026-08-01T00%3A00%3A00Z" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.cartgenie.com/public/v1/products"
);

const params = {
    "limit": "50",
    "cursor": "eyJpZCI6MzY0fQ",
    "search": "tee",
    "updated_since": "2026-08-01T00:00:00Z",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.cartgenie.com/public/v1/products';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'limit' => '50',
            'cursor' => 'eyJpZCI6MzY0fQ',
            'search' => 'tee',
            'updated_since' => '2026-08-01T00:00:00Z',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Catalog page):


{
    "data": [
        {
            "id": 364,
            "name": "Classic Tee",
            "slug": "classic-tee",
            "status": "published",
            "published": true,
            "price": 4900,
            "formatted_price": "$49.00",
            "stock": 120,
            "available_stock": 118,
            "track_inventory": true,
            "images": {
                "main_image": "https://cdn.example.com/tee.jpg",
                "main_image_large": "https://cdn.example.com/tee-large.jpg"
            },
            "url": "https://store.example.com/product/classic-tee",
            "variants": [
                {
                    "id": 2301,
                    "product_id": 364,
                    "sku": "TEE-BLK-M",
                    "title": "black-medium",
                    "published": true,
                    "price": 4900,
                    "formatted_price": "$49.00",
                    "stock": 120,
                    "reserved_stock": 2,
                    "available_stock": 118,
                    "track_inventory": true,
                    "requires_shipping": true,
                    "dimensions": {
                        "height": 2,
                        "length": 30,
                        "width": 20,
                        "weight": 180
                    },
                    "created_at": "2026-01-14T09:00:00.000000Z",
                    "updated_at": "2026-08-01T11:20:31.000000Z"
                }
            ],
            "created_at": "2026-01-14T09:00:00.000000Z",
            "deleted_at": null,
            "updated_at": "2026-08-01T11:20:31.000000Z"
        }
    ],
    "meta": {
        "per_page": 50,
        "has_more": true,
        "next_cursor": "eyJpZCI6MzY0fQ"
    }
}
 

Example response (200, Incremental feed carrying both removal shapes):


{
    "data": [
        {
            "id": 351,
            "name": "Summer Cap",
            "slug": "summer-cap",
            "status": "deleted",
            "deleted_at": "2026-08-03T14:22:09.000000Z",
            "updated_at": "2026-08-03T14:22:09.000000Z"
        },
        {
            "id": 358,
            "name": "Winter Scarf",
            "slug": "winter-scarf",
            "status": "archived",
            "published": false,
            "price": 2900,
            "formatted_price": "$29.00",
            "stock": 0,
            "available_stock": 0,
            "track_inventory": true,
            "images": {
                "main_image": "https://cdn.example.com/scarf.jpg",
                "main_image_large": "https://cdn.example.com/scarf-large.jpg"
            },
            "url": "https://store.example.com/product/winter-scarf",
            "variants": [
                {
                    "id": 2288,
                    "product_id": 358,
                    "sku": "SCARF-GRY",
                    "title": "grey",
                    "published": false,
                    "price": 2900,
                    "formatted_price": "$29.00",
                    "stock": 0,
                    "reserved_stock": 0,
                    "available_stock": 0,
                    "track_inventory": true,
                    "requires_shipping": true,
                    "dimensions": {
                        "height": 1,
                        "length": 25,
                        "width": 15,
                        "weight": 90
                    },
                    "created_at": "2026-02-02T08:12:00.000000Z",
                    "updated_at": "2026-08-04T09:15:44.000000Z"
                }
            ],
            "created_at": "2026-02-02T08:12:00.000000Z",
            "deleted_at": "2026-08-04T09:15:44.000000Z",
            "updated_at": "2026-08-04T09:15:44.000000Z"
        }
    ],
    "meta": {
        "per_page": 50,
        "has_more": false,
        "next_cursor": null
    }
}
 

Example response (422, Malformed cursor):


{
    "message": "The cursor is invalid.",
    "errors": {
        "cursor": [
            "The cursor is invalid."
        ]
    }
}
 

Request   

GET public/v1/products

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

limit   integer  optional    

Results per page, 1–250. Example: 50

cursor   string  optional    

Opaque cursor from meta.next_cursor of the previous page. Example: eyJpZCI6MzY0fQ

search   string  optional    

Matches against product name and slug. Example: tee

updated_since   string  optional    

ISO-8601 timestamp. Returns products changed or deleted since then. Example: 2026-08-01T00:00:00Z

Get a product

requires authentication

Includes the product's variants.

Example request:
curl --request GET \
    --get "https://api.cartgenie.com/public/v1/products/364" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.cartgenie.com/public/v1/products/364"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.cartgenie.com/public/v1/products/364';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200):


{
    "id": 364,
    "name": "Classic Tee",
    "slug": "classic-tee",
    "status": "published",
    "published": true,
    "price": 4900,
    "formatted_price": "$49.00",
    "stock": 120,
    "available_stock": 118,
    "track_inventory": true,
    "images": {
        "main_image": "https://cdn.example.com/tee.jpg",
        "main_image_large": "https://cdn.example.com/tee-large.jpg"
    },
    "url": "https://store.example.com/product/classic-tee",
    "variants": [
        {
            "id": 2301,
            "product_id": 364,
            "sku": "TEE-BLK-M",
            "title": "black-medium",
            "published": true,
            "price": 4900,
            "formatted_price": "$49.00",
            "stock": 120,
            "reserved_stock": 2,
            "available_stock": 118,
            "track_inventory": true,
            "requires_shipping": true,
            "dimensions": {
                "height": 2,
                "length": 30,
                "width": 20,
                "weight": 180
            },
            "created_at": "2026-01-14T09:00:00.000000Z",
            "updated_at": "2026-08-01T11:20:31.000000Z"
        }
    ],
    "created_at": "2026-01-14T09:00:00.000000Z",
    "deleted_at": null,
    "updated_at": "2026-08-01T11:20:31.000000Z"
}
 

Example response (404, Product belongs to another store or does not exist):


{
    "message": "Resource not found."
}
 

Request   

GET public/v1/products/{id}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   integer     

The product id. Example: 364

Variants

List variants

requires authentication

Returns every variant in the store, oldest update first. Filter by sku to resolve your own identifiers into variant ids. SKUs are not unique within a store, so this always returns an array — match on the fields you need rather than assuming a single result.

stock is the persisted level, reserved_stock is held by checkouts in flight, and available_stock is what a shopper can still buy.

A bundle variant holds no stock of its own: stock carries a placeholder and available_stock is derived from its components, so read that one.

Example request:
curl --request GET \
    --get "https://api.cartgenie.com/public/v1/variants?limit=50&cursor=eyJpZCI6MjMwMX0&sku=TEE-BLK-M&updated_since=2026-08-01T00%3A00%3A00Z" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.cartgenie.com/public/v1/variants"
);

const params = {
    "limit": "50",
    "cursor": "eyJpZCI6MjMwMX0",
    "sku": "TEE-BLK-M",
    "updated_since": "2026-08-01T00:00:00Z",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.cartgenie.com/public/v1/variants';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'limit' => '50',
            'cursor' => 'eyJpZCI6MjMwMX0',
            'sku' => 'TEE-BLK-M',
            'updated_since' => '2026-08-01T00:00:00Z',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200):


{
    "data": [
        {
            "id": 2301,
            "product_id": 364,
            "sku": "TEE-BLK-M",
            "title": "black-medium",
            "published": true,
            "price": 4900,
            "formatted_price": "$49.00",
            "stock": 120,
            "reserved_stock": 2,
            "available_stock": 118,
            "track_inventory": true,
            "requires_shipping": true,
            "dimensions": {
                "height": 2,
                "length": 30,
                "width": 20,
                "weight": 180
            },
            "created_at": "2026-01-14T09:00:00.000000Z",
            "updated_at": "2026-08-01T11:20:31.000000Z"
        }
    ],
    "meta": {
        "per_page": 50,
        "has_more": false,
        "next_cursor": null
    }
}
 

Request   

GET public/v1/variants

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

limit   integer  optional    

Results per page, 1–250. Example: 50

cursor   string  optional    

Opaque cursor from meta.next_cursor of the previous page. Example: eyJpZCI6MjMwMX0

sku   string  optional    

Returns every variant carrying this SKU. Example: TEE-BLK-M

updated_since   string  optional    

ISO-8601 timestamp. Example: 2026-08-01T00:00:00Z

Inventory

Set variant inventory

requires authentication

Sets the stock level to an absolute value — this is not an increment. The variant must have inventory tracking enabled and must be published.

Send expected_stock to make the write conditional: the update is applied only while the stored level still matches, and a mismatch answers 409 without changing anything. The conflict response carries actual_stock, so a retry needs no extra read.

The comparison is against the persisted stock level, not the available one, so checkouts holding reserved stock will not cause spurious conflicts.

Example request:
curl --request PUT \
    "https://api.cartgenie.com/public/v1/variants/2301/inventory" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"stock\": 120,
    \"expected_stock\": 118
}"
const url = new URL(
    "https://api.cartgenie.com/public/v1/variants/2301/inventory"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "stock": 120,
    "expected_stock": 118
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.cartgenie.com/public/v1/variants/2301/inventory';
$response = $client->put(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'stock' => 120,
            'expected_stock' => 118,
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200):


{
    "id": 2301,
    "product_id": 364,
    "sku": "TEE-BLK-M",
    "title": "black-medium",
    "published": true,
    "price": 4900,
    "formatted_price": "$49.00",
    "stock": 120,
    "reserved_stock": 2,
    "available_stock": 118,
    "track_inventory": true,
    "requires_shipping": true,
    "dimensions": {
        "height": 2,
        "length": 30,
        "width": 20,
        "weight": 180
    },
    "created_at": "2026-01-14T09:00:00.000000Z",
    "updated_at": "2026-08-14T10:05:12.000000Z"
}
 

Example response (409, expected_stock no longer matches):


{
    "message": "Inventory changed before the update could be applied.",
    "expected_stock": 118,
    "actual_stock": 120
}
 

Example response (409, Variant is not published):


{
    "message": "Variant must be published before inventory can be updated."
}
 

Example response (422, Inventory tracking is off for this variant):


{
    "message": "Inventory tracking is not enabled for this variant."
}
 

Request   

PUT public/v1/variants/{id}/inventory

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   integer     

The variant id. Example: 2301

Body Parameters

stock   integer     

The new absolute stock level. Example: 120

expected_stock   integer  optional    

The level you last read. When it no longer matches, the request fails with 409. Example: 118

Orders

List orders

requires authentication

Returns the store's orders, oldest update first. Pass updated_since for an incremental feed. Orders are never removed, so a cancelled order appears as a status change rather than a deletion.

Example request:
curl --request GET \
    --get "https://api.cartgenie.com/public/v1/orders?limit=50&cursor=eyJpZCI6MTAwMn0&updated_since=2026-08-01T00%3A00%3A00Z" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.cartgenie.com/public/v1/orders"
);

const params = {
    "limit": "50",
    "cursor": "eyJpZCI6MTAwMn0",
    "updated_since": "2026-08-01T00:00:00Z",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.cartgenie.com/public/v1/orders';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'limit' => '50',
            'cursor' => 'eyJpZCI6MTAwMn0',
            'updated_since' => '2026-08-01T00:00:00Z',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200):


{
    "data": [
        {
            "order_id": "1002",
            "name": "1002",
            "guid": "01KSNP2NFV6DJWW6MXME27Q0KE",
            "status": "unfulfilled",
            "currency_code": "usd",
            "coupon_code": null,
            "customer_email": "[email protected]",
            "customer_name": "John Doe",
            "shipment": {
                "tracking_url": null,
                "tracking_number": null,
                "provider": null,
                "address": {
                    "name": "John Doe",
                    "email": "[email protected]",
                    "phone": "+14155550123",
                    "country": "us",
                    "state": "California",
                    "region": "us-ca",
                    "region_name": "California",
                    "city": "San Francisco",
                    "address_line_one": "1 Market St",
                    "address_line_two": null,
                    "postal_code": "94105",
                    "validation_result": null
                }
            },
            "fulfillments": [],
            "billing": {
                "name": "John Doe",
                "email": "[email protected]",
                "phone": "+14155550123",
                "country": "us",
                "state": "California",
                "region": "us-ca",
                "region_name": "California",
                "city": "San Francisco",
                "address_line_one": "1 Market St",
                "address_line_two": null,
                "postal_code": "94105",
                "validation_result": null
            },
            "payment": {
                "label": "Visa ending in 4242",
                "status": "paid",
                "method": "credit_card",
                "transaction_id": "pi_3Q1example"
            },
            "details": {
                "custom_fields": {
                    "Gift Message": "Happy birthday"
                },
                "checkout_type": ""
            },
            "items": [
                {
                    "sku": "TEE-BLK-M",
                    "quantity": 1,
                    "price": 4900,
                    "name": "Classic Tee",
                    "slug": "classic-tee",
                    "url": "https://store.example.com/product/classic-tee",
                    "image_url": "https://cdn.example.com/tee.jpg",
                    "subtotal": 4900,
                    "options": [
                        {
                            "name": "Size",
                            "slug": "size",
                            "value": "M",
                            "value_slug": "m"
                        }
                    ],
                    "weight": 180,
                    "weight_unit": "g",
                    "height": 2,
                    "width": 20,
                    "length": 30,
                    "downloads": [],
                    "has_subscription": false,
                    "availability": "available",
                    "estimated_shipping_date": null,
                    "bundle_components": [],
                    "fulfilled_quantity": 0,
                    "remaining_fulfillment_quantity": 1,
                    "fulfillment_state": "unfulfilled"
                }
            ],
            "subtotal": 4900,
            "formatted_subtotal": "$49.00",
            "total": 5391,
            "formatted_total": "$53.91",
            "discount_total": 0,
            "formatted_discount_total": "$0.00",
            "shipping_total": 0,
            "formatted_shipping_total": "$0.00",
            "shipping_method_label": "Standard",
            "tax_total": 491,
            "formatted_tax_total": "$4.91",
            "created_at": "2026-08-01T10:12:00.000000Z",
            "updated_at": "2026-08-01T10:12:00.000000Z",
            "placed_at": "2026-08-01T10:12:00.000000Z",
            "fulfilled_at": null,
            "disputed_at": null,
            "discounts": []
        }
    ],
    "meta": {
        "per_page": 50,
        "has_more": true,
        "next_cursor": "eyJpZCI6MTAwMn0"
    }
}
 

Request   

GET public/v1/orders

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

limit   integer  optional    

Results per page, 1–250. Example: 50

cursor   string  optional    

Opaque cursor from meta.next_cursor of the previous page. Example: eyJpZCI6MTAwMn0

updated_since   string  optional    

ISO-8601 timestamp. Example: 2026-08-01T00:00:00Z

Get an order

requires authentication

Orders are addressed by the name the merchant sees, not by a database id.

A refunds array is present only when payment.status is refunded or partially_refunded; each entry carries id, amount, formatted_amount, quantity, status, reason and created_at. Treat its absence as "nothing refunded" rather than as an error.

Some fields are only as complete as the order is. shipment is an object when the order has at least one shippable item and an empty array [] when it has none, as on a digital-only order.

Under payment, status always carries a value and reads unpaid before the charge is captured, while method and transaction_id are null until the gateway records them. billing and payment.label are null together, and only on an order whose billing record was never written.

Example request:
curl --request GET \
    --get "https://api.cartgenie.com/public/v1/orders/1002" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.cartgenie.com/public/v1/orders/1002"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.cartgenie.com/public/v1/orders/1002';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200):


{
    "order_id": "1002",
    "name": "1002",
    "guid": "01KSNP2NFV6DJWW6MXME27Q0KE",
    "status": "unfulfilled",
    "currency_code": "usd",
    "coupon_code": null,
    "customer_email": "[email protected]",
    "customer_name": "John Doe",
    "shipment": {
        "tracking_url": null,
        "tracking_number": null,
        "provider": null,
        "address": {
            "name": "John Doe",
            "email": "[email protected]",
            "phone": "+14155550123",
            "country": "us",
            "state": "California",
            "region": "us-ca",
            "region_name": "California",
            "city": "San Francisco",
            "address_line_one": "1 Market St",
            "address_line_two": null,
            "postal_code": "94105",
            "validation_result": null
        }
    },
    "fulfillments": [],
    "billing": {
        "name": "John Doe",
        "email": "[email protected]",
        "phone": "+14155550123",
        "country": "us",
        "state": "California",
        "region": "us-ca",
        "region_name": "California",
        "city": "San Francisco",
        "address_line_one": "1 Market St",
        "address_line_two": null,
        "postal_code": "94105",
        "validation_result": null
    },
    "payment": {
        "label": "Visa ending in 4242",
        "status": "paid",
        "method": "credit_card",
        "transaction_id": "pi_3Q1example"
    },
    "details": {
        "custom_fields": {
            "Gift Message": "Happy birthday"
        },
        "checkout_type": ""
    },
    "items": [
        {
            "sku": "TEE-BLK-M",
            "quantity": 1,
            "price": 4900,
            "name": "Classic Tee",
            "slug": "classic-tee",
            "url": "https://store.example.com/product/classic-tee",
            "image_url": "https://cdn.example.com/tee.jpg",
            "subtotal": 4900,
            "options": [
                {
                    "name": "Size",
                    "slug": "size",
                    "value": "M",
                    "value_slug": "m"
                }
            ],
            "weight": 180,
            "weight_unit": "g",
            "height": 2,
            "width": 20,
            "length": 30,
            "downloads": [],
            "has_subscription": false,
            "availability": "available",
            "estimated_shipping_date": null,
            "bundle_components": [],
            "fulfilled_quantity": 0,
            "remaining_fulfillment_quantity": 1,
            "fulfillment_state": "unfulfilled"
        }
    ],
    "subtotal": 4900,
    "formatted_subtotal": "$49.00",
    "total": 5391,
    "formatted_total": "$53.91",
    "discount_total": 0,
    "formatted_discount_total": "$0.00",
    "shipping_total": 0,
    "formatted_shipping_total": "$0.00",
    "shipping_method_label": "Standard",
    "tax_total": 491,
    "formatted_tax_total": "$4.91",
    "created_at": "2026-08-01T10:12:00.000000Z",
    "updated_at": "2026-08-01T10:12:00.000000Z",
    "placed_at": "2026-08-01T10:12:00.000000Z",
    "fulfilled_at": null,
    "disputed_at": null,
    "discounts": []
}
 

Example response (404, Unknown order):


{
    "message": "Resource not found."
}
 

Request   

GET public/v1/orders/{orderName}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

orderName   string     

The order name. Example: 1002

Update tracking

requires authentication

Adds or replaces the shipping carrier and tracking details on an order. Send a field as null to clear it. The order must contain at least one shippable item, and at least one of shipping_carrier, tracking_number or tracking_url must be present.

Where the details land depends on how many fulfillments the order has. With more than one, or when the order is partially fulfilled, fulfillment_id is required and targets that fulfillment. With exactly one, omit it and the details go to that fulfillment. With none, they go to the order's shipment.

Read the tracking back from where it was written: anything stored on a fulfillment comes back under the matching entry in fulfillments[], and shipment.tracking_url, shipment.tracking_number and shipment.provider are always null once the order has any fulfillment.

Example request:
curl --request PATCH \
    "https://api.cartgenie.com/public/v1/orders/1002/tracking" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"shipping_carrier\": \"DHL\",
    \"tracking_number\": \"JD014600006281230542\",
    \"tracking_url\": \"https:\\/\\/www.dhl.com\\/track?id=JD014600006281230542\",
    \"fulfillment_id\": 91
}"
const url = new URL(
    "https://api.cartgenie.com/public/v1/orders/1002/tracking"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "shipping_carrier": "DHL",
    "tracking_number": "JD014600006281230542",
    "tracking_url": "https:\/\/www.dhl.com\/track?id=JD014600006281230542",
    "fulfillment_id": 91
};

fetch(url, {
    method: "PATCH",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.cartgenie.com/public/v1/orders/1002/tracking';
$response = $client->patch(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'shipping_carrier' => 'DHL',
            'tracking_number' => 'JD014600006281230542',
            'tracking_url' => 'https://www.dhl.com/track?id=JD014600006281230542',
            'fulfillment_id' => 91,
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200):


{
    "order_id": "1002",
    "name": "1002",
    "guid": "01KSNP2NFV6DJWW6MXME27Q0KE",
    "status": "unfulfilled",
    "currency_code": "usd",
    "coupon_code": null,
    "customer_email": "[email protected]",
    "customer_name": "John Doe",
    "shipment": {
        "provider": "DHL",
        "tracking_number": "JD014600006281230542",
        "tracking_url": "https://www.dhl.com/track?id=JD014600006281230542",
        "address": {
            "name": "John Doe",
            "email": "[email protected]",
            "phone": "+14155550123",
            "country": "us",
            "state": "California",
            "region": "us-ca",
            "region_name": "California",
            "city": "San Francisco",
            "address_line_one": "1 Market St",
            "address_line_two": null,
            "postal_code": "94105",
            "validation_result": null
        }
    },
    "fulfillments": [],
    "billing": {
        "name": "John Doe",
        "email": "[email protected]",
        "phone": "+14155550123",
        "country": "us",
        "state": "California",
        "region": "us-ca",
        "region_name": "California",
        "city": "San Francisco",
        "address_line_one": "1 Market St",
        "address_line_two": null,
        "postal_code": "94105",
        "validation_result": null
    },
    "payment": {
        "label": "Visa ending in 4242",
        "status": "paid",
        "method": "credit_card",
        "transaction_id": "pi_3Q1example"
    },
    "details": {
        "custom_fields": {
            "Gift Message": "Happy birthday"
        },
        "checkout_type": ""
    },
    "items": [
        {
            "sku": "TEE-BLK-M",
            "quantity": 1,
            "price": 4900,
            "name": "Classic Tee",
            "slug": "classic-tee",
            "url": "https://store.example.com/product/classic-tee",
            "image_url": "https://cdn.example.com/tee.jpg",
            "subtotal": 4900,
            "options": [
                {
                    "name": "Size",
                    "slug": "size",
                    "value": "M",
                    "value_slug": "m"
                }
            ],
            "weight": 180,
            "weight_unit": "g",
            "height": 2,
            "width": 20,
            "length": 30,
            "downloads": [],
            "has_subscription": false,
            "availability": "available",
            "estimated_shipping_date": null,
            "bundle_components": [],
            "fulfilled_quantity": 0,
            "remaining_fulfillment_quantity": 1,
            "fulfillment_state": "unfulfilled"
        }
    ],
    "subtotal": 4900,
    "formatted_subtotal": "$49.00",
    "total": 5391,
    "formatted_total": "$53.91",
    "discount_total": 0,
    "formatted_discount_total": "$0.00",
    "shipping_total": 0,
    "formatted_shipping_total": "$0.00",
    "shipping_method_label": "Standard",
    "tax_total": 491,
    "formatted_tax_total": "$4.91",
    "created_at": "2026-08-01T10:12:00.000000Z",
    "updated_at": "2026-08-14T10:05:12.000000Z",
    "placed_at": "2026-08-01T10:12:00.000000Z",
    "fulfilled_at": null,
    "disputed_at": null,
    "discounts": []
}
 

Example response (422, Nothing to ship):


{
    "message": "Order has no shippable items."
}
 

Example response (422, No tracking field sent):


{
    "message": "At least one supported field is required.",
    "errors": {
        "request": [
            "At least one supported field is required."
        ]
    }
}
 

Example response (422, fulfillment_id omitted on a multi-fulfillment order):


{
    "message": "The fulfillment id field is required.",
    "errors": {
        "fulfillment_id": [
            "The fulfillment id field is required."
        ]
    }
}
 

Example response (422, Fulfillment belongs to another order):


{
    "message": "The fulfillment_id does not belong to this order.",
    "errors": {
        "fulfillment_id": [
            "The fulfillment_id does not belong to this order."
        ]
    }
}
 

Request   

PATCH public/v1/orders/{orderName}/tracking

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

orderName   string     

The order name. Example: 1002

Body Parameters

shipping_carrier   string  optional    

The carrier name shown to the customer. Example: DHL

tracking_number   string  optional    

Example: JD014600006281230542

tracking_url   string  optional    

Must be a valid URL. Example: https://www.dhl.com/track?id=JD014600006281230542

fulfillment_id   integer  optional    

Required when the order has multiple fulfillments or is partially fulfilled. Example: 91

Fulfill an order

requires authentication

Marks the whole order fulfilled and, when send_notification is true, emails the customer. Tracking details sent here are stored alongside the fulfillment, so a shipment can be recorded in one call.

Only whole-order fulfillment is supported; item-level fulfillment is not part of this version.

Each fulfillments[].items[].order_item_id is an internal identifier for the line it covers; it is not repeated in items[], which carries no id. A line covering a bundle carries a fourth key, components[], whose entries hold live_product_variant_id, name and quantity for each bundled variant.

Example request:
curl --request POST \
    "https://api.cartgenie.com/public/v1/orders/1002/fulfill" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"shipping_carrier\": \"DHL\",
    \"tracking_number\": \"JD014600006281230542\",
    \"tracking_url\": \"https:\\/\\/www.dhl.com\\/track?id=JD014600006281230542\",
    \"send_notification\": true
}"
const url = new URL(
    "https://api.cartgenie.com/public/v1/orders/1002/fulfill"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "shipping_carrier": "DHL",
    "tracking_number": "JD014600006281230542",
    "tracking_url": "https:\/\/www.dhl.com\/track?id=JD014600006281230542",
    "send_notification": true
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.cartgenie.com/public/v1/orders/1002/fulfill';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'shipping_carrier' => 'DHL',
            'tracking_number' => 'JD014600006281230542',
            'tracking_url' => 'https://www.dhl.com/track?id=JD014600006281230542',
            'send_notification' => true,
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200):


{
    "order_id": "1002",
    "name": "1002",
    "guid": "01KSNP2NFV6DJWW6MXME27Q0KE",
    "status": "fulfilled",
    "currency_code": "usd",
    "coupon_code": null,
    "customer_email": "[email protected]",
    "customer_name": "John Doe",
    "shipment": {
        "tracking_url": null,
        "tracking_number": null,
        "provider": null,
        "address": {
            "name": "John Doe",
            "email": "[email protected]",
            "phone": "+14155550123",
            "country": "us",
            "state": "California",
            "region": "us-ca",
            "region_name": "California",
            "city": "San Francisco",
            "address_line_one": "1 Market St",
            "address_line_two": null,
            "postal_code": "94105",
            "validation_result": null
        }
    },
    "fulfillments": [
        {
            "id": 91,
            "provider": "DHL",
            "tracking_number": "JD014600006281230542",
            "tracking_url": "https://www.dhl.com/track?id=JD014600006281230542",
            "fulfilled_at": "2026-08-14T10:05:12.000000Z",
            "items": [
                {
                    "order_item_id": 5501,
                    "name": "Classic Tee",
                    "quantity": 1
                }
            ]
        }
    ],
    "billing": {
        "name": "John Doe",
        "email": "[email protected]",
        "phone": "+14155550123",
        "country": "us",
        "state": "California",
        "region": "us-ca",
        "region_name": "California",
        "city": "San Francisco",
        "address_line_one": "1 Market St",
        "address_line_two": null,
        "postal_code": "94105",
        "validation_result": null
    },
    "payment": {
        "label": "Visa ending in 4242",
        "status": "paid",
        "method": "credit_card",
        "transaction_id": "pi_3Q1example"
    },
    "details": {
        "custom_fields": {
            "Gift Message": "Happy birthday"
        },
        "checkout_type": ""
    },
    "items": [
        {
            "sku": "TEE-BLK-M",
            "quantity": 1,
            "price": 4900,
            "name": "Classic Tee",
            "slug": "classic-tee",
            "url": "https://store.example.com/product/classic-tee",
            "image_url": "https://cdn.example.com/tee.jpg",
            "subtotal": 4900,
            "options": [
                {
                    "name": "Size",
                    "slug": "size",
                    "value": "M",
                    "value_slug": "m"
                }
            ],
            "weight": 180,
            "weight_unit": "g",
            "height": 2,
            "width": 20,
            "length": 30,
            "downloads": [],
            "has_subscription": false,
            "availability": "available",
            "estimated_shipping_date": null,
            "bundle_components": [],
            "fulfilled_quantity": 1,
            "remaining_fulfillment_quantity": 0,
            "fulfillment_state": "fulfilled"
        }
    ],
    "subtotal": 4900,
    "formatted_subtotal": "$49.00",
    "total": 5391,
    "formatted_total": "$53.91",
    "discount_total": 0,
    "formatted_discount_total": "$0.00",
    "shipping_total": 0,
    "formatted_shipping_total": "$0.00",
    "shipping_method_label": "Standard",
    "tax_total": 491,
    "formatted_tax_total": "$4.91",
    "created_at": "2026-08-01T10:12:00.000000Z",
    "updated_at": "2026-08-14T10:05:12.000000Z",
    "placed_at": "2026-08-01T10:12:00.000000Z",
    "fulfilled_at": "2026-08-14T10:05:12.000000Z",
    "disputed_at": null,
    "discounts": []
}
 

Example response (409, Order is in a status that cannot be fulfilled):


{
    "message": "Order cannot be fulfilled from status canceled."
}
 

Request   

POST public/v1/orders/{orderName}/fulfill

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

orderName   string     

The order name. Example: 1002

Body Parameters

shipping_carrier   string  optional    

Example: DHL

tracking_number   string  optional    

Example: JD014600006281230542

tracking_url   string  optional    

Example: https://www.dhl.com/track?id=JD014600006281230542

send_notification   boolean  optional    

Emails the customer that the order shipped. Defaults to false. Example: true

Unfulfill an order

requires authentication

Returns a fulfilled or partially fulfilled order to the unfulfilled state. Tracking stored on the order's shipment is kept; tracking recorded on a fulfillment is removed with the fulfillment.

Example request:
curl --request POST \
    "https://api.cartgenie.com/public/v1/orders/1002/unfulfill" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.cartgenie.com/public/v1/orders/1002/unfulfill"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.cartgenie.com/public/v1/orders/1002/unfulfill';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200):


{
    "order_id": "1002",
    "name": "1002",
    "guid": "01KSNP2NFV6DJWW6MXME27Q0KE",
    "status": "unfulfilled",
    "currency_code": "usd",
    "coupon_code": null,
    "customer_email": "[email protected]",
    "customer_name": "John Doe",
    "shipment": {
        "provider": "DHL",
        "tracking_number": "JD014600006281230542",
        "tracking_url": "https://www.dhl.com/track?id=JD014600006281230542",
        "address": {
            "name": "John Doe",
            "email": "[email protected]",
            "phone": "+14155550123",
            "country": "us",
            "state": "California",
            "region": "us-ca",
            "region_name": "California",
            "city": "San Francisco",
            "address_line_one": "1 Market St",
            "address_line_two": null,
            "postal_code": "94105",
            "validation_result": null
        }
    },
    "fulfillments": [],
    "billing": {
        "name": "John Doe",
        "email": "[email protected]",
        "phone": "+14155550123",
        "country": "us",
        "state": "California",
        "region": "us-ca",
        "region_name": "California",
        "city": "San Francisco",
        "address_line_one": "1 Market St",
        "address_line_two": null,
        "postal_code": "94105",
        "validation_result": null
    },
    "payment": {
        "label": "Visa ending in 4242",
        "status": "paid",
        "method": "credit_card",
        "transaction_id": "pi_3Q1example"
    },
    "details": {
        "custom_fields": {
            "Gift Message": "Happy birthday"
        },
        "checkout_type": ""
    },
    "items": [
        {
            "sku": "TEE-BLK-M",
            "quantity": 1,
            "price": 4900,
            "name": "Classic Tee",
            "slug": "classic-tee",
            "url": "https://store.example.com/product/classic-tee",
            "image_url": "https://cdn.example.com/tee.jpg",
            "subtotal": 4900,
            "options": [
                {
                    "name": "Size",
                    "slug": "size",
                    "value": "M",
                    "value_slug": "m"
                }
            ],
            "weight": 180,
            "weight_unit": "g",
            "height": 2,
            "width": 20,
            "length": 30,
            "downloads": [],
            "has_subscription": false,
            "availability": "available",
            "estimated_shipping_date": null,
            "bundle_components": [],
            "fulfilled_quantity": 0,
            "remaining_fulfillment_quantity": 1,
            "fulfillment_state": "unfulfilled"
        }
    ],
    "subtotal": 4900,
    "formatted_subtotal": "$49.00",
    "total": 5391,
    "formatted_total": "$53.91",
    "discount_total": 0,
    "formatted_discount_total": "$0.00",
    "shipping_total": 0,
    "formatted_shipping_total": "$0.00",
    "shipping_method_label": "Standard",
    "tax_total": 491,
    "formatted_tax_total": "$4.91",
    "created_at": "2026-08-01T10:12:00.000000Z",
    "updated_at": "2026-08-14T10:41:03.000000Z",
    "placed_at": "2026-08-01T10:12:00.000000Z",
    "fulfilled_at": null,
    "disputed_at": null,
    "discounts": []
}
 

Example response (409, Order was never fulfilled):


{
    "message": "Order cannot be unfulfilled from status unfulfilled."
}
 

Request   

POST public/v1/orders/{orderName}/unfulfill

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

orderName   string     

The order name. Example: 1002

Cancel an order

requires authentication

Moves the order to the canceled status and sends the usual cancellation event and email.

This is a status change only. It does not refund the payment, return stock to inventory, remove fulfillments, or cancel subscriptions created by the order — handle those separately if your flow needs them.

Example request:
curl --request POST \
    "https://api.cartgenie.com/public/v1/orders/1002/cancel" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.cartgenie.com/public/v1/orders/1002/cancel"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.cartgenie.com/public/v1/orders/1002/cancel';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200):


{
    "order_id": "1002",
    "name": "1002",
    "guid": "01KSNP2NFV6DJWW6MXME27Q0KE",
    "status": "canceled",
    "currency_code": "usd",
    "coupon_code": null,
    "customer_email": "[email protected]",
    "customer_name": "John Doe",
    "shipment": {
        "tracking_url": null,
        "tracking_number": null,
        "provider": null,
        "address": {
            "name": "John Doe",
            "email": "[email protected]",
            "phone": "+14155550123",
            "country": "us",
            "state": "California",
            "region": "us-ca",
            "region_name": "California",
            "city": "San Francisco",
            "address_line_one": "1 Market St",
            "address_line_two": null,
            "postal_code": "94105",
            "validation_result": null
        }
    },
    "fulfillments": [],
    "billing": {
        "name": "John Doe",
        "email": "[email protected]",
        "phone": "+14155550123",
        "country": "us",
        "state": "California",
        "region": "us-ca",
        "region_name": "California",
        "city": "San Francisco",
        "address_line_one": "1 Market St",
        "address_line_two": null,
        "postal_code": "94105",
        "validation_result": null
    },
    "payment": {
        "label": "Visa ending in 4242",
        "status": "paid",
        "method": "credit_card",
        "transaction_id": "pi_3Q1example"
    },
    "details": {
        "custom_fields": {
            "Gift Message": "Happy birthday"
        },
        "checkout_type": ""
    },
    "items": [
        {
            "sku": "TEE-BLK-M",
            "quantity": 1,
            "price": 4900,
            "name": "Classic Tee",
            "slug": "classic-tee",
            "url": "https://store.example.com/product/classic-tee",
            "image_url": "https://cdn.example.com/tee.jpg",
            "subtotal": 4900,
            "options": [
                {
                    "name": "Size",
                    "slug": "size",
                    "value": "M",
                    "value_slug": "m"
                }
            ],
            "weight": 180,
            "weight_unit": "g",
            "height": 2,
            "width": 20,
            "length": 30,
            "downloads": [],
            "has_subscription": false,
            "availability": "available",
            "estimated_shipping_date": null,
            "bundle_components": [],
            "fulfilled_quantity": 0,
            "remaining_fulfillment_quantity": 1,
            "fulfillment_state": "unfulfilled"
        }
    ],
    "subtotal": 4900,
    "formatted_subtotal": "$49.00",
    "total": 5391,
    "formatted_total": "$53.91",
    "discount_total": 0,
    "formatted_discount_total": "$0.00",
    "shipping_total": 0,
    "formatted_shipping_total": "$0.00",
    "shipping_method_label": "Standard",
    "tax_total": 491,
    "formatted_tax_total": "$4.91",
    "created_at": "2026-08-01T10:12:00.000000Z",
    "updated_at": "2026-08-14T11:02:47.000000Z",
    "placed_at": "2026-08-01T10:12:00.000000Z",
    "fulfilled_at": null,
    "disputed_at": null,
    "discounts": []
}
 

Example response (409, Order already reached a final status):


{
    "message": "Order cannot be canceled from status canceled."
}
 

Request   

POST public/v1/orders/{orderName}/cancel

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

orderName   string     

The order name. Example: 1002

Webhooks

List webhook subscriptions

requires authentication

Example request:
curl --request GET \
    --get "https://api.cartgenie.com/public/v1/webhooks?limit=50&cursor=eyJpZCI6N30" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.cartgenie.com/public/v1/webhooks"
);

const params = {
    "limit": "50",
    "cursor": "eyJpZCI6N30",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.cartgenie.com/public/v1/webhooks';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'limit' => '50',
            'cursor' => 'eyJpZCI6N30',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200):


{
    "data": [
        {
            "id": 7,
            "name": "Order sync",
            "url": "https://example.com/hooks/cartgenie",
            "description": null,
            "enabled": true,
            "events": [
                "new_order",
                "order_updated"
            ],
            "last_dispatched_at": "2026-08-14T09:12:00.000000Z",
            "created_at": "2026-07-01T12:00:00.000000Z",
            "updated_at": "2026-08-14T09:12:00.000000Z"
        }
    ],
    "meta": {
        "per_page": 50,
        "has_more": false,
        "next_cursor": null
    }
}
 

Request   

GET public/v1/webhooks

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

limit   integer  optional    

Results per page, 1–250. Example: 50

cursor   string  optional    

Opaque cursor from meta.next_cursor of the previous page. Example: eyJpZCI6N30

Get a webhook subscription

requires authentication

Example request:
curl --request GET \
    --get "https://api.cartgenie.com/public/v1/webhooks/7" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.cartgenie.com/public/v1/webhooks/7"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.cartgenie.com/public/v1/webhooks/7';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200):


{
    "id": 7,
    "name": "Order sync",
    "url": "https://example.com/hooks/cartgenie",
    "description": null,
    "enabled": true,
    "events": [
        "new_order",
        "order_updated"
    ],
    "last_dispatched_at": "2026-08-14T09:12:00.000000Z",
    "created_at": "2026-07-01T12:00:00.000000Z",
    "updated_at": "2026-08-14T09:12:00.000000Z"
}
 

Example response (404, Subscription belongs to another store or does not exist):


{
    "message": "Resource not found."
}
 

Request   

GET public/v1/webhooks/{webhook}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

webhook   integer     

The subscription id. Example: 7

Create a webhook subscription

requires authentication

Registers a URL to receive the events you list. The URL must be a publicly reachable HTTPS endpoint — private and loopback addresses are rejected.

Receiving deliveries

The request body is {"type": "<event>", "payload": { ... }}. Every delivery carries a Signature header holding an HMAC-SHA256 of the raw JSON body, keyed with your store's webhook secret from Settings → Webhooks. Compute the same digest over the body you received and compare before trusting it.

A delivery is attempted up to three times with exponential backoff and a 10 second timeout, so your endpoint should answer quickly and be safe to call twice with the same event.

Available events

new_order, order_updated, order_fulfillment_updated, new_customer, customer_updated, new_subscription, subscription_updated, new_subscription_charge, new_discount, discount_updated, new_category, category_updated, new_product, product_updated, new_abandoned_cart, refund_issued, inventory_updated.

Example request:
curl --request POST \
    "https://api.cartgenie.com/public/v1/webhooks" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Order sync\",
    \"url\": \"https:\\/\\/example.com\\/hooks\\/cartgenie\",
    \"description\": \"Feeds our ERP\",
    \"enabled\": true,
    \"events\": [
        \"new_order\",
        \"order_updated\"
    ]
}"
const url = new URL(
    "https://api.cartgenie.com/public/v1/webhooks"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Order sync",
    "url": "https:\/\/example.com\/hooks\/cartgenie",
    "description": "Feeds our ERP",
    "enabled": true,
    "events": [
        "new_order",
        "order_updated"
    ]
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.cartgenie.com/public/v1/webhooks';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'name' => 'Order sync',
            'url' => 'https://example.com/hooks/cartgenie',
            'description' => 'Feeds our ERP',
            'enabled' => true,
            'events' => ['new_order', 'order_updated'],
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (201):


{
    "id": 7,
    "name": "Order sync",
    "url": "https://example.com/hooks/cartgenie",
    "description": null,
    "enabled": true,
    "events": [
        "new_order",
        "order_updated"
    ],
    "last_dispatched_at": null,
    "created_at": "2026-08-14T09:12:00.000000Z",
    "updated_at": "2026-08-14T09:12:00.000000Z"
}
 

Example response (422, Endpoint is not publicly reachable over HTTPS):


{
    "message": "The url must be a publicly reachable HTTPS URL.",
    "errors": {
        "url": [
            "The url must be a publicly reachable HTTPS URL."
        ]
    }
}
 

Request   

POST public/v1/webhooks

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

name   string     

A label for this subscription. Example: Order sync

url   string     

Publicly reachable HTTPS endpoint. Example: https://example.com/hooks/cartgenie

description   string  optional    

Free-form note, up to 10000 characters. Example: Feeds our ERP

enabled   boolean  optional    

Defaults to true. Example: true

events   string[]     

One or more event names from the list above.

Update a webhook subscription

requires authentication

Only the fields you send are changed. Sending events replaces the whole list rather than adding to it.

Example request:
curl --request PATCH \
    "https://api.cartgenie.com/public/v1/webhooks/7" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Order sync\",
    \"url\": \"https:\\/\\/example.com\\/hooks\\/cartgenie\",
    \"description\": \"Feeds our ERP\",
    \"enabled\": false,
    \"events\": [
        \"new_order\"
    ]
}"
const url = new URL(
    "https://api.cartgenie.com/public/v1/webhooks/7"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Order sync",
    "url": "https:\/\/example.com\/hooks\/cartgenie",
    "description": "Feeds our ERP",
    "enabled": false,
    "events": [
        "new_order"
    ]
};

fetch(url, {
    method: "PATCH",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.cartgenie.com/public/v1/webhooks/7';
$response = $client->patch(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'name' => 'Order sync',
            'url' => 'https://example.com/hooks/cartgenie',
            'description' => 'Feeds our ERP',
            'enabled' => false,
            'events' => ['new_order'],
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200):


{
    "id": 7,
    "name": "Order sync",
    "url": "https://example.com/hooks/cartgenie",
    "description": null,
    "enabled": false,
    "events": [
        "new_order"
    ],
    "last_dispatched_at": "2026-08-14T09:12:00.000000Z",
    "created_at": "2026-07-01T12:00:00.000000Z",
    "updated_at": "2026-08-15T08:30:00.000000Z"
}
 

Example response (422, Endpoint is not publicly reachable over HTTPS):


{
    "message": "The url must be a publicly reachable HTTPS URL.",
    "errors": {
        "url": [
            "The url must be a publicly reachable HTTPS URL."
        ]
    }
}
 

Request   

PATCH public/v1/webhooks/{webhook}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

webhook   integer     

The subscription id. Example: 7

Body Parameters

name   string  optional    

Example: Order sync

url   string  optional    

Publicly reachable HTTPS endpoint. Example: https://example.com/hooks/cartgenie

description   string  optional    

Example: Feeds our ERP

enabled   boolean  optional    

Pause deliveries without deleting the subscription. Example: false

events   string[]  optional    

Replaces the subscribed events.

Delete a webhook subscription

requires authentication

Example request:
curl --request DELETE \
    "https://api.cartgenie.com/public/v1/webhooks/7" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.cartgenie.com/public/v1/webhooks/7"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.cartgenie.com/public/v1/webhooks/7';
$response = $client->delete(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (204):

Empty response
 

Request   

DELETE public/v1/webhooks/{webhook}

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

webhook   integer     

The subscription id. Example: 7

Send a test delivery

requires authentication

Queues one delivery of the chosen event to this subscription, carrying representative sample data rather than a real record. Use it to verify your endpoint and your signature check before going live.

The event must already be one this subscription listens for. The destination is re-validated at this point, so a URL that has since become unreachable is rejected here rather than failing silently later.

Example request:
curl --request POST \
    "https://api.cartgenie.com/public/v1/webhooks/7/test" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"event\": \"new_order\"
}"
const url = new URL(
    "https://api.cartgenie.com/public/v1/webhooks/7/test"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "event": "new_order"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://api.cartgenie.com/public/v1/webhooks/7/test';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_TOKEN}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'event' => 'new_order',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (202):


{
    "queued": true,
    "event": "new_order"
}
 

Example response (422, Subscription does not listen for that event):


{
    "message": "Event is not configured for this webhook."
}
 

Example response (422, Destination is no longer reachable):


{
    "message": "Webhook destination must be a publicly reachable HTTPS URL."
}
 

Request   

POST public/v1/webhooks/{webhook}/test

Headers

Authorization        

Example: Bearer {YOUR_API_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

webhook   integer     

The subscription id. Example: 7

Body Parameters

event   string     

An event this subscription is subscribed to. Example: new_order