Client API quickstart

The client API lets a Shopify app open and update conversations for a merchant. Route requests through your app’s backend so the API key remains secret and your server can verify which Shopify shop is making the request.

Before you start

  1. Ask a space owner to create a key in Space → Settings → API keys.
  2. Save the full key in your backend’s secret store. It is shown only once.
  3. Point your app at the API base URL, https://api.wishsupport.app/v1.
  4. From your backend, derive the current shop domain from an authenticated Shopify session.
  5. Ask the space owner to set your app’s App slug under Settings → Notifications, so the emails merchants receive about replies carry a button back into your app.

Set these values in your server environment (do not commit a real key):

WISHSUPPORT_API_BASE=https://api.wishsupport.app/v1
WISHSUPPORT_API_KEY=<key copied from the space settings>

Never put the API key in browser JavaScript, a storefront theme, a query string or source control. The API key identifies the support space; X-Shop-Domain scopes each request to a shop inside it.

Security requirement: Set X-Shop-Domain on your server from a verified Shopify session. Do not trust a domain submitted by the browser. The API validates the domain format but cannot prove that the caller owns that shop.

Required headers

Every /v1/client/** request needs both headers:

Authorization: Bearer <WISHSUPPORT_API_KEY>
X-Shop-Domain: acme.myshopify.com

The shop domain must be the authenticated merchant’s canonical .myshopify.com domain.

Create a conversation

The POST /client/tickets endpoint creates a ticket and its first message in one request. The following example uses placeholder data; replace it with values from your authenticated app session.

curl -X POST "$WISHSUPPORT_API_BASE/client/tickets" \
  -H "Authorization: Bearer $WISHSUPPORT_API_KEY" \
  -H "X-Shop-Domain: acme.myshopify.com" \
  -H "Content-Type: application/json" \
  -d '{
    "shopName": "Acme Supplies",
    "requesterName": "Alex Merchant",
    "requesterEmail": "merchant@example.com",
    "subject": "Question about my order",
    "message": "Could you help me check the status of this issue?"
  }'

The response contains a ticket.code and the first message. Save the ticket code; use it to load the thread, send replies and update status. A message or at least one attachment is required to open a ticket.

List this shop’s conversations

curl "$WISHSUPPORT_API_BASE/client/tickets?status=open&limit=25" \
  -H "Authorization: Bearer $WISHSUPPORT_API_KEY" \
  -H "X-Shop-Domain: acme.myshopify.com"

The response is { "items": [...], "nextCursor": 1036 }. Pass a non-null nextCursor as cursor to load the next page; it is null on the last page. limit accepts 1–100 and defaults to 25. Available status values are open, closed and archived; omitting status returns non-archived tickets. Add search to match ticket codes, subjects and message text, or unread=1 to show conversations the shop has not read.

Read a thread and reply

Load messages for a ticket:

curl "$WISHSUPPORT_API_BASE/client/tickets/TCK-A7K2P9X/messages" \
  -H "Authorization: Bearer $WISHSUPPORT_API_KEY" \
  -H "X-Shop-Domain: acme.myshopify.com"

Send a merchant reply:

curl -X POST "$WISHSUPPORT_API_BASE/client/tickets/TCK-A7K2P9X/messages" \
  -H "Authorization: Bearer $WISHSUPPORT_API_KEY" \
  -H "X-Shop-Domain: acme.myshopify.com" \
  -H "Content-Type: application/json" \
  -d '{"body":"Thanks, here is the additional information."}'

A closed ticket returns 409 TICKET_CLOSED; reopen it before sending. Sending a message to an archived ticket reopens it automatically. To reopen or close a ticket yourself, call PATCH /client/tickets/:code with { "status": "open" } or { "status": "closed" }.

Mark a thread read

Call this when the merchant actually opens a conversation, not when your app prefetches it:

curl -X POST "$WISHSUPPORT_API_BASE/client/tickets/TCK-A7K2P9X/read" \
  -H "Authorization: Bearer $WISHSUPPORT_API_KEY" \
  -H "X-Shop-Domain: acme.myshopify.com"

It clears unread on the ticket and shows your support team that their replies were seen. It also matters for email: a support reply the merchant has not seen for 5 minutes is emailed to requesterEmail, so an app that never marks threads read sends an email after every reply.

Upload attachments

Upload a file as multipart/form-data to /client/attachments. The response is an upload record. Pass that record unchanged in the attachments array when creating a ticket or sending a message. Include ?ticketCode=<code> for a file attached to an existing ticket.

Uploads allow one file per request, up to 10 MB (images up to 5 MB). Up to four attachments can be added to one message. Supported types: PNG, JPEG, GIF, WebP, PDF, plain text, CSV, ZIP, GZIP, TAR and 7z. Do not treat attachment URLs as access-controlled; they are public URLs.

Handle errors

All API errors use the same shape:

{
  "statusCode": 409,
  "code": "TICKET_CLOSED",
  "message": "This ticket is closed.",
  "requestId": "request-id"
}

Branch on the stable code, not the human-readable message. Useful cases:

Code What to do
UNAUTHORIZED Check whether the key is missing, invalid or revoked. Replace a revoked key.
VALIDATION_ERROR Inspect details for the invalid field; also check the required shop header.
NOT_FOUND Confirm the ticket belongs to the authenticated shop.
TICKET_CLOSED Reopen before sending a message.
ATTACHMENT_TOO_LARGE / ATTACHMENT_TYPE_REJECTED Check the upload size and accepted types.
RATE_LIMIT_EXCEEDED Back off and retry with jitter.

When asking the support team to investigate a server error, include the response’s requestId.

Optional: live updates

REST works for all client API operations. For near-real-time updates, the API also offers a WebSocket. A browser must never receive the API key; have your backend request a short-lived socket ticket from POST /client/socket-tickets, then give that ticket to the browser connection. The ticket expires after 60 seconds and can be used once. See the Full API reference for the socket frames, reconnect catch-up and event list.

Next

The Full API reference lists every route with its body, response and a copyable example.

Esc