From product selection to a paid invoice

Use an integration environment with a configured store, an account with the required roles, and a product the account can access. The examples assume Bash and curl. Set MERCHI_API to the environment’s HTTPS origin without a trailing slash. Replace the sample IDs with records from that environment. Use a product without required options for the basic estimate example, or include the options your customer selected.

For storefront purchases, use the cart checkout workflow. The steps below cover a staff integration that estimates work, creates a job, and issues a quote or invoice.

Sign in and check the session

POST /v6/store-session/ accepts HTTP Basic credentials and the form field storeName. Use the store’s subdomain identifier for storeName. Supply remember=true if your integration requires a remembered session. With just the email passed to curl’s --user, curl prompts for the password.

curl --fail-with-body --user "$MERCHI_EMAIL" \
  --data-urlencode "storeName=$MERCHI_STORE" \
  "$MERCHI_API/v6/store-session/"

A successful login returns HTTP 201 with session.token. Save it securely as MERCHI_SESSION_TOKEN for subsequent calls.

{"session":{"token":"<session-token>"}}

Integrations that have a domain API secret can use POST /v6/sessions/ with Basic user credentials and the form field api_secret. That endpoint uses the secret to authenticate the domain during login. Keep credentials in your server-side integration. Use the returned session token on later calls.

Send the session token in the session_token cookie. To inspect the session, call GET /v6/sessions/<token>/ with its own token and URL-encode the path segment as needed. Treat that path as sensitive. Use DELETE on the same session resource to revoke an accessible session when finished. Rejected credentials or a store that cannot be resolved can produce 403. An expired or archived session requires signing in again. A session carries the user’s existing permissions, and using the correct store does not grant staff rights.

Session routes · Login examples in test helpers

Read a product and its options

Use GET /v6/products/?q=shirt&limit=10 to find a product, then use its ID to load the fields and options for the order form.

curl --fail-with-body --get "$MERCHI_API/v6/products/123/" \
  --cookie "session_token=$MERCHI_SESSION_TOKEN" \
  --data-urlencode 'embed={"independentVariationFields":{"options":{}},"groupVariationFields":{"options":{}}}'

Read product.id, product.independentVariationFields, and product.groupVariationFields from the 200 response. Independent fields apply to the order and grouped fields describe choices repeated across groups. Keep the field IDs with the selected values. Visibility, allowed values and required fields depend on the product. An invalid embed can produce 400, and a product outside the caller’s access can produce an authorization or lookup error.

Product access and routes · Variation relationships

Estimate the job

POST /v6/specialised-order-estimate/ accepts the product relationship and quantity as form fields. For products with options, include the applicable variations. A standalone text selection uses fields such as variations-count=1, variations-0-variationField-count=1, variations-0-variationField-0-id and variations-0-value. A grouped selection nests the equivalent fields under variationsGroups-0- and supplies the group quantity. Use the selected option ID for a select field where the product expects an ID.

curl --fail-with-body "$MERCHI_API/v6/specialised-order-estimate/" \
  --cookie "session_token=$MERCHI_SESSION_TOKEN" \
  --data-urlencode 'product-count=1' \
  --data-urlencode 'product-0-id=123' \
  --data-urlencode 'quantity=100'

HTTP 201 returns a simulated job directly. The response contains calculated costs and can include variations, variationsGroups and matchingInventories. Use the recalculated values to display an estimate. Save the job in a separate request after the customer or operator agrees to proceed. Keep the original configuration and quantity so the saved job can be checked against the estimate.

Estimate handler and status · Worked grouped and independent variation test

Upload a file and create a job

If the job needs artwork, upload the file first with multipart form data. The value of fileDataIndex names the multipart field holding the file. A missing matching part produces HTTP 400. Read file.id from the 201 response.

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

With a staff session that can create jobs for the selected domain and client, send a form to POST /v6/jobs/. The example includes a manager and an uploaded client file. Supply the same variation inputs used in the estimate if the product requires them. Add any address, deadline or workflow fields required by the job.

