Overview
Base URL, authentication, pagination, and errors for the Context API.
The Context API reads tickets, their messages, orders, and customers, polls the Needs reply feed, and asks for a mail check of your mailboxes. It returns JSON and never sends email or changes merchant orders. The operation pages are generated from its OpenAPI document, so they list exactly the fields the API returns.
Three ways in
Each operation is one procedure, served three ways with the same inputs, outputs, and permission checks.
| Surface | Address | For |
|---|---|---|
| REST | https://api.meetwillow.cx/api/v1 | Integrations with an API key. |
| MCP | https://api.meetwillow.cx/api/v1/mcp | Assistants. See MCP tools. |
| tRPC | https://api.meetwillow.cx/api/trpc/<procedure> | EcomSquad’s own apps, such as OneInbox, with a member’s sign-in. |
Operations
| Procedure | REST | MCP tool | Permission |
|---|---|---|---|
context.ticket.list | GET /tickets | list_tickets | context:read |
context.ticket.get | GET /tickets/{ticketId} | get_ticket | context:read |
context.ticket.messages | GET /tickets/{ticketId}/messages | get_ticket_messages | context:read |
context.ticket.refresh | POST /tickets/{ticketId}/refresh | refresh_ticket | context:refresh |
context.order.get | GET /orders | get_order | context:read |
context.order.lookup | POST /orders/lookup | None | context:lookup, API key only |
context.order.lookupByEmail | POST /orders/lookup-by-email | None | context:lookupByEmail, API key only |
context.customer.get | GET /customers | get_customer | context:read |
context.inbox.changes | GET /inbox/changes | None | inbox:read |
context.inbox.refresh | POST /inbox/refresh | refresh_inbox | inbox:refresh |
context.inbox.refreshStatus | GET /inbox/refresh | get_inbox_refresh | inbox:refresh |
Reads return stored data and never contact a provider. Only a
refresh may call the store, and only a
mailbox Refresh asks for a mail check. A key or
sign-in given context:refresh also gets context:read, because a refresh
returns the ticket; a key given inbox:refresh also gets inbox:read. The
Needs reply feed and the mailbox Refresh answer API keys only.
Authentication
Send an EcomSquad API key in the x-api-key header on every request:
curl --header "x-api-key: ${ECOMSQUAD_API_KEY}" \
"https://api.meetwillow.cx/api/v1/tickets?limit=5"The organization comes from the key; you never send an organization ID. Keys are for server-side use only. Each key carries permissions and connection limits.
REST and tRPC also accept a signed-in member’s session. It uses the member’s role and reaches every connection. When a request carries both a session and a key, the session decides. MCP accepts a key or MCP sign-in, limited to the scopes you approve.
Pagination
List operations return items and a nextCursor. To read the next page, send
nextCursor as the URL-encoded cursor query parameter with the same filters.
On /tickets and /tickets/{ticketId}/messages, a null nextCursor means you
reached the end. Each cursor works only with the operation that returned it. The
Needs reply feed’s nextCursor is always present, and hasMore says whether to
read again straight away.
Errors
Errors use an HTTP status and a JSON body with code and message, and issues
when input validation failed:
{
"code": "NOT_FOUND",
"message": "Ticket not found"
}| Status | code | Meaning |
|---|---|---|
| 400 | BAD_REQUEST | Invalid input: a malformed UUID or email, a value out of range, an unknown parameter, or an invalid cursor. Schema failures say Input validation failed and list issues, except on POST /orders/lookup and POST /orders/lookup-by-email, whose 400 has one fixed message and never repeats what was sent. |
| 400 | PARSE_ERROR | A POST body is not valid JSON. Send no body, or a JSON object. |
| 401 | UNAUTHORIZED | No session, and the x-api-key header is missing, invalid, expired, or revoked, or the member who created the key has left the organization. The message does not say which. |
| 403 | FORBIDDEN | The message names what is missing: the operation’s permission (This API key lacks the inbox:read permission), an API key on a key-only operation, or a signed-in user’s current organization. |
| 404 | NOT_FOUND | The record does not exist, or is outside the key’s organization or connection access. |
| 415 | UNSUPPORTED_MEDIA_TYPE | A POST carries a body that is not application/json. |
| 429 | TOO_MANY_REQUESTS | POST /orders/lookup and POST /orders/lookup-by-email only: too many failed lookups for this email, order name or key, or too many lookups by email for this email or key, in the last 24 hours, or a wait of more than a second behind the organization’s other lookups. The message does not say which. |
| 500 | INTERNAL_SERVER_ERROR | EcomSquad failed. The message is always Request failed. Retry reads; read the ticket before repeating a refresh. |
An HTTP 200 means the read succeeded, not that an order was found or recently
checked. Inspect the returned orders and, after a refresh, refresh.state. See
Troubleshooting for next steps.
A null from GET /orders is
often an id in the wrong form. The numeric orderId on a ticket or in a
customer’s synced orders matches nothing there. Send the order’s name as
orderName, or put gid://shopify/Order/ in front of the number.
Every response, success or error, carries an x-request-id header. Quote it when
you report a problem: it finds the request in EcomSquad’s logs. To use your own
id, send it as x-request-id, 1 to 128 characters of letters, digits, ., _,
: and -, and the response returns it unchanged. Any other value is replaced
with a new id. Browsers can read the header.
OpenAPI document
The API serves the document these pages are generated from:
https://api.meetwillow.cx/api/v1/openapi.jsonUse it to generate a client or to check exact request and response schemas.