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
- Ask a space owner to create a key in Space → Settings → API keys.
- Save the full key in your backend’s secret store. It is shown only once.
- Point your app at the API base URL,
https://api.wishsupport.app/v1. - From your backend, derive the current shop domain from an authenticated Shopify session.
- 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-Domainon 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.