Xatern OneAPI v1

Endpoints

All paths start with https://api.xatern.com/v1. Send a key with each call. Every response is JSON with "ok": true on success. For errors see Errors and limits.

Full machine readable version: openapi.yaml.

Method and pathScope
GET /healthNone
GET /meAny key
GET /catalogcatalog:read
POST /ordersorders:create
POST /orders/{number}/payment-prooforders:create
GET /orders/{number}orders:create with token, or orders:read
GET /orders/{number}/waybillorders:create
GET /ordersorders:read
GET /customerscustomers:read
POST /subscriberssubscribers:create
POST /leadsleads:create
/webhooks (nine calls)webhooks:manage

Basics

GET/health

Scope: none. Check the API is up. No key needed.

Response
{ "ok": true }

GET/me

Scope: any key. Shows which company and scopes the key has.

curl
curl https://api.xatern.com/v1/me -H "Authorization: Bearer KEY"
Response
{
  "ok": true,
  "company": "Example Shop Co",
  "kind": "publishable",
  "scopes": ["catalog:read", "orders:create", "subscribers:create", "leads:create"]
}

Catalog

GET/catalog

Scope: catalog:read. Visible products with prices and stock. Cache the answer for about a minute.

curl
curl https://api.xatern.com/v1/catalog -H "Authorization: Bearer KEY"
Response
{
  "ok": true,
  "catalog": {
    "currency": "PHP",
    "at": "2026-11-02T08:00:00Z",
    "products": [
      {
        "name": "Rose Mist",
        "category": "Mists",
        "tags": ["new"],
        "allow_preorder": false,
        "discount": null,
        "sizes": [
          { "sku": "SKU-ROSE-50", "label": "50 ml", "price": 450, "sale_price": null, "available": 24 }
        ]
      }
    ]
  }
}

Orders

POST/orders

Scope: orders:create. Place an order. Send the same idempotency_key again and you get the first order back, never a second one. The reply has the order number and a secret token. Keep the token to show the shopper their order. See Orders for every field.

curl
curl -X POST https://api.xatern.com/v1/orders -H "Authorization: Bearer KEY" \
  -H "Content-Type: application/json" \
  -d @order.json
Request body
{
  "order": {
    "idempotency_key": "6b1c2f5e-0f1a-4b9e-9d6b-2f4e8c1a7d30",
    "payment_method": "bank",
    "contact": { "name": "Maria Santos", "phone": "09171234567", "email": "maria@example.test", "social": null },
    "delivery": { "method": "jnt", "region": "NCR", "city": "Quezon City", "street": "12 Sample St", "landmark": "Blue gate" },
    "lines": [
      { "product_id": "SKU-ROSE-50", "name": "Rose Mist 50 ml", "qty": 2, "unit_price": 450, "line_total": 900, "preorder": false }
    ],
    "freight": 120,
    "promo_code": null,
    "promo_saving": 0,
    "sale_saving": 0,
    "total": 1020,
    "due_today": 1020,
    "balance": 0
  }
}
Response
{
  "ok": true,
  "order": {
    "number": "NT-482913",
    "token": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
    "status": "awaiting_payment",
    "payment_status": "unpaid",
    "total": 1020,
    "due_today": 1020,
    "balance": 1020
  }
}

POST/orders/{number}/payment-proof

Scope: orders:create. Upload the shopper payment screenshot as multipart form data. Fields: token, reference (up to 60 characters, from the receipt) and file (JPG, PNG, WebP or HEIC, up to 8 MB). The order number comes from the path.

curl
curl -X POST https://api.xatern.com/v1/orders/NT-482913/payment-proof -H "Authorization: Bearer KEY" \
  -F token=a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6 \
  -F reference=GC123456789 \
  -F file=@receipt.png
Response
{
  "ok": true,
  "order": { "number": "NT-482913", "status": "proof_received", "payment_status": "checking" }
}

GET/orders/{number}

Scope: orders:create with ?token=, or orders:read without. With the order token, returns the shopper view: status, amounts, lines, payment results and timeline. With a secret key that has orders:read and no token, returns the full order object.

curl
# Shopper view
curl "https://api.xatern.com/v1/orders/NT-482913?token=a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6" -H "Authorization: Bearer KEY"

