You own the website code or the customer-service system? Pick one of two paths: paste the widget into your store page, or call the server-to-server API from a system that already has its own chat UI. Both run the same Nata as the chat on app.nyataka.com.
The API is not part of a subscription — here is what it costs
The API is its own package, separate from the web/Telegram subscription (Pro or Max). Buying Max does not include the API: a Max subscription is not a prerequisite and not the purchase. The storefront widget is a Max plan feature; the API is not part of any plan.
Rp 199per inbound message, prepaid. No monthly fee, no monthly minimum.
1 inbound message = 1 reply = Rp 199.
50,000 messages/month and up: Rp 175 per message; 100,000 and up: Rp 149 per message, separate agreement.
Balance empty: the API answers 402 quota_exhausted and does not process the message until you add credits.
Minimum top-up Rp 250,000. There is no self-serve checkout in the app yet: the Nyataka team activates the package and adds credits by hand.
These prices are an owner decision from 2026-10-03 (docs product-strategy-tiers-api §4). Billing is still manual: no self-serve top-up in the app and no automatic invoice. Limits are in section 7.
1. Two paths, pick one
Widget on your site
One <script> line shows Nata as a panel on the right side of your site. Shoppers ask questions and get answers from your catalog and policies. You write no backend code.
The key is the store's public key (nata_pub_…). It is safe in HTML: it can only answer from your catalog, and only from the domains you allow.
API server-to-server
Your system or helpdesk already has a chat box? Send the message to POST /webhook/nata and render Nata's reply in your own UI. It fits helpdesks, WhatsApp gateways, and internal ERP tools.
The secret key (nata_whk_…) belongs on your server only. Never ship it in browser code.
You can use both: the widget for shoppers on your store pages, the API for your support team's internal dashboard.
2. API keys
Open api.nyataka.com → Kunci and click Buat kunci. The key is shown once. Our server keeps only its hash, so save it somewhere safe right then.
The format:
nata_whk_<key_id>.<rahasia>
key_id is the public identifier, safe to quote in a support ticket. The secret half must never leave your server. Send both on every request:
Authorization: Bearer nata_whk_<key_id>.<rahasia>
Don't confuse it with the widget key
The official widget uses a public key, nata_pub_…, which may sit in HTML and can only call the widget. The API uses nata_whk_…, which is secret, server-side only, and spends API credits. A widget key cannot call /webhook/nata, and an API key must never be used in a widget script.
Rotate a key
Click Ganti kunci on the same page. The old key dies immediately, with no transition period, so update the environment variable on your server as soon as you rotate. Need to rotate with no downtime? Ask us: two active keys at once are not supported today.
If a key leaks
Rotate the key now on the API page. The old key dies at once.
Update NATA_API_KEY on your server and redeploy.
Check usage on the API page (messages today) for traffic that is not yours.
Email [email protected] with the key_id, not the key, so we can trace the logs.
A key pasted into frontend code is the most common cause. If the browser can read the key, treat it as leaked.
3. Quickstart
Every integration endpoint, at a glance:
Endpoint
Auth
What it is for
POST /webhook/nata
Bearer nata_whk_…
Send one message, get a reply. Synchronous, or 202 plus a callback.
GET /webhook/nata/usage
Bearer nata_whk_…
Your own meter: today's messages, tokens, remaining credits.
PUT /webhook/nata/knowledge
Bearer nata_whk_…
Push product and policy pages from your backend.
GET /api/embed/config
public key + a registered origin
Widget config. The widget calls it; handy for a quick check.
POST /api/embed/shop-chat
public key + a registered origin
A shopper question to the widget.
Rotate keys, set the callback URL, and read the meter on the API surface itself: api.nyataka.com → Kunci. There is no endpoint to rotate a key from your server. That is deliberate: leaked code must not be able to replace the key.
curl
curl -X POST https://app.nyataka.com/webhook/nata \
-H "Authorization: Bearer $NATA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"text":"Stok gamis ukuran L masih ada?","thread_id":"cust-123"}'
JavaScript (Node 18+, built-in fetch)
const res = await fetch("https://app.nyataka.com/webhook/nata", {
method: "POST",
headers: {
Authorization: "Bearer " + process.env.NATA_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({ text: "Stok gamis L masih ada?", thread_id: "cust-123" }),
});
const data = await res.json();
if (!res.ok) {
// data.error holds the machine code; see section 8
throw new Error(data.error);
}
console.log(data.reply); // the reply is ready to display
Request body
Field
Required
Meaning
text
yes, unless you send an attachment
The inbound message. Truncated at 8,000 characters. message works as an alias.
thread_id
no
Your conversation label, such as a ticket or customer id. Echoed back unchanged.
mode
no
"sync" (default) or "async". See section 4.
attachments
no
[{ filename, mime, content_base64 }]. At most 4 files, 4 MB each.
Response body (200)
{
"ok": true,
"reply": "Masih ada 4 pcs ukuran L.",
"reply_html": "<p>Masih ada 4 pcs ukuran L.</p>",
"reply_text": "Masih ada 4 pcs ukuran L.",
"backend": "internal",
"thread_id": "cust-123",
"usage": { "messages_today": 12, "input_tokens": 812, "output_tokens": 233, "messages_this_month": 640 },
"credits": 488
}
reply is Nata's Markdown. reply_html is rendered HTML, and reply_text is the same text without Markdown. Pick whichever fits your UI. usage.input_tokens/output_tokens are for your own cost reconciliation, not a unit we bill. messages_this_month tracks the volume threshold. backend is an internal label: do not build logic on it.
A heavy request, such as an analysis or a long report, can outlast a waiting user. Send mode: "async": we answer 202 right away and POST the result to the callback URL saved on the API page.
The callback body is the same JSON as a synchronous reply (reply, reply_html, reply_text, thread_id), sent as a POST to your URL.
Verify the callback signature
Every callback is signed, so knowing your callback URL is not enough to inject a fake reply. The secret is callback_signing_secret on the API page. It is issued with the key and does not change when you rotate it; GET /api/web/webhook/status shows it again if you lose it. We send two headers:
Header
Value
X-Nyataka-Timestamp
Unix seconds when we signed. Reject the request if the difference is more than 5 minutes.
X-Nyataka-Signature
sha256=<hex>, an HMAC-SHA256 of <timestamp>.<raw body> with that secret.
Verification in Node. Use the raw body, before JSON.parse:
The callback URL must be public https. http://, localhost, private IPs, and .local/.internal hosts get callback_url_must_be_https or invalid_callback_url.
We try 2 times, 1.5 seconds apart, then stop. We keep no queue on our side, so answer 2xx fast. Queue on your side if you need to.
We do not follow redirects, and we re-check the address at delivery time to block DNS rebinding.
If you set a callback URL, a copy of the reply also goes there for synchronous requests. Leave the URL empty if you do not want that.
5. Push your own catalog
Nata answers shoppers from your store data. Two ways to fill it: let Nata read your site sitemap from the Akun tab (Baca katalog dari website), or push the data from your backend. Pushing is more accurate for stock and prices that change fast.
The product or page name. Items without title are skipped.
id
no
Your stable id. Used by mode: "upsert". Without it, url or title is the key.
price_idr
no
Price in rupiah, as a number with no separators.
in_stock
no
true or false. Nata uses it to say "out of stock" honestly.
url
no
Link to the product page.
description / body
no
Text Nata reads when it answers.
category
no
Any category you like, such as gamis.
mode: "replace" (default) swaps the whole category. "upsert" adds or updates only.
At most 500 products and 500 pages per request. Send in batches if your catalog is bigger.
{ "ok": true, "counts": { "products": 128, "pages": 12 } } — what is stored after the push.
An empty body, where no product or page passes validation, returns 400 nothing_to_store.
What you get: Nata answers from your real prices, stock, and policies instead of guessing. If you never pushed a catalog, Nata says the data is missing. That is correct behaviour, not a bug.
Both take the public key and serve only the origins you allow. They are not a replacement for the server-to-server API, and they reject nata_whk_ keys.
CORS and the origin list
A shopper request is served only when the browser's Origin header matches a row in Website yang boleh memasang widget exactly. Write the scheme and host only, with no path and no trailing slash:
https://tokumu.com
https://www.tokumu.com
An unlisted origin gets 403 origin_not_allowed and no CORS header. In a browser that usually shows up as a CORS error in the console, not as a widget error message. It is the most common red herring, so check the origin list first.
A different subdomain is a different origin. https://www.tokumu.com does not inherit the allowance from https://tokumu.com. Add both if shoppers arrive from both.
Widget limits
Per IP: 30 messages per 10 minutes. Per site: 40 messages per minute. Over the limit: 429 slow_down.
A daily shopper quota per site (default 25, set on the widget page). When it runs out: 429 store_daily_cap, and the message tells the shopper to contact your store.
Shopper messages are truncated at 800 characters. Over 1,600 characters is rejected with 413 message_too_long.
The widget needs a plan that includes web integration (Max). Without it: 403 widget_plan_required, and the widget does not appear.
6b. Page copilot (page agent)
If the admin page you want help with is your own, send mode: "copilot" to the same endpoint. You send what is visible on the page; Nata replies with words plus a list of actions the page may run. Nata has no browser here — your page executes them.
The response carries reply, actions, requires_approval, and session_id. The only allowed actions are set_field (the name must exist in page.fields) and click (the ref must exist in page.elements). The server drops Save/Publish/Delete/Pay/Send actions; the page must still show a diff and ask for approval when requires_approval is true.
access_mode: read_only (no actions), approval (default; actions wait for approval), full_access (actions may run immediately). One copilot call costs one API credit. The full guide and a page-agent example live in docs/copilot-api.md; this contract is for every API customer, not one store.
If your page has many routes, declare the manifest once. Real Rumaisha example: 20 routes, 86 fields, 11 flows; the pack is at docs/rumaisha-page-agent-pack.json. A manifest stores structure, never field values, passwords, or buyer data.
7. API limits and quota
Limits
Value
What happens
text length
8,000 characters
Truncated, not rejected.
JSON body size
8 MB
The request is rejected at the server before it reaches Nata.
3,334 messages/day, derived from the 100,000/month volume. An operator can raise it per account.
429 daily_chat_quota. Resets at 07:00 WIB. This is not your plan's chat quota.
API credits
prepaid, 1 message = 1 credit
402 quota_exhausted; the response includes the remaining credits.
Per-minute limit per key
none yet
—
Two fences, two counters. API messages use the API's own daily quota, not your plan's daily chat quota: web chat and the API do not consume each other's allowance. The two fences are the API daily ceiling above (429 daily_chat_quota) and the prepaid balance of one credit per message (402 quota_exhausted). A key with no prepaid balance — an old key created before credits existed — does not spend a credit, but the API daily ceiling still applies. Your account's real numbers are on the API page.
Pricing
In short, from Pricing and packages above: the API is a separate package, prepaid at Rp 199 per message, with no monthly fee.
Usage
Price
Note
Inbound text message
Rp 199
1 message = 1 reply. Minimum top-up Rp 250,000.
Volume, from 50,000 messages/month
Rp 175
Separate agreement for partners.
Volume, from 100,000 messages/month
Rp 149
Separate agreement for partners.
These prices are an owner decision from 2026-10-03 (docs product-strategy-tiers-api §4). Billing is still manual through the team: no self-serve top-up in the app and no automatic invoice. To add credits, email [email protected] with your key_id.
8. Errors
Every error is JSON: { "error": "machine_code" }. error is the stable contract and never changes language. When there is human text it arrives twice: message in Indonesian and message_en in English. Some responses also carry credits or required. These are the codes the API emits today.
API webhook
HTTP
Code
What it means and what to do
401
missing_api_key
The Authorization header is missing or not in Bearer format.
401
invalid_api_key
The key is wrong, rotated, or stale. Create a new key on the API page.
400
invalid_text
The body has no text and no attachment.
402
quota_exhausted
The prepaid balance is empty. The response includes credits. Ask the team for a top-up.
400
callback_url_required_for_async
mode: "async" was used with no callback URL saved.
429
daily_chat_quota
The API daily message ceiling is reached. The code name says "chat"; on the API path it means the API ceiling. Resets at 07:00 WIB.
403
webhook_channel_scale_only
This account has no API subscription yet. The code name mentions a plan for historical reasons; read required: "api_subscription" and message/message_en in the body instead.
403
account_disabled
The account is disabled. Contact support.
401
unknown_seller
The key points at an account that no longer exists.
502
agent_failed
Nata failed to answer. The credit is refunded; try again.
504
agent_timeout
Nata ran out of time. The credit is refunded; try again or use mode: "async".
400
page_cannot_commit
The page has no save. Use read-only, or add a commit handler in the page agent.
400
attachments_not_supported
Copilot mode does not accept attachments; send only page and message.
501
copilot_not_configured
This deployment has not enabled copilot. Contact the team.
PUT /webhook/nata/knowledge, or "Baca katalog dari website" on the widget page
The reply is cut off
The 8,000-character limit on the inbound message
Send a shorter message, or summarise on your side
10. Support
Email [email protected]. Include the key_id, not the secret, plus the time it happened (WIB), the endpoint you called, and the raw response body. For a credit top-up, put "Top up kredit API" in the subject.