curl --fail-with-body "$MERCHI_API/v6/jobs/" \
  --cookie "session_token=$MERCHI_SESSION_TOKEN" \
  --data-urlencode 'domain-count=1' --data-urlencode 'domain-0-id=1' \
  --data-urlencode 'client-count=1' --data-urlencode 'client-0-id=22' \
  --data-urlencode 'manager-count=1' --data-urlencode 'manager-0-id=9' \
  --data-urlencode 'product-count=1' --data-urlencode 'product-0-id=123' \
  --data-urlencode 'clientFiles-count=1' --data-urlencode 'clientFiles-0-id=451' \
  --data-urlencode 'quantity=100'

HTTP 201 wraps the persisted record under job. Keep job.id for the invoice, and use GET /v6/jobs/<id>/ to verify its product, quantity and pricing before billing. You can request those relationships using embed. A 403 can indicate a role or relationship restriction. Resolve it through the account’s permissions or record selection before retrying.

Upload field parser · Job request used in integration tests

Create and approve a quote

POST /v6/invoices/ accepts form data and returns an invoice on HTTP 201. Use domain-count and domain-0-id, client-count and client-0-id, and jobs-count and jobs-0-id to identify the domain, billable client and job. Supply the job’s invoice line information, such as jobs-0-taxType-count, jobs-0-taxType-0-id, jobs-0-quantity and jobs-0-cost, as needed for the selected billing workflow. Additional lines use items-count and indexed fields such as items-0-description, items-0-quantity and items-0-cost. Resolve the tax, billing and payment settings before issuing the document.

This example creates a quote for an existing job. Set MERCHI_JOB_ID, MERCHI_JOB_QUANTITY, MERCHI_JOB_COST and MERCHI_TAX_TYPE_ID from the reviewed job and billing settings.

curl --fail-with-body "$MERCHI_API/v6/invoices/" \
  --cookie "session_token=$MERCHI_SESSION_TOKEN" \
  --data-urlencode 'domain-count=1' --data-urlencode 'domain-0-id=1' \
  --data-urlencode 'client-count=1' --data-urlencode 'client-0-id=22' \
  --data-urlencode 'jobs-count=1' --data-urlencode "jobs-0-id=$MERCHI_JOB_ID" \
  --data-urlencode "jobs-0-quantity=$MERCHI_JOB_QUANTITY" \
  --data-urlencode "jobs-0-cost=$MERCHI_JOB_COST" \
  --data-urlencode 'jobs-0-taxType-count=1' \
  --data-urlencode "jobs-0-taxType-0-id=$MERCHI_TAX_TYPE_ID" \
  --data-urlencode 'isQuote=true'

Set isQuote=true when the customer must approve the quote before paying. The customer responds with POST /v6/invoices/<id>/approve_quote/ using an authorized session or that invoice’s scoped token. The token is accepted in the form body.

curl --fail-with-body "$MERCHI_API/v6/invoices/456/approve_quote/" \
  --data-urlencode "invoice_token=$MERCHI_INVOICE_TOKEN"

HTTP 200 returns invoice and paymentUrl. Approval makes the quote payable. Offer the URL to the authorized customer when it is present. Declining uses POST /v6/invoices/<id>/decline_quote/ with an optional form field reason. An invalid or ineligible quote response may return 404. Re-read the invoice after a timeout so you can distinguish a failed request from an approval that already succeeded.

Invoice creation fixture with job and line fields · Token-based approval and rejection tests

Reconcile payment against the invoice

Use the Stripe workflow with kind=invoice and the returned invoice ID. Check the payment mode first. For wallet payments, keep the same attempt ID through confirmation and refresh. Use recorded and fullyPaid to distinguish payment recording from settlement of the entire balance.

After the payment is recorded, re-read the invoice with computed balance fields embedded.

curl --fail-with-body --get "$MERCHI_API/v6/invoices/456/" \
  --cookie "session_token=$MERCHI_SESSION_TOKEN" \
  --data-urlencode 'embed={"owedMoney":{},"paidMoney":{},"isCompletelyPaid":{}}'

For a caller with access to the computed fields, the 200 response wraps them under invoice. Use isCompletelyPaid to confirm settlement, and owedMoney to show the remaining balance after a partial payment. Treat an absent field as unavailable and check the caller’s permissions or payment status response before marking the invoice paid.

For a PDF copy, GET /v6/invoices/<id>/generate/pdf/ returns pdf as Base64. Pass as_file=1 to receive a PDF attachment instead. Both use the invoice access check.

Invoice balance serialization · Invoice PDF handler