# Staff view, secret key
curl https://api.xatern.com/v1/orders/NT-482913 -H "Authorization: Bearer KEY"
Response (staff view)
{ "ok": true, "order": {
    "number": "NT-482913",
    "status": "awaiting_payment",
    "payment_status": "unpaid",
    "payment_method": "bank",
    "placed_at": "2026-11-02T08:15:00Z",
    "updated_at": "2026-11-02T08:15:00Z",
    "currency": "PHP",
    "total": 1020,
    "due_today": 1020,
    "paid": 0,
    "balance": 1020,
    "freight": 120,
    "sale_saving": 0,
    "promo_code": null,
    "promo_saving": 0,
    "contact": { "name": "Maria Santos", "phone": "+639171234567", "email": "maria@example.test", "social": null },
    "delivery": { "method": "jnt", "region": "NCR", "city": "Quezon City", "street": "12 Sample St", "landmark": "Blue gate", "courier": null, "tracking_number": null },
    "lines": [
      { "product_id": "SKU-ROSE-50", "name": "Rose Mist 50 ml", "qty": 2, "unit_price": 450, "line_total": 900, "preorder": false }
    ],
    "payments": []
  } }

GET/orders/{number}/waybill

Scope: orders:create. A link to the waybill photo. It works for 10 minutes. url is null when there is no waybill yet. Needs ?token=.

curl
curl "https://api.xatern.com/v1/orders/NT-482913/waybill?token=a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6" -H "Authorization: Bearer KEY"
Response
{ "ok": true, "url": "https://files.example.test/waybill/NT-482913.jpg" }

GET/orders

Scope: orders:read. List orders, newest first. Query: status, payment_status, since (placed after, ISO 8601), updated_since, limit (1 to 100, default 25), cursor. To get the next page, pass next_cursor as cursor. When next_cursor is null there are no more.

