Returns API
Customers ask for returns on your returns page in the app. The API lets your own systems see those requests and act on them. It does not create return requests: those always come from the customer, who proves an order with the order or tracking number and the email on it.
Permissions
Section titled “Permissions”returns:readlists and reads returns.returns:writeapproves, rejects, receives and closes them.
Lifecycle
Section titled “Lifecycle”A return has one of these statuses: requested, approved, rejected, label_issued, in_transit, received, closed or cancelled. Approval buys a prepaid return label and emails it to the customer.
| Call | What it does |
|---|---|
GET /returns |
List returns. Filter with ?status=requested. |
GET /returns/{id} |
Read one return. |
POST /returns/{id}/approve |
Approve and buy the prepaid return label. Spends wallet money. Accepts an optional note. |
POST /returns/{id}/reject |
Reject. A note is required and explains the decision. |
POST /returns/{id}/receive |
Mark the parcel as received. |
POST /returns/{id}/close |
Close a finished return. |
Example
Section titled “Example”# Find requests waiting for a decisioncurl "https://shipjay.com/api/v1/returns?status=requested" \ -H "Authorization: Bearer $SHIPJAY_KEY"
# Approve onecurl -X POST https://shipjay.com/api/v1/returns/$RETURN_ID/approve \ -H "Authorization: Bearer $SHIPJAY_KEY" \ -H "Content-Type: application/json" \ -d '{ "note": "Within the 30 day window" }'const headers = { Authorization: `Bearer ${process.env.SHIPJAY_KEY}`, "Content-Type": "application/json",};
const list = await (await fetch("https://shipjay.com/api/v1/returns?status=requested", { headers })).json();for (const ret of list.data) { // Apply your own policy here before approving. await fetch(`https://shipjay.com/api/v1/returns/${ret.id}/approve`, { method: "POST", headers, body: JSON.stringify({ note: "Within the 30 day window" }), });}headers = {"Authorization": f"Bearer {os.environ['SHIPJAY_KEY']}"}
listing = requests.get( "https://shipjay.com/api/v1/returns", params={"status": "requested"}, headers=headers, timeout=30).json()for ret in listing["data"]: # Apply your own policy here before approving. requests.post( f"https://shipjay.com/api/v1/returns/{ret['id']}/approve", headers=headers, json={"note": "Within the 30 day window"}, timeout=30, )A return looks like this:
{ "id": "a41d9e27-6b80-4c35-8f12-d3e05b7a9c61", "rma": 1042, "status": "requested", "shipment_id": "2f6c3a9e-5d1b-4c7e-9a41-8b0d6e7f1c20", "return_shipment_id": null, "reason": "too_small", "reason_note": null, "customer_name": "Avery Quinn", "label_error": null, "created_at": "2026-05-06T09:30:00Z", "received_at": null}reason is one of too_small, too_large, damaged, wrong_item, not_as_described, changed_mind or other. label_error explains why the prepaid label could not be bought. The return stays approved, and approving it again retries the label.
The list returns the 200 most recent returns that match the filter, newest first. It does not paginate, and next_cursor is always null.
Events
Section titled “Events”Return changes produce return.requested, return.approved, return.rejected and return.received events. Use them to react to a new request without polling. See Webhooks.
© 2026 Ship Jay. All rights reserved. Created & Developed by WCWS Internet
