Integration workflows

For staff integrations, the API quickstart walks through sign-in, product configuration, job creation and invoicing.

These examples use protocol v6. Substitute IDs, credentials and hostnames from your integration environment. JSON examples show the fields the client needs for the next step. Entity responses can include additional fields depending on access and embeds.

Standard entity writes use form data. For example, cart-0-id references an existing cart, and images-0-id references the first file in a draft’s images. Include the relationship count, such as cart-count=1 or images-count=1, when constructing the form manually. Action endpoints specify their own encoding. See the REST guide for session handling and endpoint reference for methods and paths.

Build a cart and select shipping

  1. Create a cart with POST /v6/carts/. Send the domain reference as domain-0-id. An authenticated user may also supply a permitted client reference. Use the returned cart.id and the cart’s scoped token when operating as the cart holder. Test checkout uses isTest at creation. Changing that flag on an existing cart does not switch its payment environment.
  2. Add a product using POST /v6/cart_items/. Send cart-0-id, product-0-id and quantity, plus any required variations. The cart token must belong to the referenced cart. On success, read cartItem from the 201 response. Update quantities with PATCH /v6/cart_items/<id>/ and remove items with DELETE on the same path.
  3. Set the receiver address on the cart before quoting. Use PATCH /v6/carts/<id>/ with receiver address form fields such as receiverAddress-0-country and receiverAddress-0-postcode, along with the address fields needed by the shipment method.
  4. Call GET /v6/generate-cart-shipment-quotes/<cart_id>/ after changing the address or items. The action replaces existing shipment groups and defaults each group with quotes to its cheapest quote. The 200 response is the cart object itself. Read shipmentGroups[].quotes and shipmentGroups[].selectedQuote. A group can have no selected quote. Discard stale quote IDs after regeneration and confirm the selections before starting payment.
  5. Check discounts with GET /v6/carts/<id>/check_discount_code/?codes=CODE1,CODE2. The response includes items, and an unknown code returns 404. Use the server’s checkout totals to determine the amount owed.

Example item creation with a scoped token in the form body

curl --fail-with-body --request POST "$MERCHI_API/v6/cart_items/" \
  --data-urlencode "cart_token=$MERCHI_CART_TOKEN" \
  --data-urlencode 'cart-count=1' \
  --data-urlencode 'cart-0-id=42' \
  --data-urlencode 'product-count=1' \
  --data-urlencode 'product-0-id=123' \
  --data-urlencode 'quantity=25'
{"cartItem":{"id":84,"quantity":25}}

Cart and shipment quote handlers · Cart item token tests

Start, resume and reconcile Stripe payments

Use the owning cart or invoice ID. The server authorizes access through that resource and its applicable session or scoped token. A provider ID alone cannot authorize an API request.

Step Request Fields to read
Choose flow GET /v6/payments/stripe/<kind>/<id>/mode/ with optional attemptId engine is wallet or legacy. An existing attemptId can be returned. This call leaves the cart invoice untouched.
Read options GET /v6/payments/stripe/<kind>/<id>/options/ methods, currency, amountMinor, minorUnitFactor, publishableKey, liveMode. For a cart without an invoice, this can return 404.
Start wallet attempt POST /v6/payments/stripe/<kind>/<id>/attempts/ with a JSON object id, status, recorded, invoiceId, amountMinor, currency, remainingAmountMinor, fullyPaid, and sometimes stripeClientSecret. Starting a cart attempt can create or replace its checkout invoice.
Refresh wallet attempt GET or POST /v6/payments/stripe/<kind>/<id>/attempts/<attempt_id>/ Current attempt and recording status. The response includes invoice when the attempt has a payment record.
Cancel wallet attempt DELETE /v6/payments/stripe/<kind>/<id>/attempts/<attempt_id>/ Current attempt state after cancellation handling. Refresh if the outcome is unclear.

Use kind=cart or kind=invoice. For a new wallet attempt, keep a stable requestKey for that payment action. It can be up to 128 characters. Supply an integer amountMinor if your integration is explicitly paying an amount supported by the invoice. Omit it to use the server’s outstanding amount. An invoice-scoped key reused with a different amount or currency can return 409.

{"requestKey":"checkout-42-payment-1"}

Send the request with Content-Type: application/json. If the server supplies stripeClientSecret, pass it to the customer-facing Stripe integration together with the publishable key. Keep the secret out of logs and URLs. After confirmation or a redirect, refresh the same attempt. Use recorded to determine whether Merchi has recorded the payment and fullyPaid to determine whether any balance remains.

Example refresh after a successful full payment

{
  "id": "example-attempt-id",
  "provider": "stripe",
  "status": "succeeded",
  "recorded": true,
  "paymentId": 801,
  "invoiceId": 456,
  "amountMinor": 2500,
  "currency": "aud",
  "remainingAmountMinor": 0,
  "fullyPaid": true
}

The zero-balance path returns id: "zero-balance", recorded: true, paymentId: null, fullyPaid: true and an invoice. It needs neither Stripe confirmation nor a follow-up request for a stored attempt. If a request times out, check the attempt state or retry with the original key and unchanged amount. Avoid changing the cart while a payment is pending. Completed checkout and pending payments can restrict cart edits.

Refund a recorded payment

Use POST /v6/payments/<payment_id>/refund/ with a staff session authorized to edit that payment. For a tracked Stripe payment, send a JSON object with an optional amountMinor and requestKey. Use the same key for a retry of the same refund. The response wraps the updated record as payment. Older payments use the legacy refund path. A pending provider refund does not update the invoice balance until the refund succeeds. Refresh the invoice before relying on its balance.

Refund route · Pending refund test

Legacy Stripe and test checkout

