Skip to content

Endpoints v26.7+

Auth

Method Endpoint Auth Description
POST /auth/token None Get JWT token (grant_type: customer/client_credentials/api_user)
POST /auth/refresh Bearer JWT Refresh JWT token (current token sent via Authorization header)
POST /auth/logout Bearer JWT Revoke the current token

"Current customer" and password reset are part of the Customer resource, see Customers.


Store Configuration

Method Endpoint Auth Description
GET /store-config None Get store configuration for the current store
GET /{storeCode}/config None Get store configuration for a specific store code
GET /stores None List all active stores and websites
GET /stores/currencies None List allowed currencies
POST /stores/switch/{storeCode} None Switch store context

Country listings live under Directory (/countries).


Products & Catalog

Method Endpoint Auth Description
GET /products None List products (paginated)
GET /products/{id} None Get product by ID
POST /products Admin/API Create product
PUT /products/{id} Admin/API Update product
DELETE /products/{id} Admin/API Delete product

Sub-resources (parent path parameter is {productId} throughout):

Method Endpoint Auth Description
GET / POST / PUT / DELETE /products/{productId}/media GET=None, writes=Admin/API Product images
GET / POST / DELETE /products/{productId}/tier-prices Admin/API Tier pricing (POST replaces, DELETE clears all)
GET / POST /products/{productId}/custom-options GET=None, POST=Admin/API Custom options
PUT / DELETE /products/{productId}/custom-options/{id} Admin/API Update/remove a custom option
GET /custom-option-file/{optionId}/{key} None Download a customer-uploaded option file
GET / POST / PUT / DELETE /products/{productId}/bundle-options GET=None, writes=Admin/API Bundle product options
PUT / DELETE /products/{productId}/bundle-options/{id} Admin/API Update/remove a bundle option
GET / PUT /products/{productId}/configurable GET=None, PUT=Admin/API Read super-attributes + child IDs / set them all
POST /products/{productId}/configurable/children Admin/API Add a child product (body: {childId: int})
DELETE /products/{productId}/configurable/children/{childId} Admin/API Remove a child product
GET / POST / PUT / DELETE /products/{productId}/downloadable-links GET=None, writes=Admin/API Downloadable links
GET / POST / PUT /products/{productId}/grouped GET=None, writes=Admin/API Grouped product links (PUT replaces all)
DELETE /products/{productId}/grouped/{childProductId} Admin/API Remove a grouped child
GET / POST / PUT /products/{productId}/links/related GET=None, writes=Admin/API Related products (POST adds one, PUT replaces all)
DELETE /products/{productId}/links/related/{linkedProductId} Admin/API Remove a related link
GET / POST / PUT /products/{productId}/links/cross-sell GET=None, writes=Admin/API Cross-sell links (POST adds one, PUT replaces all)
DELETE /products/{productId}/links/cross-sell/{linkedProductId} Admin/API Remove a cross-sell link
GET / POST / PUT /products/{productId}/links/up-sell GET=None, writes=Admin/API Up-sell links (POST adds one, PUT replaces all)
DELETE /products/{productId}/links/up-sell/{linkedProductId} Admin/API Remove an up-sell link
GET / POST /products/{productId}/reviews GET=None, POST=Customer Reviews for a product

Layered navigation:

Method Endpoint Auth Description
GET /layered-filters None Get layered navigation filters

Categories

Method Endpoint Auth Description
GET /categories None List categories (tree)
GET /categories/{id} None Get category by ID
POST /categories Admin/API Create category
PUT /categories/{id} Admin/API Update category
DELETE /categories/{id} Admin/API Delete category

Cart (Authenticated)

Method Endpoint Auth Description
GET /carts/{id} Customer/Admin/API Get a cart by numeric ID (ownership enforced via verifyCartAccess())
POST /carts Customer/Admin/API Create a new cart for the authenticated customer
POST /carts/{id}/items Customer/Admin/API Add item to cart
PUT /carts/{id}/items/{itemId} Customer/Admin/API Update cart item quantity
DELETE /carts/{id}/items/{itemId} Customer/Admin/API Remove cart item

Guest Cart

{id} is the masked cart ID returned by POST /guest-carts.

