Get started
- Create a key. In your dashboard, open Settings ▸ API & webhooks and click Create key. Name it after the tool (“Zoho CRM”) and tick only the permissions it needs. API keys are part of the Pro plan.
- Copy it once. The key starts with
oyk_and is shown only when it's created — keep it in your tool's settings or a secrets manager, never in a web page. - Make a call. Every request goes to
https://api.oyklane.com/api/v1.
curl https://api.oyklane.com/api/v1/shop \
-H "Authorization: Bearer oyk_your_key"Authentication
Send the key in the Authorization header as Bearer oyk_… (or in X-Oyklane-Key). A key only reaches its own store, and only what its permissions allow — a request outside them gets 403. Revoke a key any time from the same settings page; it stops working immediately.
| Permission | Allows |
|---|---|
read_products | Read products, categories and collections |
write_products | Create, edit and delete products, categories and collections |
read_inventory | Read stock levels |
write_inventory | Change stock levels |
read_orders | Read orders |
write_orders | Ship orders (add tracking) |
import_orders | Import past orders (no payment, stock or messages) |
read_customers | Read customers |
write_customers | Create and edit customers |
read_discounts | Read discounts |
write_discounts | Create discounts |
Requests and pagination
Requests and responses are JSON. Lists return the newest first, 50 at a time: pass ?page=2&limit=100 (100 at most). Every list answers with { data, page, limit, total }. To sync only what changed, pass updated_since with the time of your last sync (ISO 8601, e.g. 2026-10-01T00:00:00+05:30). Amounts are in rupees (or the store's currency), not paise.
curl "https://api.oyklane.com/api/v1/orders?payment_status=paid&updated_since=2026-10-01T00:00:00%2B05:30&limit=100" \
-H "Authorization: Bearer oyk_your_key"Endpoints
| Method | Path | Permission | What it does |
|---|---|---|---|
| GET | /shop | any key | Your store: name, handle, currency, time zone, domain. |
| GET | /products | read_products | Products, newest first. Filters: status, q (title or SKU), sku, handle, external_id, updated_since. |
| GET | /products/{id} | read_products | One product with its variants and images. |
| POST | /products | write_products | Create a product. category by name (made if new), images copied into your Files, externalId to import safely. |
| PATCH | /products/{id} | write_products | Change a product — send only the fields that change. |
| DELETE | /products/{id} | write_products | Delete a product. |
| GET | /categories | read_products | Categories. Filters: name, external_id. |
| POST | /categories | write_products | Create a category (name, image, externalId) — or get the one with that name. |
| GET | /collections | read_products | Collections with their product ids. Filter: external_id. |
| POST | /collections | write_products | Create a collection with productIds or productExternalIds. |
| GET | /inventory | read_inventory | Stock for every variant (variant_id, sku, quantity). |
| POST | /inventory/adjust | write_inventory | Add to or set a variant's stock, with a reason. |
| GET | /orders | read_orders | Orders, newest first. Filters: payment_status, fulfillment_status, created_since, external_id, updated_since. |
| GET | /orders/{id} | read_orders | One order with line items, address and totals. |
| POST | /orders/{id}/fulfillments | write_orders | Ship items: courier, tracking number and link; the shopper is emailed. |
| POST | /orders/import | import_orders | Record a past order from another platform, with its date and number. No payment, stock change, messages or fees. |
| GET | /customers | read_customers | Customers, newest first. Filters: email, phone, external_id, updated_since. |
| GET | /customers/{id} | read_customers | One customer. |
| POST | /customers | write_customers | Create a customer — or fill in the one with that externalId, email or phone. |
| PATCH | /customers/{id} | write_customers | Change a customer. |
| GET | /discounts | read_discounts | Discounts. Filters: code, external_id. |
| POST | /discounts | write_discounts | Create a discount code — or get the one with that code or externalId. |
curl -X POST https://api.oyklane.com/api/v1/orders/ORDER_ID/fulfillments \
-H "Authorization: Bearer oyk_your_key" \
-H "Content-Type: application/json" \
-d '{ "courier": "Delhivery", "tracking_number": "1234567890", "notify_customer": true }'curl -X POST https://api.oyklane.com/api/v1/inventory/adjust \
-H "Authorization: Bearer oyk_your_key" \
-H "Content-Type: application/json" \
-d '{ "variant_id": "VARIANT_ID", "mode": "set", "quantity": 42, "reason": "received" }'Example: an order
{
"data": {
"id": "cm8x2k…",
"number": 1043,
"email": "ananya@example.com",
"phone": "+919876543210",
"currency": "INR",
"subtotal": 2199,
"shipping": 100,
"tax": 0,
"total": 2299,
"payment_method": "razorpay",
"payment_status": "paid",
"fulfillment_status": "unfulfilled",
"gstin": null,
"customer": { "id": "cm8w9q…", "name": "Ananya Sharma", "email": "ananya@example.com" },
"shipping_address": { "name": "Ananya Sharma", "address1": "12 MG Road", "city": "Bengaluru", "province": "Karnataka", "zip": "560001", "country": "India" },
"line_items": [
{ "id": "cm8x2m…", "product_id": "cm7…", "variant_id": "cm7…", "title": "Pure Linen Shirt — M", "quantity": 1, "price": 2199, "total": 2199 }
]
}
}Webhooks
Instead of asking every few minutes, let Oyklane tell you. In Settings ▸ API & webhooks, add an endpoint (an https:// URL of your server or your CRM's webhook URL) and pick its events. Oyklane sends a POST with the JSON body (the same shape as the API) within seconds.
| Event | When |
|---|---|
order.created | A new order was placed |
order.paid | An order was paid |
order.fulfilled | Items were shipped |
order.delivered | An order was delivered |
order.cancelled | An order was cancelled |
order.refunded | An order was refunded (fully or partly) |
product.created | A product was created |
product.updated | A product was changed |
product.deleted | A product was deleted |
customer.created | A new customer was added |
Answer with any 2xx within 10 seconds. Anything else is retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours. Every delivery is listed in your settings with its response, and you can send one again or send a test.
Verifying a webhook
Each request carries these headers. Check the signature before trusting the body, and ignore anything older than five minutes.
x-oyklane-event | The event, e.g. order.paid |
x-oyklane-delivery | A unique id — use it to ignore a repeat |
x-oyklane-timestamp | Unix seconds when it was sent |
x-oyklane-signature | sha256= + HMAC-SHA256 of {timestamp}.{raw body} with your endpoint's secret (whsec_…) |
import crypto from "node:crypto";
// Express: keep the raw body — the signature is over the exact bytes.
app.post("/oyklane-webhook", express.raw({ type: "application/json" }), (req, res) => {
const ts = req.get("x-oyklane-timestamp");
const sig = req.get("x-oyklane-signature"); // "sha256=…"
const expected = "sha256=" + crypto
.createHmac("sha256", process.env.OYKLANE_WEBHOOK_SECRET) // whsec_…
.update(`${ts}.${req.body}`)
.digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300;
if (!fresh || !crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
return res.sendStatus(401);
}
const event = req.get("x-oyklane-event"); // e.g. "order.paid"
const payload = JSON.parse(req.body);
// …send it to your CRM, then answer quickly:
res.sendStatus(200);
});Connect a CRM
The same four steps work for Zoho CRM, HubSpot, Salesforce, LeadSquared, Freshsales — or Zapier and Make if you'd rather not write code.
- Create a key with
read_ordersandread_customers(addwrite_ordersif your CRM or warehouse should add tracking). - Add a webhook for
customer.created,order.paidandorder.fulfilled. Point it at your server — or at the “catch hook” URL Zapier or Make gives you. - Map the fields. Customer → contact (name, email, phone); order → deal or sale (number, total, status, line items). Use the customer's
idas the external id so repeats update instead of duplicating. - Backfill once. Page through
/customersand/ordersto bring in history; after that, webhooks keep the CRM up to date. A nightly call withupdated_sincecatches anything a busy CRM missed.
Move a store to Oyklane
Bring a whole store across — from Shopify or anywhere — with one key that has write_products, write_customers, write_discounts and import_orders. Send each record's id from the old store as externalId (e.g. shopify:8123): it's unique in your store, so running the import again finds what it already made ("created": false) instead of duplicating it.
- Categories —
POST /categorieswith a name. Or skip them and sendcategoryby name on each product. - Products — image URLs are copied into your Files, so nothing depends on the old store. Variants are named by their option values, e.g.
M / Red. - Collections — link products with
productExternalIds. - Customers — matched by externalId, email, then phone; a match is only filled in, never overwritten.
- Discounts — imported codes stay off product pages unless you send
showOnStore: true. - Past orders —
POST /orders/importkeeps the date and number. Nothing a new order sets off happens: no payment, stock change, emails, WhatsApp, webhooks or Oyklane fees.
curl -X POST https://api.oyklane.com/api/v1/orders/import \
-H "Authorization: Bearer oyk_…" -H "Content-Type: application/json" \
-d '{"externalId":"shopify:5012","number":1042,"createdAt":"2026-03-02T10:15:00+05:30",
"email":"asha@example.com","paymentMethod":"prepaid","paymentStatus":"paid",
"fulfillmentStatus":"fulfilled","shipping":49,"total":548,
"customer":{"name":"Asha Rao","phone":"+919876543210"},
"shippingAddress":{"name":"Asha Rao","address1":"12 MG Road","city":"Bengaluru","province":"Karnataka","zip":"560001","country":"India"},
"lineItems":[{"title":"Lakadong Turmeric 250g","sku":"TUR-250","quantity":2,"price":249.5,"productExternalId":"shopify:771"}],
"courier":"Delhivery","trackingNumber":"1234567890"}'AI agents (MCP)
The same API is available as a Model Context Protocol server at https://api.oyklane.com/api/mcp, so AI assistants can look up orders, products and stock with your key — with the same permissions and limits.
Errors and limits
| Status | Meaning |
|---|---|
400 | Something in the request isn't valid — the error field says what. |
401 | No key, or the key was revoked. |
403 | The key doesn't have the permission this needs. |
404 | Not found in this store. |
429 | More than 240 requests a minute with this key — wait a moment and retry. |
Questions about the API? Ask the Help assistant in your dashboard. Building a theme or an app for other stores? See the integrations guide.