# 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.