Method Endpoint Auth Description
POST /guest-carts None Create a guest cart
GET /guest-carts/{id} None Get a guest cart by masked ID
POST /guest-carts/{id}/items None Add item to cart
PUT /guest-carts/{id}/items/{itemId} None Update cart item quantity
DELETE /guest-carts/{id}/items/{itemId} None Remove cart item
GET /guest-carts/{id}/totals None Get cart totals
POST /guest-carts/{id}/coupon None Apply coupon code
DELETE /guest-carts/{id}/coupon None Remove coupon
POST /guest-carts/{id}/giftcards None Apply gift card
DELETE /guest-carts/{id}/giftcards/{code} None Remove gift card
POST /guest-carts/{id}/shipping-methods None Get available shipping methods
GET /guest-carts/{id}/payment-methods None Get available payment methods
POST /guest-carts/{id}/place-order None Place order from guest cart

Customers

Method Endpoint Auth Description
GET /customers Admin/API List customers
GET /customers/{id} Customer/Admin/API Get customer by ID
POST /customers None Register a customer
PUT /customers/me Customer/API Update current customer profile
POST /customers/me/password Customer/API Change password
GET /customers/me Customer/API Get current authenticated customer
GET /customers/me/orders Customer/API List own orders
GET /customers/me/reviews Customer/API List own reviews
POST /customers/forgot-password None Request password reset email
POST /customers/reset-password None Reset password with token
POST /customers/create-from-order None Create a customer account from a placed guest order

Addresses (Address resource, same DTO is exposed under three URL families):

Method Endpoint Auth Description
GET /customers/me/addresses Customer/API List own addresses
POST /customers/me/addresses Customer/API Create an address for the current customer
GET /customers/me/addresses/{id} Customer/API Get one of the current customer's addresses
PUT /customers/me/addresses/{id} Customer/API Update an address
DELETE /customers/me/addresses/{id} Customer/API Delete an address
GET /addresses Customer/API List own addresses (alias of /customers/me/addresses)
POST /addresses Customer/API Create an address
GET /addresses/{id} Customer/API Get an address by ID
PUT /addresses/{id} Customer/API Update an address
DELETE /addresses/{id} Customer/API Delete an address
GET /customers/{customerId}/addresses Admin/API List a customer's addresses
POST /customers/{customerId}/addresses Admin/API Create an address for a customer

Orders

Method Endpoint Auth Description
GET /orders Admin/API List orders (paginated)
GET /orders/{id} Customer/Admin/API Get order by ID (customers see only their own)
GET /orders/{incrementId}/details Order token Read a guest order via the per-order one-time X-Order-Token header
POST /orders None Place an order from a customer or guest cart

Guest order lookup (GET /orders/{incrementId}/details): a public, unauthenticated endpoint for rendering order-confirmation views in headless/guest checkouts. The per-order token is passed in the X-Order-Token header (never the query string, which would leak into access logs). The token is single-use: it's cleared on the first successful read, so refreshing the page won't replay the lookup. The endpoint is IP rate-limited to prevent brute-forcing a token against a known increment ID. A missing/invalid token or unknown increment ID returns 404. If no customer account exists for the order's email, the response includes an accountToken the frontend can use with POST /customers/create-from-order.

curl /api/rest/v2/orders/100000123/details \
  -H 'X-Order-Token: <one-time-token>'

The GraphQL counterpart is the guestOrder(incrementId, accessToken) query (same one-time-use semantics).

Order sub-resources:

Method Endpoint Auth Description
GET /orders/{orderId}/shipments Admin/API List shipments for order
POST /orders/{orderId}/shipments Admin/API Create shipment
GET /orders/{orderId}/credit-memos Admin/API List credit memos for order
POST /orders/{orderId}/credit-memos Admin/API Create credit memo/refund
GET /orders/{orderId}/invoices Customer/Admin/API List invoices for order
GET /orders/{orderId}/invoices/{id}/pdf Customer/Admin/API Download invoice PDF

Customer invoice access:

Method Endpoint Auth Description
GET /customers/me/orders/{orderId}/invoices Customer/API List own invoices
GET /customers/me/orders/{orderId}/invoices/{id}/pdf Customer/API Download own invoice PDF

Shipments

Method Endpoint Auth Description
GET /shipments/{id} Admin/API Get shipment by ID

Create shipment:

curl -X POST /api/rest/v2/orders/123/shipments \
  -H 'Authorization: Bearer eyJ...' \
  -H 'Content-Type: application/json' \
  -d '{
    "items": [{"orderItemId": 456, "qty": 2}],
    "tracks": [{"carrierCode": "auspost", "title": "Australia Post", "trackNumber": "AP123456"}],
    "comment": "Shipped via express",
    "notifyCustomer": true
  }'

Omit items to ship every remaining item on the order. Each track entry needs at least trackNumber; carrierCode defaults to custom and title defaults to the carrier code.