When mode reports legacy, use GET /v6/stripe/payment_intent/cart/<id>/ or GET /v6/stripe/payment_intent/invoice/<id>/. These routes can create a Stripe intent despite using GET. A normal response supplies stripeClientSecret, amount in minor units and currency. A zero-balance cart can return zeroBalance: true instead.

After successful Stripe confirmation, call GET /v6/stripe/payment_intent/cart/complete/<id>/ or the invoice equivalent. Read the returned invoice. Test carts use the legacy flow with test keys when configured. The test-only POST /v6/carts/<id>/complete_test_payment/ path is available for eligible test carts without configured test Stripe keys. The server rejects live carts and carts with test keys configured.

Payment routes · Attempt response and retry logic · Legacy routes · Test checkout cases

Upload and review a draft

Use a session with access to the job. First upload the image with POST /v6/files/ as multipart form data. Send fileDataIndex=1 and attach the binary file as part 1. Read file.id from the result. Let the HTTP library generate the multipart boundary.

curl --fail-with-body --request POST "$MERCHI_API/v6/files/" \
  --cookie "session_token=$MERCHI_SESSION_TOKEN" \
  --form 'fileDataIndex=1' \
  --form '1=@draft.png;type=image/png'

Create the draft with POST /v6/drafts/ using job-0-id and images-0-id, then read draft.id from the 201 response. For notification behavior, the tested create flow supports sendEmail=on and sendSms=on. Fetch the result with GET /v6/drafts/<id>/?embed={"images":{}} using URL encoding for the embed value.

To request changes, a client with access can send a form to POST /v6/draft_comments/ with draft-0-id, text and changeRequest=true. Read the draftComment from the 201 response. The server updates the job’s design state. Refresh the draft and job after edits, comments or approvals. Authorization is field-specific, so a successful PATCH can leave a field unchanged. The integration tests cover a client attempting to replace draft images and a manager successfully making that edit.

To approve a draft, an authorized caller sends PATCH /v6/drafts/<id>/ with accepted set to the approval time as a Unix timestamp in seconds. Fetch that draft again and confirm that its accepted value is set.

The optional POST /v6/drafts/generate/initial/ action takes JSON with jobId and optional prompt, images, selectedDraftTemplates and useVariations. It returns an uploaded file. Create the draft separately if you want that file attached to the job. Resending a draft is an explicit side effect of GET /v6/drafts/<id>/resend/, which returns {"message":"Draft resent"}.

Upload and draft creation examples in test helpers · Draft access and change request tests · Draft actions

Quote, book and track a shipment

A product estimate, cart quote and booked consignment are different objects. Use the action that matches the stage of the order.

Action Request and response
Estimate for a product POST /v6/products/<id>/shipment_options/ accepts form quantity (defaults to 1) and nested address-0-* fields. Returns shipments, each wrapped under shipment, sorted by total cost.
Quote a staff shipment POST /v6/shipments/shipment_service/quote/ takes form relationships for shipmentMethod, receiverAddress and jobs. Missing relationships produce 400. Returns items containing quote shipment objects.
Suggest parcels GET /v6/shipments/<id>/carrier_parcels/ returns parcels, included, excluded and carrierTestMode. Review excluded job lines before booking.
Book POST /v6/shipments/<id>/shipment_service/book/ accepts JSON parcels or a form parameter containing the JSON array.
Lodge or void POST /v6/shipments/<id>/shipment_service/lodge/ lodges a booked shipment. POST /v6/shipments/<id>/shipment_service/void/ voids a booking before lodging.
Refresh POST /v6/shipments/<id>/carrier_shipment/refresh/ refreshes a booked consignment from the carrier.
Get documents GET /v6/shipments/<id>/sticker/generate/?format=shipping returns Base64 PDF in sticker with format and label. Also supports square and wide. GET /v6/shipments/<id>/packing_list/generate/ returns Base64 PDF in packingList with HTTP 201.

Example JSON for a booking with manually entered parcel dimensions

{"parcels":[{"weightKg":2.5,"lengthCm":30,"widthCm":20,"heightCm":15}]}

Each dimension must be positive. Booking requires an editable shipment, a supported configured service, domestic Australian addresses and valid sender and receiver details. A pickup cannot be booked with the carrier. Read carrierShipmentId, carrierOrderId, trackingNumber, shipmentLabelId, status and carrierTestMode from the action response. Compare carrierCost and carrierCurrency separately from the customer-facing cost. After an ambiguous response, check the shipment before issuing another booking or lodgement.

Shipment routes and response shapes · Booking state and carrier validation · Parcel inputs

Receive Stripe webhook events

Configure Stripe to deliver directly to the Merchi URL issued for the relevant account and mode. These routes accept Stripe-signed deliveries.

Destination Request
Company-specific POST /v6/payments/stripe/webhooks/<endpoint_id>/
Platform Connect POST /v6/payments/stripe/webhooks/platform/test/ or /platform/live/

Send the original request bytes and Stripe-Signature header. Re-serializing the event JSON changes the bytes used to verify the signature. Company delivery checks the account and mode against its configured endpoint. Platform delivery uses the matching mode secret and routes supported payment-intent and refund events to enrolled accounts.

A valid delivery returns {"received":true}. Merchi stores applicable events durably and deduplicates deliveries before a worker reconciles them. Platform deliveries for irrelevant accounts, modes or event types may be acknowledged without storing an event. Invalid signatures return 400, an unknown endpoint or mode returns 404, and a missing platform secret returns 503. Use the payment-attempt response to confirm that a payment was recorded and the invoice balance is settled. A webhook acknowledgement only reports delivery handling.

Webhook entry points · Durability, account routing and deduplication tests