# Batch update inventory
Source: https://platform.take.app/api-reference/v2/endpoint/batch-update-inventory
openapi-v2 POST /api/v2/inventory_items/batch_update
# Create customer
Source: https://platform.take.app/api-reference/v2/endpoint/create-customer
openapi-v2 POST /api/v2/customers
# Create order
Source: https://platform.take.app/api-reference/v2/endpoint/create-order
openapi-v2 POST /api/v2/orders
# Create product
Source: https://platform.take.app/api-reference/v2/endpoint/create-product
openapi-v2 POST /api/v2/products
# Get customer
Source: https://platform.take.app/api-reference/v2/endpoint/get-customer
openapi-v2 GET /api/v2/customers/{customer_id}
# Get authenticated store
Source: https://platform.take.app/api-reference/v2/endpoint/get-me
openapi-v2 GET /api/v2/me
# Get order
Source: https://platform.take.app/api-reference/v2/endpoint/get-order
openapi-v2 GET /api/v2/orders/{order_id}
# Get product
Source: https://platform.take.app/api-reference/v2/endpoint/get-product
openapi-v2 GET /api/v2/products/{product_id}
# List customers
Source: https://platform.take.app/api-reference/v2/endpoint/list-customers
openapi-v2 GET /api/v2/customers
# List inventory items
Source: https://platform.take.app/api-reference/v2/endpoint/list-inventory-items
openapi-v2 GET /api/v2/inventory_items
# List orders
Source: https://platform.take.app/api-reference/v2/endpoint/list-orders
openapi-v2 GET /api/v2/orders
# List products
Source: https://platform.take.app/api-reference/v2/endpoint/list-products
openapi-v2 GET /api/v2/products
# Search orders
Source: https://platform.take.app/api-reference/v2/endpoint/search-orders
openapi-v2 GET /api/v2/orders/search
# Send message
Source: https://platform.take.app/api-reference/v2/endpoint/send-message
openapi-v2 POST /api/v2/messages
Sends a single WhatsApp message using one of the store's approved broadcast templates. Map the template body variables via the named `parameters` object.
# Update customer
Source: https://platform.take.app/api-reference/v2/endpoint/update-customer
openapi-v2 PATCH /api/v2/customers/{customer_id}
# Update inventory item
Source: https://platform.take.app/api-reference/v2/endpoint/update-inventory-item
openapi-v2 PATCH /api/v2/inventory_items/{item_id}
# Update order
Source: https://platform.take.app/api-reference/v2/endpoint/update-order
openapi-v2 PATCH /api/v2/orders/{order_id}
# Update product
Source: https://platform.take.app/api-reference/v2/endpoint/update-product
openapi-v2 PATCH /api/v2/products/{product_id}
# Introduction
Source: https://platform.take.app/api-reference/v2/introduction
Merchant API V2 — authenticated REST API for stores, LLM agents, MCP tools, and ERP integrations.
The Merchant API V2 lets merchants, scripts, and integrations interact with a Take App store through a clean, predictable REST contract. It is the recommended REST API for all integrations.
V2 lives under `/api/v2/*` and is designed for long-term stability.
The V1 Platform API (`/api/platform/*`) and its Svix-based webhooks were
retired on July 13, 2026. All integrations must use V2.
## Conventions
* **Naming**: every JSON field is `snake_case`.
* **Money**: integer values in the smallest currency unit (e.g. cents). No human-formatted strings. Decimals vary by currency — see [Amounts & Currencies](/api-reference/v2/money).
* **Phone**: international, digits only, no leading `+`.
* **Order number**: the raw counter integer as a string. No prefix/suffix.
* **Errors**: structured `{ error: { type, code, message, param?, request_id } }`.
* **Lists**: cursor-paginated `{ object: "list", data, has_more, next_cursor }`.
## Authentication
All requests require an authenticated Merchant API token. Pass it in the `Authorization` header using the Bearer scheme.
```http theme={null}
GET https://take.app/api/v2/me
Authorization: Bearer YOUR_API_TOKEN
```
* `401 authentication_error` — missing or invalid token.
* `403 permission_error` — reserved for future scope checks.
## Idempotency
For safe retries on `POST` requests, pass an `Idempotency-Key` header. Repeated requests with the same token, route, and key return the same response within 24 hours.
```http theme={null}
POST https://take.app/api/v2/orders
Authorization: Bearer YOUR_API_TOKEN
Idempotency-Key: 0d3c9d04-2f2b-4e7c-9c4c-b8c0f7e0a1ab
```
## Pagination
List endpoints return:
```json theme={null}
{
"object": "list",
"data": [ /* ... */ ],
"has_more": true,
"next_cursor": "eyJjcmVhdGVkQXQiOi..."
}
```
Pass the returned `next_cursor` value as the `cursor` query parameter on the next request. Default `limit` is 25; max 100.
## Errors
All errors share one envelope:
```json theme={null}
{
"error": {
"type": "invalid_request_error",
"code": "invalid_parameter",
"message": "Customer phone is required",
"param": "customer.phone",
"request_id": "req_xxx"
}
}
```
| Type | HTTP |
| ----------------------- | ---- |
| `invalid_request_error` | 400 |
| `authentication_error` | 401 |
| `permission_error` | 403 |
| `not_found_error` | 404 |
| `rate_limit_error` | 429 |
| `api_error` | 500 |
Every response includes an `x-request-id` header that matches `error.request_id`.
## Resources
* **Store** — `/api/v2/me`
* **Products** — `/api/v2/products`
* **Customers** — `/api/v2/customers`
* **Orders** — `/api/v2/orders`
* **Inventory items** — `/api/v2/inventory_items`
# Amounts & Currencies
Source: https://platform.take.app/api-reference/v2/money
How the Merchant API V2 represents money — integer minor units, per-currency decimals, and the fields that carry amounts.
Every monetary value in the API is an **integer in the smallest unit of the store's currency**. There are no decimal points and no human-formatted strings like `"$12.34"` or `"Rp 5.000"`.
```json theme={null}
{
"currency": "USD",
"total_amount": 1234 // = $12.34
}
```
Integers avoid floating-point rounding errors and keep every amount exact. The catch: an integer alone is ambiguous — you must know **how many decimal places the currency has** to turn it into a human amount. That number varies by currency, and there is no universal standard, which is the most common source of confusion.
Always read an amount together with its `currency`. Amounts live on
resources next to a currency code: a store has `store.currency`, an order
has `order.currency`. Products and inventory items inherit the store
currency.
## Minor units per currency
The "smallest unit" is the currency's minor unit. Most currencies split into 100 (cents, pence, sen), but many do not.
| Decimals | Multiplier | Currencies | Example |
| --------------------- | ---------- | --------------------------------------------------- | --------------------- |
| **2** (default) | ×100 | USD, EUR, GBP, SGD, MYR, and most others | `1234` → **\$12.34** |
| **0** (no minor unit) | ×1 | IDR, JPY, KRW, VND, CLP, XAF, XOF, and others below | `5000` → **Rp 5,000** |
| **3** | ×1000 | BHD, JOD, KWD, OMR, TND | `1234` → **BD 1.234** |
**Zero-decimal currencies** (the integer *is* the amount — do not multiply by 100):
`BIF`, `CLP`, `DJF`, `GNF`, `IDR`, `IQD`, `JPY`, `KMF`, `KRW`, `MGA`, `PYG`, `RWF`, `UGX`, `VND`, `VUV`, `XAF`, `XOF`, `XPF`.
**Three-decimal currencies:** `BHD`, `JOD`, `KWD`, `OMR`, `TND`.
Every other currency uses **2 decimals**.
For a store using **IDR** (Indonesian Rupiah), a price of `5000` means
**Rp 5,000** — not Rp 50. Rupiah has no minor unit, so the integer is the
whole-rupiah amount. Sending `500000` when you mean Rp 5,000 overcharges the
customer 100×.
## Converting to and from human amounts
Let `d` be the number of decimals for the currency (from the table above):
```
human amount = minor_units / 10^d
minor units = round(human_amount * 10^d)
```
```ts theme={null}
// Decimals per currency. Only non-2 currencies are listed; everything else is 2.
const CURRENCY_DECIMALS: Record = {
BIF: 0, CLP: 0, DJF: 0, GNF: 0, IDR: 0, IQD: 0, JPY: 0, KMF: 0, KRW: 0,
MGA: 0, PYG: 0, RWF: 0, UGX: 0, VND: 0, VUV: 0, XAF: 0, XOF: 0, XPF: 0,
BHD: 3, JOD: 3, KWD: 3, OMR: 3, TND: 3,
};
const decimals = (currency: string) => CURRENCY_DECIMALS[currency] ?? 2;
// API integer -> human amount
const toHuman = (minor: number, currency: string) =>
minor / 10 ** decimals(currency);
// human amount -> API integer (use when sending prices/amounts)
const toMinor = (human: number, currency: string) =>
Math.round(human * 10 ** decimals(currency));
toHuman(1234, "USD"); // 12.34
toHuman(5000, "IDR"); // 5000
toMinor(12.34, "USD"); // 1234
toMinor(5000, "IDR"); // 5000
```
For display, `Intl.NumberFormat` reads the correct decimals from the currency for you:
```ts theme={null}
new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" })
.format(toHuman(1234, "USD")); // "$12.34"
new Intl.NumberFormat("en-US", { style: "currency", currency: "IDR" })
.format(toHuman(5000, "IDR")); // "IDR 5,000.00"
```
## Amount fields in the API
Every field below is an integer in the store currency's minor units.
### Products & inventory items
| Field | Meaning |
| ---------------- | ----------------------------------------------------------------- |
| `price` | Current selling price |
| `original_price` | Price before discount (strike-through) |
| `cost` | Merchant's unit cost (present only when cost tracking is enabled) |
Product variants carry the same three fields.
### Orders
| Field | Meaning |
| ----------------------- | --------------------------------------- |
| `items_amount` | Sum of line items before adjustments |
| `answers_amount` | Total from priced order-form answers |
| `discounts_amount` | Total discounts applied |
| `service_amount` | Selected service (e.g. delivery) charge |
| `service_charge_amount` | Store service charge |
| `tax_amount` | Tax total |
| `tip_amount` | Tip |
| `adjustment_amount` | Manual adjustment (can be negative) |
| `total_before_tax` | Subtotal before tax |
| `total_amount` | Final amount payable |
Nested amounts: each `discounts[].amount`, each line item's `price` and `total_amount`, line-item option `prices[]`, and the selected `service.price`.
### Store
| Field | Meaning |
| ----------------------- | ----------------------------------------------- |
| `service_charge.amount` | Default service charge configured for the store |
# Webhooks
Source: https://platform.take.app/api-reference/v2/webhooks
Receive store events at your own endpoint, signed and verifiable.
Webhooks let your systems react to events in a Take App store in real time — no
polling required. When an order or product changes, Take App sends an HTTP
`POST` request to the endpoint(s) you configure.
The event payload reuses the **same object** returned by the matching Merchant
API V2 endpoint — [Get order](/api-reference/v2/endpoint/get-order) for order
events and [Get product](/api-reference/v2/endpoint/get-product) for product
events — so you only need to model each shape once.
## Configuring endpoints
Add and manage endpoints in your admin dashboard under **Settings → Apps →
Webhooks**. For each endpoint you choose:
* **Endpoint URL** — an `https://` URL that accepts `POST` requests.
* **Events** — which events this endpoint subscribes to.
Each endpoint has a **signing secret** used to verify incoming requests. Copy it
anytime from the endpoint's **⋯ → Copy signing secret** menu. You can rotate the
secret at any time; the old secret stops working immediately.
## Events
| Event | When it fires |
| ----------------- | ------------------------------------------------------------------------ |
| `order.created` | A new order is placed, or a draft order is activated for the first time. |
| `order.updated` | An existing order changes — status, payment, fulfillment, or line items. |
| `product.updated` | An existing product changes — name, price, variants, or options. |
## Payload
Every delivery has the same envelope. `data` is the V2 object for the event —
an Order for `order.*` events, a Product for `product.updated`.
```json theme={null}
{
"type": "order.updated",
"created_at": "2026-06-05T08:30:00.000Z",
"data": {
"id": "ord_123",
"object": "order",
"number": "1042",
"name": "#1042",
"store": {
"id": "sto_123",
"name": "Downtown Cafe",
"alias": "downtown-cafe"
},
"order_status": "confirmed",
"payment_status": "paid",
"fulfillment_status": "unfulfilled",
"customer": {
"id": "cus_123",
"object": "customer",
"name": "Jane Doe",
"phone": "6591234567"
},
"line_items": [
{
"id": "oli_1",
"object": "order_line_item",
"product_id": "prod_1",
"name": "Nasi Lemak",
"quantity": 2,
"price": 650,
"total_amount": 1300,
"options": [
{
"question_id": "po_1",
"question_text": "Add-ons",
"question_type": "OPTION_CHECKBOX",
"answers": ["Fried egg", "Chicken wing"],
"prices": [100, 300],
"quantities": [2, 1],
"option_ids": [],
"text": null,
"image_urls": []
}
]
}
],
"items_amount": 1300,
"total_amount": 1300,
"currency": "SGD",
"created_at": "2026-06-05T08:00:00.000Z",
"updated_at": "2026-06-05T08:30:00.000Z"
}
}
```
Inside a line item option, `answers`, `prices` and `quantities` share the same
order — index `n` of each describes the same selected choice. `quantities` is
`1` for options without a quantity selector.
See [Get order](/api-reference/v2/endpoint/get-order) for the full Order schema.
### Headers
| Header | Description |
| -------------------- | ---------------------------------------------------- |
| `X-Take-Event` | The event type, e.g. `order.created`. |
| `X-Take-Delivery-Id` | Unique ID for this delivery attempt. Use for dedupe. |
| `X-Take-Signature` | Signature used to verify authenticity (see below). |
## Verifying signatures
Each request is signed with your endpoint's signing secret using HMAC-SHA256.
The `X-Take-Signature` header looks like:
```
t=1717574400,v1=5257a869e7ec...
```
* `t` — the Unix timestamp (seconds) when the request was signed.
* `v1` — the HMAC-SHA256 of `{t}.{raw_request_body}`, hex-encoded.
To verify, recompute `v1` with your secret over `{t}.{raw_body}` and compare
using a constant-time comparison. Reject requests whose timestamp is too old
(e.g. older than 5 minutes) to defend against replays.
```javascript Node.js theme={null}
import crypto from "crypto";
function verifyTakeSignature(rawBody, header, secret) {
const parts = Object.fromEntries(
header.split(",").map((kv) => kv.split("="))
);
const timestamp = parts.t;
const received = parts.v1;
// Reject old timestamps (replay protection)
const age = Math.floor(Date.now() / 1000) - Number(timestamp);
if (Number.isNaN(age) || age > 300) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(received)
);
}
```
```python Python theme={null}
import hashlib
import hmac
import time
def verify_take_signature(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(kv.split("=", 1) for kv in header.split(","))
timestamp = parts.get("t", "")
received = parts.get("v1", "")
# Reject old timestamps (replay protection)
try:
if time.time() - int(timestamp) > 300:
return False
except ValueError:
return False
signed_payload = f"{timestamp}.".encode() + raw_body
expected = hmac.new(
secret.encode(), signed_payload, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, received)
```
Verify the signature against the **raw request body** bytes, before any JSON
parsing or re-serialization. Reformatting the body changes the signature.
## Responding
Return a `2xx` status as soon as you've received the event. Do any heavy
processing asynchronously so you can respond quickly.
## Retries
If your endpoint returns a non-`2xx` status or times out, delivery is retried
with exponential backoff. A `4xx` response (other than `408` and `429`) is
treated as a permanent rejection and is **not** retried.
Because retries and at-least-once delivery are possible, treat webhooks as
**idempotent**: use `X-Take-Delivery-Id` to ignore duplicates.
## Ordering
Events are not guaranteed to arrive in order. Use the object's `updated_at`
field to discard stale updates if you persist order or product state.
Configure webhook endpoints in **Settings → Apps → Webhooks**.
# Authentication
Source: https://platform.take.app/mcp/authentication
How the Take App MCP server authorizes AI assistants using OAuth 2.1.
The MCP server is an OAuth 2.1 protected resource. Compliant MCP clients — including Claude and ChatGPT — discover and complete the flow automatically; you only click **Approve**.
## Discovery
The server advertises its authorization server per [RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728). An unauthenticated request returns `401` with a `WWW-Authenticate` header pointing at:
```
https://take.app/.well-known/oauth-protected-resource
```
That document names the authorization server, whose metadata ([RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414)) lives at:
```
https://take.app/.well-known/oauth-authorization-server
```
## Flow
1. **Dynamic client registration** ([RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591)) — the assistant registers itself and gets a `client_id`. No manual setup.
2. **Authorization code + PKCE** (`S256`) — you sign in to Take App, choose which store to connect, and approve. The connection is scoped to that one store.
3. **Token exchange** — the assistant exchanges the code for an access token and a refresh token.
Access tokens are Bearer tokens sent on every request:
```http theme={null}
POST https://take.app/api/mcp
Authorization: Bearer YOUR_ACCESS_TOKEN
```
## Permissions
Connections use the `all` scope for full access to read and manage products, orders, customers, and inventory, and to send messages. It includes token refresh for up to one year from approval; after that, reconnect the assistant. Granular tool permissions will come later.
## Managing connections
Every connected assistant appears in your admin dashboard under **Settings → Integrations → API keys**, where you can disconnect it at any time. Disconnecting revokes its token immediately.
# Connect ChatGPT
Source: https://platform.take.app/mcp/connect-chatgpt
Add your Take App store to ChatGPT as a developer-mode connector.
ChatGPT connects to the Take App MCP server as a custom app. Take App uses write actions, so full MCP support requires a ChatGPT Business, Enterprise, or Edu workspace with **Developer mode** enabled.
## Steps
In ChatGPT on the web, go to **Settings → Apps → Advanced settings** and
turn on **Developer mode**. Workspace admins can also enable it under
**Workspace Settings → Apps**.
Go to **Settings → Apps → Create** or **Workspace Settings → Apps → Create**,
paste the MCP server URL, and set **Authentication** to **OAuth**:
```
https://take.app/api/mcp
```
ChatGPT opens the Take App sign-in. Sign in, pick the store you want to
connect, and click **Approve**. ChatGPT finishes linking automatically.
## Try it
In a new chat, enable the connector and ask:
> Create a product called "Weekend Special" priced at 12.00 and mark it visible.
You can also reach these steps from your Take App admin dashboard under
**Settings → Integrations → MCP**, which shows the server URL for every
client. Connected assistants are listed under **Settings → Integrations → API
keys**.
# Connect Claude
Source: https://platform.take.app/mcp/connect-claude
Add your Take App store to Claude as a custom connector.
Claude connects to the Take App MCP server as a [custom connector](https://support.claude.com/en/articles/11175166-get-started-with-custom-connectors-using-remote-mcp). It is available on Claude Free, Pro, Max, Team, and Enterprise; Free accounts can add one custom connector.
## Steps
In Claude, go to **Customize → Connectors** and choose **Add custom connector**.
Team and Enterprise owners can manage connectors under
**Organization settings → Connectors**.
Paste the MCP server URL and click **Add**:
```
https://take.app/api/mcp
```
Claude opens the Take App sign-in. Sign in, pick the store you want to
connect, and click **Approve**. Claude finishes linking automatically.
## Try it
Ask Claude:
> How many orders did my store get this week, and which products are low on stock?
You can also reach these steps from your Take App admin dashboard under
**Settings → Integrations → MCP**, which shows the server URL for every
client. Connected assistants are listed under **Settings → Integrations → API
keys**.
# MCP Server
Source: https://platform.take.app/mcp/introduction
Connect your Take App store to Claude, ChatGPT, and other AI assistants over the Model Context Protocol.
Take App runs a remote **Model Context Protocol (MCP)** server so AI assistants can read and manage your store in chat — list orders, check inventory, create products, message customers, and more.
The server is a thin, authenticated layer over the [Merchant API V2](/api-reference/v2/introduction): every tool maps to a V2 endpoint, so behaviour, validation, and money handling are identical.
## Endpoint
```
https://take.app/api/mcp
```
It speaks the **streamable HTTP** MCP transport and requires OAuth — no API key to copy, no config file to edit. You authorize once in the assistant, pick which store to connect, and you're done.
## What it can do
Each tool is scoped to the single store you approve during authorization.
| Area | Tools |
| --------- | --------------------------------------------------------------------------- |
| Store | `get_store` |
| Products | `list_products`, `get_product`, `create_product`, `update_product` |
| Customers | `list_customers`, `get_customer`, `create_customer`, `update_customer` |
| Orders | `list_orders`, `search_orders`, `get_order`, `create_order`, `update_order` |
| Inventory | `list_inventory_items`, `update_inventory_item`, `batch_update_inventory` |
| Messaging | `send_message` |
## Connect
* [Connect Claude](/mcp/connect-claude)
* [Connect ChatGPT](/mcp/connect-chatgpt)
MCP connections require a store on the **Business** plan or higher, the same
as the Merchant API.