Credit Memos / Refunds

Method Endpoint Auth Description
GET /credit-memos/{id} Admin/API Get credit memo by ID
GET /orders/{orderId}/credit-memos Admin/API List credit memos for order
POST /orders/{orderId}/credit-memos Admin/API Create credit memo

Create a credit memo:

curl -X POST /api/rest/v2/orders/123/credit-memos \
  -H 'Authorization: Bearer eyJ...' \
  -H 'Content-Type: application/json' \
  -d '{
    "items": [
      {"orderItemId": 456, "qty": 1, "backToStock": true}
    ],
    "comment": "Customer returned item",
    "adjustmentPositive": 5.00,
    "adjustmentNegative": 0,
    "offlineRefund": true
  }'

Response:

{
  "id": 789,
  "incrementId": "100000001",
  "orderId": 123,
  "orderIncrementId": "100000456",
  "state": "refunded",
  "grandTotal": 29.95,
  "baseGrandTotal": 29.95,
  "subtotal": 24.95,
  "taxAmount": 0,
  "shippingAmount": 0,
  "discountAmount": 0,
  "adjustmentPositive": 5.00,
  "adjustmentNegative": 0,
  "items": [
    {
      "id": 101,
      "orderItemId": 456,
      "sku": "TENNIS-BALL-3PK",
      "name": "Tennis Balls (3 pack)",
      "qty": 1,
      "price": 24.95,
      "rowTotal": 24.95,
      "taxAmount": 0,
      "discountAmount": 0,
      "backToStock": true
    }
  ],
  "comment": "Customer returned item"
}

Parameters: - items[], Array of items to refund. Each requires orderItemId and qty. Optional: backToStock (boolean, returns qty to inventory). - comment, Optional refund note. - adjustmentPositive, Additional positive adjustment (add to refund). - adjustmentNegative, Negative adjustment (reduce refund). - offlineRefund, true (default) for offline refund, false to trigger payment gateway refund.

GraphQL:

mutation {
  createCreditMemo(input: {
    orderId: 123
    items: [{orderItemId: 456, qty: 1, backToStock: true}]
    comment: "Returned"
    offlineRefund: true
  }) {
    id
    incrementId
    state
    grandTotal
  }
}


Invoices

Method Endpoint Auth Description
GET /orders/{orderId}/invoices Customer/Admin/API List invoices for order
GET /orders/{orderId}/invoices/{id}/pdf Customer/Admin/API Download invoice PDF
GET /customers/me/orders/{orderId}/invoices Customer/API List own invoices
GET /customers/me/orders/{orderId}/invoices/{id}/pdf Customer/API Download own invoice PDF

There is no standalone collection endpoint or write endpoint for invoices, they are produced as part of the order workflow.


Inventory / Stock Updates

Fast direct-SQL stock updates, no model overhead, no observers.

Method Endpoint Auth Description
PUT /inventory Admin/API Update single SKU stock
PUT /inventory/bulk Admin/API Bulk update (max 100 items)

Single update:

curl -X PUT /api/rest/v2/inventory \
  -H 'Authorization: Bearer eyJ...' \
  -H 'Content-Type: application/json' \
  -d '{
    "sku": "TENNIS-BALL-3PK",
    "qty": 150,
    "isInStock": true,
    "manageStock": true
  }'

Response:

{
  "sku": "TENNIS-BALL-3PK",
  "qty": 150,
  "isInStock": true,
  "manageStock": true,
  "previousQty": 42,
  "success": true
}

Bulk update:

curl -X PUT /api/rest/v2/inventory/bulk \
  -H 'Authorization: Bearer eyJ...' \
  -H 'Content-Type: application/json' \
  -d '{
    "items": [
      {"sku": "TENNIS-BALL-3PK", "qty": 150},
      {"sku": "RACQUET-PRO-V2", "qty": 25, "isInStock": true},
      {"sku": "GRIP-TAPE-WHT", "qty": 0}
    ]
  }'

Notes: - isInStock auto-sets to qty > 0 if not provided. - manageStock defaults to true if not provided. - Qty must be 0–99,999,999. - Bulk limit: 100 items per request (validated upfront, executed in a DB transaction).

GraphQL:

mutation {
  updateStock(input: {sku: "TENNIS-BALL-3PK", qty: 150}) {
    sku
    qty
    previousQty
    success
  }
}

mutation {
  updateStockBulk(input: {
    items: [
      {sku: "TENNIS-BALL-3PK", qty: 150},
      {sku: "RACQUET-PRO-V2", qty: 25}
    ]
  }) {
    success
    results {
      sku
      qty
      previousQty
    }
  }
}