curl
curl "https://api.xatern.com/v1/orders?payment_status=paid&limit=2" -H "Authorization: Bearer KEY"
Response
{
  "ok": true,
  "orders": [ { "number": "NT-482913", "status": "awaiting_payment", "payment_status": "unpaid", ... ],
  "next_cursor": "eyJwIjoiMjAyNi0xMS0wMlQwODoxNTowMFoifQ"
}

Customers

GET/customers

Scope: customers:read. List customers. Query: q (matches name, phone or email), limit, cursor.

curl
curl "https://api.xatern.com/v1/customers?q=maria" -H "Authorization: Bearer KEY"
Response
{
  "ok": true,
  "customers": [
    {
      "id": "9e1c5a52-3a0c-4d0a-bb3e-1f6d2c7a8b40",
      "name": "Maria Santos",
      "phone": "+639171234567",
      "email": "maria@example.test",
      "social": null,
      "orders_count": 3,
      "total_spent": 3060,
      "first_order_at": "2026-09-10T03:20:00Z",
      "last_order_at": "2026-11-02T08:15:00Z",
      "created_at": "2026-09-10T03:20:00Z"
    }
  ],
  "next_cursor": null
}

Mailing list and enquiries

POST/subscribers

Scope: subscribers:create. Add an email to the mailing list. source is footer (default) or checkout. Send it only after the person ticked a consent box. The answer is the same whether the email was new or not.

curl
curl -X POST https://api.xatern.com/v1/subscribers -H "Authorization: Bearer KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"maria@example.test","source":"footer"}'
Response
{ "ok": true, "subscribed": true }

POST/leads

Scope: leads:create. Send a website enquiry into the CRM. The body is the same as the lead form accepts today: the contact details and the message.

curl
curl -X POST https://api.xatern.com/v1/leads -H "Authorization: Bearer KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Jon Reyes","email":"jon@example.test","phone":"09181234567","message":"Do you do bulk orders?"}'
Response
{ "ok": true }

Webhooks

All webhook calls need webhooks:manage, so use a secret key. Read Webhooks first. A company can have up to 10 endpoints. The URL must start with https, be at most 500 characters, and must not point to localhost or a private address.

An endpoint looks like this:

Endpoint
{
  "id": "3f0a6c1e-6c1d-4a6e-8a0c-0d5a7a1b9e11",
  "url": "https://hooks.example.test/xatern",
  "events": ["order.created", "order.paid"],
  "description": "Fulfilment system",
  "enabled": true,
  "disabled_at": null,
  "disabled_reason": null,
  "created_at": "2026-11-02T08:00:00Z"
}

GET/webhooks

Scope: webhooks:manage. List your endpoints.

curl
curl https://api.xatern.com/v1/webhooks -H "Authorization: Bearer KEY"
Response
{ "ok": true, "endpoints": [ ... ] }

POST/webhooks

Scope: webhooks:manage. Make an endpoint. The reply includes its signing secret once. Save it now.

curl
curl -X POST https://api.xatern.com/v1/webhooks -H "Authorization: Bearer KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://hooks.example.test/xatern","events":["order.created","order.paid"],"description":"Fulfilment system"}'
Response
{
  "ok": true,
  "endpoint": {
    "id": "3f0a6c1e-6c1d-4a6e-8a0c-0d5a7a1b9e11",
    "url": "https://hooks.example.test/xatern",
    "events": ["order.created", "order.paid"],
    "description": "Fulfilment system",
    "enabled": true,
    "disabled_at": null,
    "disabled_reason": null,
    "created_at": "2026-11-02T08:00:00Z"
  },
  "secret": "whsec_0123456789abcdef0123456789abcdef0123456789abcdef"
}

PATCH/webhooks/{id}

Scope: webhooks:manage. Change url, events, description or enabled. Send only what changes. Setting enabled to true turns a disabled endpoint back on.

curl
curl -X PATCH https://api.xatern.com/v1/webhooks/3f0a6c1e-6c1d-4a6e-8a0c-0d5a7a1b9e11 -H "Authorization: Bearer KEY" \
  -H "Content-Type: application/json" \
  -d '{"enabled":false}'
Response
{ "ok": true, "endpoint": { ... } }

DELETE/webhooks/{id}

Scope: webhooks:manage. Remove an endpoint.

curl
curl -X DELETE https://api.xatern.com/v1/webhooks/3f0a6c1e-6c1d-4a6e-8a0c-0d5a7a1b9e11 -H "Authorization: Bearer KEY"
Response
{ "ok": true }

POST/webhooks/{id}/test

Scope: webhooks:manage. Send a ping event to the endpoint now.

curl
curl -X POST https://api.xatern.com/v1/webhooks/3f0a6c1e-6c1d-4a6e-8a0c-0d5a7a1b9e11/test -H "Authorization: Bearer KEY"
Response
{ "ok": true }

GET/webhooks/{id}/deliveries

Scope: webhooks:manage. The delivery log for an endpoint, newest first. Query: limit.

curl
curl "https://api.xatern.com/v1/webhooks/3f0a6c1e-6c1d-4a6e-8a0c-0d5a7a1b9e11/deliveries?limit=10" -H "Authorization: Bearer KEY"
Response
{
  "ok": true,
  "deliveries": [
    {
      "id": "c7d2f0a4-1b3e-4c55-9a8d-6e2f1b0a3c77",
      "event_id": "evt_0123456789abcdef01234567",
      "type": "order.paid",
      "status": "succeeded",
      "attempt": 1,
      "last_status": 200,
      "last_error": null,
      "last_ms": 184,
      "created_at": "2026-11-02T08:20:00Z",
      "delivered_at": "2026-11-02T08:20:01Z"
    }
  ]
}

POST/webhooks/deliveries/{delivery_id}/resend

Scope: webhooks:manage. Send a delivery again.

curl
curl -X POST https://api.xatern.com/v1/webhooks/deliveries/c7d2f0a4-1b3e-4c55-9a8d-6e2f1b0a3c77/resend -H "Authorization: Bearer KEY"
Response
{ "ok": true }

POST/webhooks/{id}/roll-secret

Scope: webhooks:manage. Make a new signing secret. The old one stops working at once. The new secret is shown once.

curl
curl -X POST https://api.xatern.com/v1/webhooks/3f0a6c1e-6c1d-4a6e-8a0c-0d5a7a1b9e11/roll-secret -H "Authorization: Bearer KEY"
Response
{ "ok": true, "secret": "whsec_fedcba9876543210fedcba9876543210fedcba9876543210" }

Xatern One API v1. Example data on these pages is made up.