Sending us your commerce data
The Custom Commerce API. If you are on Shopify we read your store for you; on anything else, push the same records here and everything downstream works the same way.
What you need
One API key with the events.write scope, made in your TryMe settings under API keys. The key is shown once, when you create it.
curl -X POST https://api.tryme.com/api/v1/events \
-H "Authorization: Bearer tk_live_..." \
-H "idempotency-key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d @events.json
The envelope
Send up to 100 events per request. Each one is a type, the moment it happened, and the record itself under data.
{
"events": [
{
"type": "purchase.completed",
"occurredAt": "2027-03-02T14:21:00Z",
"data": {
"id": "ord-90412",
"currency": "GBP",
"total": "128.00",
"discount": "10.00",
"discountCodes": [
"TRYME-8QK2VP"
],
"customerId": "cus-4181",
"attributes": {
"tryme_journey": "jny_01HW3Z6K2P"
},
"lineItems": [
{
"productId": "sku-harbour-fog",
"quantity": 1,
"unitPrice": "110.00"
}
]
}
}
]
}
A batch is all or nothing. If any event is rejected, none are accepted, and the response lists every problem with the index of the event it came from. Idempotency keys here are positional, so a partial success would leave you no safe way to retry.
Money and dates
Amounts are decimal, in the currency's main unit. "12.80" is twelve pounds eighty. Not minor units: a contract that took 1280 here and 12.80 from a platform would put a hundredfold error one typo away. Send them as strings if you can, so nothing rounds on the way.
Dates are ISO 8601. occurredAt is optional and defaults to when we received the event. A date we cannot parse is refused rather than replaced, because an integration sending an unreadable date has a bug worth knowing about.
What you can send
| Event | Carries |
|---|---|
commerce.product_updated | product |
commerce.product_deleted | product |
commerce.customer_updated | customer |
commerce.order_created | order |
commerce.order_cancelled | order |
purchase.completed | purchase |
purchase.refunded | refund |
fulfillment.shipped | fulfillment |
fulfillment.delivered | fulfillment |
fulfillment.failed | fulfillment |
Nothing else. The event taxonomy is larger, but the rest of it is things TryMe concludes or does, and a key that could assert those would be writing its own results.
Product
| Field | Notes |
|---|---|
id | Required. Your own product id. We never mint one for you. |
title | Required. |
status | `active`, `draft` or `archived`. Defaults to `active`. |
currency | ISO 4217, e.g. `GBP`. Needed when you send any price. |
price | Used for the default variant when you send no variants. |
variants[].id | Required on each variant. A trial is mapped to a variant. |
variants[].price | Decimal, main unit. `"12.80"` is twelve pounds eighty. |
variants[].inventoryQuantity | Leave it out when you do not track stock. |
commerce.product_updated
{
"type": "commerce.product_updated",
"occurredAt": "2027-03-01T09:00:00Z",
"data": {
"id": "sku-harbour-fog",
"title": "Harbour Fog Eau de Parfum",
"status": "active",
"handle": "harbour-fog",
"vendor": "Northaven",
"currency": "GBP",
"imageUrl": "https://cdn.northaven.test/harbour-fog.jpg",
"variants": [
{
"id": "hf-50",
"title": "50ml",
"sku": "HF-50",
"price": "128.00",
"inventoryQuantity": 24
},
{
"id": "hf-100",
"title": "100ml",
"sku": "HF-100",
"price": "180.00",
"available": false
}
]
}
}commerce.product_deleted
{
"type": "commerce.product_deleted",
"occurredAt": "2027-03-01T09:05:00Z",
"data": {
"id": "sku-harbour-fog",
"title": "Harbour Fog Eau de Parfum"
}
}
Order
| Field | Notes |
|---|---|
id | Required. Your own order id, and what every later event refers to. |
currency | Required. ISO 4217. |
total | Required. Decimal, main unit, including tax. |
discountCodes | Send these. A trial credit is recognised by its code. |
attributes | Whatever the checkout carried, including `tryme_journey`. |
lineItems[].quantity | Required on each line. |
lineItems[].productId | Send it where you have it. Attribution reads it. |
placedAt | ISO 8601. Defaults to the event time. |
commerce.order_created
{
"type": "commerce.order_created",
"occurredAt": "2027-03-02T14:20:00Z",
"data": {
"id": "ord-90412",
"number": "#1042",
"currency": "GBP",
"total": "128.00",
"subtotal": "110.00",
"tax": "18.00",
"customerId": "cus-4181",
"email": "rowan@fernlake.test",
"lineItems": [
{
"productId": "sku-harbour-fog",
"variantId": "hf-50",
"quantity": 1,
"unitPrice": "110.00"
}
]
}
}commerce.order_cancelled
{
"type": "commerce.order_cancelled",
"occurredAt": "2027-03-02T16:00:00Z",
"data": {
"id": "ord-90412",
"currency": "GBP",
"total": "128.00",
"cancelReason": "customer_changed_mind"
}
}
Customer
| Field | Notes |
|---|---|
id | Required. Your own customer id. |
email | Hashed on arrival. We hold identities, not address books. |
acceptsMarketing | Only send it if you know. Absent is not false. |
commerce.customer_updated
{
"type": "commerce.customer_updated",
"occurredAt": "2027-03-01T09:10:00Z",
"data": {
"id": "cus-4181",
"email": "rowan@fernlake.test",
"firstName": "Rowan",
"lastName": "Ash",
"acceptsMarketing": true
}
}
Fulfillment
| Field | Notes |
|---|---|
id | Required. Your own fulfilment or shipment id. |
orderId | Required. The order this parcel is for. |
trackingNumber | Optional, and shown to the shopper where you send it. |
shippedAt | ISO 8601. Defaults to the event time. |
fulfillment.shipped
{
"type": "fulfillment.shipped",
"occurredAt": "2027-03-03T08:00:00Z",
"data": {
"id": "shp-771",
"orderId": "ord-90412",
"carrier": "Fernlake Post",
"trackingNumber": "FP-4471192",
"trackingUrl": "https://track.fernlake.test/FP-4471192"
}
}fulfillment.delivered
{
"type": "fulfillment.delivered",
"occurredAt": "2027-03-05T10:30:00Z",
"data": {
"id": "shp-771",
"orderId": "ord-90412",
"carrier": "Fernlake Post"
}
}fulfillment.failed
{
"type": "fulfillment.failed",
"occurredAt": "2027-03-05T10:30:00Z",
"data": {
"id": "shp-771",
"orderId": "ord-90412",
"carrier": "Fernlake Post"
}
}
Purchase
| Field | Notes |
|---|---|
| The same object as `order`. The event is what says the money moved. |
purchase.completed
{
"type": "purchase.completed",
"occurredAt": "2027-03-02T14:21:00Z",
"data": {
"id": "ord-90412",
"currency": "GBP",
"total": "128.00",
"discount": "10.00",
"discountCodes": [
"TRYME-8QK2VP"
],
"customerId": "cus-4181",
"attributes": {
"tryme_journey": "jny_01HW3Z6K2P"
},
"lineItems": [
{
"productId": "sku-harbour-fog",
"quantity": 1,
"unitPrice": "110.00"
}
]
}
}
Refund
| Field | Notes |
|---|---|
id | Required. Your own refund id. |
orderId | Required. The order the money went back on. |
amount | Required. Decimal, main unit. What actually went back. |
partial | Whether only part of the order came back. |
orderTotal | Send this instead of `partial` and we work it out. |
purchase.refunded
{
"type": "purchase.refunded",
"occurredAt": "2027-03-09T11:00:00Z",
"data": {
"id": "ref-2290",
"orderId": "ord-90412",
"amount": "28.00",
"currency": "GBP",
"orderTotal": "128.00",
"reason": "damaged_in_transit"
}
}
When something is wrong
A rejected batch answers 400 with code: "invalid_request" and a problem per bad field, named by where it sits in what you sent.
{
"ok": false,
"code": "invalid_request",
"error": "The request body is not what this endpoint expects.",
"details": {
"problems": [
{
"event": 0,
"path": "data.total",
"problem": "is required"
},
{
"event": 1,
"path": "data.lineItems[0].quantity",
"problem": "must be a whole number, zero or more"
}
]
}
}
Order of events
Send the order before the fulfilment and the fulfilment before the refund where you can, but nothing breaks if you do not: every consumer is idempotent and out-of-order delivery is normal. What matters is that the ids match. orderId on a fulfilment or a refund is the id you sent on the order.