Coupons / Price Rules

Full CRUD for coupon/discount rule management + validation.

Method Endpoint Auth Description
GET /coupons Admin/API List coupons (paginated, filterable)
GET /coupons/{id} Admin/API Get coupon by ID
POST /coupons Admin/API Create coupon + rule
PUT /coupons/{id} Admin/API Update coupon/rule
DELETE /coupons/{id} Admin/API Delete coupon + rule
POST /coupons/validate None Validate a coupon code (public, used by storefront checkouts)

Create a coupon:

curl -X POST /api/rest/v2/coupons \
  -H 'Authorization: Bearer eyJ...' \
  -H 'Content-Type: application/json' \
  -d '{
    "code": "SUMMER25",
    "discountType": "percent",
    "discountAmount": 25,
    "description": "Summer sale 25% off",
    "isActive": true,
    "usageLimit": 500,
    "usagePerCustomer": 1,
    "fromDate": "2026-01-01",
    "toDate": "2026-03-31",
    "minimumSubtotal": 50
  }'

Discount types: | API Value | Maho Action | Description | |-----------|-------------|-------------| | percent | by_percent | Percentage off each item | | fixed | by_fixed | Fixed amount off each item | | cart_fixed | cart_fixed | Fixed amount off cart total | | buy_x_get_y | buy_x_get_y | Buy X get Y free |

Validate a coupon:

curl -X POST /api/rest/v2/coupons/validate \
  -H 'Authorization: Bearer eyJ...' \
  -H 'Content-Type: application/json' \
  -d '{"code": "SUMMER25"}'

Response:

{
  "id": 0,
  "code": "SUMMER25",
  "isValid": true,
  "validationMessage": "Coupon is valid",
  "discountType": "percent",
  "discountAmount": 25,
  "ruleName": "Summer sale 25% off"
}

Collection filters:

GET /api/rest/v2/coupons?code=SUMMER          # Filter by code (LIKE search)
GET /api/rest/v2/coupons?isActive=true        # Filter by active status
GET /api/rest/v2/coupons?page=2&itemsPerPage=50

GraphQL:

mutation {
  createCoupon(input: {
    code: "SUMMER25"
    discountType: "percent"
    discountAmount: 25
    isActive: true
  }) {
    id
    code
    ruleId
  }
}

mutation {
  validateCoupon(input: {code: "SUMMER25"}) {
    isValid
    validationMessage
    discountType
    discountAmount
  }
}


Gift Cards

Method Endpoint Auth Description
GET /giftcards/{id} Admin/API Get a gift card by ID
POST /giftcards Admin/API Create a new gift card

Balance lookups and adjustments are exposed via GraphQL only (checkGiftcardBalance, adjustGiftcardBalance).


CMS Content

Method Endpoint Auth Description
GET /cms-pages None List CMS pages
GET /cms-pages/{id} None Get CMS page
POST /cms-pages Admin/API Create CMS page
PUT /cms-pages/{id} Admin/API Update CMS page
DELETE /cms-pages/{id} Admin/API Delete CMS page
GET /cms-blocks None List CMS blocks
GET /cms-blocks/{id} None Get CMS block
POST /cms-blocks Admin/API Create CMS block
PUT /cms-blocks/{id} Admin/API Update CMS block
DELETE /cms-blocks/{id} Admin/API Delete CMS block

Blog

Method Endpoint Auth Description
GET /blog-posts None List blog posts
GET /blog-posts/{id} None Get blog post
POST /blog-posts Admin/API Create blog post
PUT /blog-posts/{id} Admin/API Update blog post
DELETE /blog-posts/{id} Admin/API Delete blog post
GET /blog-categories None List blog categories
GET /blog-categories/{id} None Get blog category

Media

Method Endpoint Auth Description
GET /media Admin/API List image files in a folder under wysiwyg/
POST /media Admin/API Upload an image (multipart/form-data; auto-converted to the configured format)
DELETE /media/{path} Admin/API Delete a media file (path must be inside wysiwyg/)

Reviews

Method Endpoint Auth Description
GET /reviews/{id} None Get review by ID
GET /products/{productId}/reviews None List reviews for product
POST /products/{productId}/reviews Customer/API Submit a review (requires authentication)
GET /customers/me/reviews Customer/API List own reviews

Revocation (EU)

Contract revocation declarations under EU Directive 2023/2673 (the "revocation button"). The API exposes the authenticated channel: a logged-in customer revokes one of their own orders, and admins list and process the declarations. The public, unauthenticated web form remains at /revocation and is not part of the API.

Method Endpoint Auth Description
POST /customers/me/revocation-requests Customer/API Submit a revocation against your own order
GET /customers/me/revocation-requests Customer/API List your own declarations
GET /revocation-requests Admin/API List all declarations
GET /revocation-requests/{id} Customer/Admin/API Get one declaration (own request for customers, any for admins)
PUT /revocation-requests/{id} Admin/API Set the processing status and internal note

Submit body:

curl -X POST /api/rest/v2/customers/me/revocation-requests \
  -H 'Authorization: Bearer <customer_jwt>' \
  -H 'Content-Type: application/json' \
  -d '{"orderId": 1234, "reason": "I changed my mind"}'
- orderId (int) or orderReference (order increment ID) identifies the order; one is required. - Ownership is re-checked server-side: an order that isn't the authenticated customer's returns 404. - Because the customer is authenticated and owns the order, the recorded declaration is verified (verified: true), the same trust level as the my-account web link. The declaration row is the legal receipt and is always written, even if the receipt/notification emails are suppressed. - The submission is gated by the store's cooling-off window; an order past it returns 422. - Disabled revocation (revocation/general/enabled = 0) returns 404.

Response fields: id, orderId, orderReference, reason, customerName, email, verified, storeId, receivedAt, processedStatus, processedAt, suppressedAt, suppressedReason. The internal-only fields adminNote, ip, and userAgent are returned only to admins.

Admin processing:

curl -X PUT /api/rest/v2/revocation-requests/1234 \
  -H 'Authorization: Bearer <admin_jwt>' \
  -H 'Content-Type: application/json' \
  -d '{"processedStatus": "accepted", "adminNote": "Refund issued"}'
- processedStatus must be one of accepted, rejected, info_requested; anything else returns 422. Setting it stamps processedAt.

GraphQL: myRevocationRequests (customer's own declarations), submitRevocation(orderId / orderReference, reason) mutation, plus the standard revocationRequest item / collection queries.


Newsletter

Method Endpoint Auth Description
POST /newsletter/subscribe None Subscribe to newsletter (gated by newsletter/subscription/allow_guest_subscribe)
POST /newsletter/unsubscribe None Unsubscribe by email
GET /newsletter/status Customer/API Get subscription status

Guest subscription control: Guest (unauthenticated) subscribe is controlled by the Maho config flag newsletter/subscription/allow_guest_subscribe (System > Config > Newsletter > Subscription Options > Allow Guest Subscription). When disabled, only authenticated customers can subscribe. Recommended: set to No for API use to prevent abuse.

Confirmation emails: When newsletter/subscription/confirm is enabled, new subscriptions receive a confirmation email and remain inactive until confirmed (double opt-in).


Contact

Method Endpoint Auth Description
POST /contact None Submit contact form
GET /contact/config None Get contact form config

GET /contact/config response example:

{
  "id": "contact",
  "enabled": true,
  "captchaProvider": "turnstile",
  "captchaSiteKey": "0x4AAA...",
  "honeypotField": "_h_a4b2c1d3"
}

captchaProvider is one of none, turnstile, recaptcha_v3 (or anything an installed third-party module registers). captchaSiteKey is null when the provider is none. Frontends use these two fields to load the matching widget client-side; for richer per-provider config the helper-based event flow described under CAPTCHA is used instead.

The honeypotField value is deterministic per install (derived from the encryption key) and opaque to the frontend, render it as a hidden input and don't expose its value, e.g. <input type="text" name="{honeypotField}" style="display:none" tabindex="-1" autocomplete="off" />. If a request body arrives with a non-empty value in that field, the API silently treats it as spam (returns success without sending the email). When honeypot is disabled in admin, honeypotField is null and the frontend can skip it.


Directory

Method Endpoint Auth Description
GET /countries None List countries
GET /countries/{id} None Get country (with regions)

Wishlist

Method Endpoint Auth Description
GET /customers/me/wishlist Customer/API Get wishlist items
POST /customers/me/wishlist Customer/API Add to wishlist
DELETE /customers/me/wishlist/{id} Customer/API Remove from wishlist
POST /customers/me/wishlist/{id}/move-to-cart Customer/API Move item to cart
POST /customers/me/wishlist/sync Customer/API Sync a guest (localStorage) wishlist into the customer's wishlist

URL Resolver

Method Endpoint Auth Description
GET /url-resolver?path=/some-page None Resolve URL to entity