Untuk developer

Integrasi Nata ke web self-hosted

Halaman ini untuk kamu yang memegang kode website atau sistem CS sendiri. Ada dua jalur: widget yang kamu tempel di halaman toko, dan API server-to-server kalau sistemmu sudah punya tampilan chat sendiri. Keduanya memakai otak Nata yang sama dengan chat di app.nyataka.com.

Ambil kunci di app Ajukan paket API
Paket terpisah

API bukan bagian dari langganan — ini biayanya

API dibeli sebagai paketnya sendiri, terpisah dari langganan web/Telegram (Max). Beli Max tidak termasuk API-nya: langganan Max bukan syarat akses dan bukan pembeliannya. Sebaliknya, widget di situs tokomu tetap fitur paket Max — API yang tidak ikut paket mana pun.

Rp 199per pesan masuk, prabayar. Tanpa biaya bulanan, tanpa minimum bulanan.


Harga ini keputusan pemilik 2026-10-03 (docs product-strategy-tiers-api §4). Penagihan masih manual: belum ada top up sendiri di app dan belum ada invoice otomatis. Rincian batas ada di bagian 7.

1. Dua jalur, pilih satu

Widget di situsmu

Satu baris <script> menampilkan Nata sebagai panel di sisi kanan situs. Pembeli bertanya dan dijawab dari katalog serta kebijakan tokomu. Kamu tidak menulis backend apa pun.

Kunci yang dipakai kunci publik toko (nata_pub_…) — aman ada di HTML karena hanya bisa menjawab dari katalog tokomu, dan hanya dari domain yang kamu izinkan.

API server-to-server

Sistem atau CS-mu sudah punya kotak chat sendiri? Kirim pesannya ke POST /webhook/nata dan tampilkan balasan Nata di UI-mu. Cocok untuk helpdesk, WhatsApp gateway, atau ERP internal.

Kunci rahasia (nata_whk_…) hanya boleh ada di server. Jangan pernah menaruhnya di kode browser.

Boleh dua-duanya: widget untuk pembeli di halaman toko, API untuk tim CS di dashboard internal.

2. Kunci API

Buka api.nyataka.com → Kunci lalu tekan Buat kunci. Kunci hanya ditampilkan sekali; di server kami yang tersimpan cuma hash-nya, jadi simpan di tempat aman waktu itu juga.

Formatnya:

nata_whk_<key_id>.<rahasia>

key_id adalah penanda publik (boleh kamu tulis di tiket dukungan); bagian rahasianya yang tidak boleh keluar dari server. Kirim di setiap permintaan:

Authorization: Bearer nata_whk_<key_id>.<rahasia>

Jangan tertukar dengan kunci widget

Widget resmi memakai kunci publik nata_pub_… yang boleh ditempel di HTML dan hanya bisa memanggil widget. API memakai nata_whk_… yang rahasia, hanya di server, dan memotong kredit API. Kunci widget tidak bisa memanggil /webhook/nata, dan kunci API tidak boleh dipakai di script widget.

Ganti kunci

Tekan Ganti kunci di halaman yang sama. Kunci lama langsung mati — tidak ada masa transisi — jadi perbarui environment variable di servermu segera setelah mengganti. Kalau kamu perlu memutar kunci tanpa downtime, hubungi kami: dua kunci aktif sekaligus belum didukung hari ini.

Kalau kunci bocor

  1. Ganti kunci sekarang di halaman API (kunci lama mati seketika).
  2. Perbarui NATA_API_KEY di server dan deploy ulang.
  3. Cek pemakaian di halaman API (pesan hari ini) untuk melihat apakah ada pemakaian yang bukan dari kamu.
  4. Email [email protected] dengan key_id (bukan kuncinya) supaya kami bisa menelusuri log.

Kunci bocor yang ditempel di kode frontend adalah penyebab paling umum. Kalau kunci itu terbaca browser, anggap sudah bocor.

3. Quickstart

Semua endpoint integrasi, sekilas:

EndpointAuthUntuk apa
POST /webhook/nataBearer nata_whk_…Kirim satu pesan, dapat balasan. Sinkron, atau 202 + callback.
GET /webhook/nata/usageBearer nata_whk_…Meteran sendiri: pesan hari ini, token, sisa kredit.
PUT /webhook/nata/knowledgeBearer nata_whk_…Dorong katalog produk dan halaman kebijakan dari backend.
GET /api/embed/configkunci publik + origin terdaftarKonfigurasi widget (dipakai widget; berguna untuk cek cepat).
POST /api/embed/shop-chatkunci publik + origin terdaftarPertanyaan pembeli ke widget.

Ganti kunci, atur URL callback, dan lihat meteran ada di halaman API-nya sendiri: api.nyataka.com → Kunci. Tidak ada endpoint untuk memutar kunci dari server — itu sengaja, supaya kunci tidak bisa diganti oleh kode yang mungkin sudah bocor.

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+, fetch bawaan)

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 berisi kode mesin, lihat bagian 8
  throw new Error(data.error);
}
console.log(data.reply); // balasan siap tampil

Isi permintaan

FieldWajibArti
textya, kecuali ada lampiranPesan masuk. Dipotong di 8.000 karakter. message juga diterima sebagai nama lain.
thread_idtidakPenanda percakapan milikmu (mis. id tiket/pelanggan). Diteruskan apa adanya di balasan.
modetidak"sync" (default) atau "async". Lihat bagian 4.
attachmentstidakArray [{ filename, mime, content_base64 }]. Maks 4 file, 4 MB per file.

Isi balasan (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 adalah Markdown dari Nata. reply_html sudah dirender jadi HTML, dan reply_text versi tanpa Markdown — pakai salah satu yang cocok dengan UI-mu. usage.input_tokens/output_tokens hanya untuk rekonsiliasi biaya di sisimu, bukan satuan yang kami tagih; messages_this_month dipakai untuk memantau ambang volume. backend adalah label internal kami: jangan dipakai untuk logika.

Meteran sendiri

curl https://app.nyataka.com/webhook/nata/usage \
  -H "Authorization: Bearer $NATA_API_KEY"
{
  "ok": true,
  "day": "2026-10-04",
  "resets_wib": "07:00",
  "credits": 488,
  "usage": { "messages": 12, "input_tokens": 8120, "output_tokens": 2330 }
}

4. Mode async dan callback

Permintaan berat (analisis, laporan panjang) bisa lebih lama dari kesabaran pengguna. Kirim mode: "async": kami menjawab 202 langsung dan mengirim hasilnya ke URL callback yang tersimpan di halaman API.

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: "Buat laporan penjualan minggu ini", thread_id: "ops-9", mode: "async" }),
});

Balasan callback adalah JSON yang sama dengan balasan sinkron (reply, reply_html, reply_text, thread_id), dikirim sebagai POST ke URL-mu.

Verifikasi tanda tangan callback

Setiap callback ditandatangani, jadi orang yang tahu URL callback-mu tetap tidak bisa menyuntikkan balasan palsu. Rahasianya adalah callback_signing_secret di halaman API (terbit bersama kunci dan tidak berubah saat kunci diganti; GET /api/web/webhook/status menampilkannya lagi kalau lupa), dikirim sebagai header:

HeaderIsi
X-Nyataka-TimestampUnix detik saat kami menandatangani. Tolak kalau selisihnya lebih dari 5 menit.
X-Nyataka-Signaturesha256=<hex>, HMAC-SHA256 dari <timestamp>.<body mentah> dengan secret itu.

Contoh verifikasi di Node (pakai body mentah, sebelum JSON.parse):

import crypto from "node:crypto";

// Signature = HMAC-SHA256(secret, `${X-Nyataka-Timestamp}.${rawBody}`)
export function verifyCallback(rawBody, headers, secret) {
  const timestamp = Number(headers["x-nyataka-timestamp"]);
  if (!Number.isFinite(timestamp) || Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
  const expected = "sha256=" + crypto.createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`).digest("hex");
  const a = Buffer.from(headers["x-nyataka-signature"] || "");
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

5. Kirim katalog sendiri

Nata menjawab pembeli dari data tokomu. Dua cara mengisinya: biarkan Nata membaca sitemap situsmu dari tab Akun (Baca katalog dari website), atau dorong datanya dari backend — lebih akurat untuk stok dan harga yang berubah cepat.

curl -X PUT https://app.nyataka.com/webhook/nata/knowledge \
  -H "Authorization: Bearer $NATA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "replace",
    "products": [
      { "id": "gm-1042", "title": "Gamis Lyla", "price_idr": 189000, "in_stock": true,
        "category": "gamis", "url": "https://tokomu.com/p/gamis-lyla",
        "description": "Bahan viscose, ukuran L dan XL." }
    ],
    "pages": [
      { "id": "retur", "title": "Kebijakan pengembalian", "url": "https://tokomu.com/retur",
        "body": "Retur 7 hari, barang belum dipakai." }
    ]
  }'
Field itemWajibArti
titleyaNama produk/halaman. Item tanpa title dilewati.
idtidakId stabil milikmu. Dipakai untuk mode: "upsert"; tanpa itu, url atau title jadi kuncinya.
price_idrtidakHarga rupiah, angka (tanpa titik).
in_stocktidaktrue/false. Dipakai Nata untuk bilang "habis" dengan jujur.
urltidakLink halaman produk.
description / bodytidakTeks yang dibaca Nata saat menjawab.
categorytidakKategori bebas, mis. gamis.

Yang didapat seller: Nata menjawab dari harga, stok, dan kebijakan asli tokomu, bukan menebak. Kalau katalog belum pernah dikirim, Nata akan bilang datanya belum ada — itu perilaku yang benar, bukan bug.

6. Widget dan origin yang diizinkan

Ambil snippetnya di app.nyataka.com → Integrasi → Widget di situs setelah mengisi nama toko dan daftar domain. Bentuknya:

<script src="https://app.nyataka.com/embed/nata-widget.js"
        data-key="nata_pub_KUNCI_PUBLIK_TOKO"
        data-mode="shopper" defer></script>
AtributDefaultArti
data-key—Kunci publik toko (nata_pub_…). Wajib untuk mode shopper.
data-modeshoppershopper untuk pembeli; seller untuk kamu di halaman admin (login akun Nyataka, bisa membaca teks halaman itu).
data-titleTanya NataJudul panel.
data-color#1d1c1dWarna aksen (#hex).
data-pushfalse"true": konten halaman digeser, bukan tertutup panel.
data-openfalse"true": panel terbuka sendiri waktu halaman dimuat.

Dari JavaScript halamanmu: window.NataWidget.open(), .close(), .toggle(), dan .ask("teks").

Endpoint yang dipanggil widget

Kalau kamu mau men-debug network tab atau membangun UI sendiri untuk pembeli:

EndpointIsi
GET /api/embed/config?key=nata_pub_…Nama toko, kontak, sapaan, dan saran pertanyaan. Berguna untuk memeriksa cepat apakah kunci dan origin sudah benar.
POST /api/embed/shop-chatBody { key, message, history? }. Balasan { ok, reply, reply_html, products }.

Keduanya memakai kunci publik dan hanya melayani origin yang kamu izinkan — bukan pengganti API server-to-server, dan tidak menerima kunci nata_whk_.

CORS dan daftar origin

Permintaan pembeli hanya dilayani kalau header Origin browser sama persis dengan salah satu baris di daftar Website yang boleh memasang widget. Tulis skema + host saja, tanpa path dan tanpa garis miring di akhir:

https://tokumu.com
https://www.tokumu.com

Origin yang tidak terdaftar mendapat 403 origin_not_allowed dan header CORS tidak dikirim — jadi di browser gejalanya sering muncul sebagai error CORS di console, bukan sebagai pesan error dari widget. Itu penyebab tersamar nomor satu; cek daftar origin dulu sebelum menuduh yang lain.

Subdomain berbeda = origin berbeda. https://www.tokumu.com tidak otomatis diizinkan oleh baris https://tokumu.com. Tambahkan keduanya kalau pembeli bisa datang dari dua-duanya.

Batas widget

6b. Copilot halaman (page agent)

Kalau halaman admin milikmu sendiri yang ingin dibantu Nata, kirim mode: "copilot" ke endpoint yang sama. Kamu mengirim apa yang terlihat di halaman; Nata membalas kalimat + daftar aksi yang boleh dijalankan halaman itu. Nata tidak punya browser di sini — halamanmu yang mengeksekusi.

POST /webhook/nata
Authorization: Bearer <API_KEY>

{
  "mode": "copilot",
  "session_id": "percakapan-1",
  "message": "Ubah nama toko jadi Rumaisha Hijab",
  "access_mode": "approval",
  "page": {
    "url": "https://admin.tokomu.com/pengaturan",
    "title": "Pengaturan Toko",
    "summary": "Form pengaturan. Nama toko: Lama.",
    "fields": [{ "name": "store_name", "label": "Nama toko", "value": "Lama" }],
    "elements": [{ "ref": "r1", "role": "button", "name": "Simpan" }]
  }
}

Balasannya memuat reply, actions, requires_approval, dan session_id. Aksi yang diizinkan hanya set_field (nama field harus ada di page.fields) dan click (ref harus ada di page.elements). Server membuang aksi ke Simpan/Publikasi/Hapus/Bayar/Kirim; halaman tetap wajib menampilkan diff dan meminta persetujuan bila requires_approval.

access_mode: read_only (tanpa aksi), approval (default, aksi menunggu persetujuan), full_access (aksi boleh langsung dijalankan). Satu panggilan copilot = satu kredit API. Panduan lengkap dan contoh page agent ada di docs/copilot-api.md di repo; kontrak ini berlaku untuk semua customer API, bukan hanya satu toko.

Kalau halamanmu punya banyak route, deklarasikan manifest sekali. Contoh nyata Rumaisha: 20 route, 86 field, 11 flow; pack-nya ada di docs/rumaisha-page-agent-pack.json. Manifest menyimpan struktur, bukan nilai field, password, atau data pembeli.

7. Batas dan kuota API

BatasNilaiKalau lewat
Panjang text8.000 karakterDipotong, bukan ditolak.
Ukuran body JSON8 MBPermintaan ditolak sebelum sampai ke Nata (batas server).
Lampiran4 file, 4 MB per file400 too_many_attachments / attachment_too_large.
Tipe lampiranJPEG, PNG, WebP, GIF, PDF, teks, CSV, JSON, XLSX, WAV, MP3, OGG, FLAC, AAC400 unsupported_attachment_type.
Batas pesan harian API3.334 pesan/hari (turunan dari volume 100.000/bulan; operator bisa menaikkannya per akun)429 daily_chat_quota. Reset 07.00 WIB. Ini bukan jatah chat paketmu.
Kredit APIprabayar, 1 pesan = 1 kredit402 quota_exhausted; balasan menyertakan sisa kredit.
Batas per menit per kuncibelum ada—

Dua pagar, dua hitungan. Pesan API memakai kuota harian API sendiri, bukan jatah chat harian paketmu — chat web dan API tidak saling memotong. Dua pagar itu: batas harian API di atas (429 daily_chat_quota), dan saldo prabayar satu kredit per pesan (402 quota_exhausted). Kunci tanpa saldo prabayar — mis. kunci lama yang dibuat sebelum kredit ada — tidak memotong kredit, tetapi batas harian API tetap berlaku. Angka nyata untuk akunmu ada di halaman API.

Harga

Ringkasnya ada di Biaya & paket di atas: API adalah paket terpisah, prabayar Rp 199 per pesan, tanpa biaya bulanan.

PemakaianHargaCatatan
Pesan teks masukRp 1991 pesan = 1 balasan. Top up minimum Rp 250.000.
Volume, mulai 50.000 pesan/bulanRp 175Perjanjian terpisah untuk partner.
Volume, mulai 100.000 pesan/bulanRp 149Perjanjian terpisah untuk partner.

Harga ini keputusan pemilik 2026-10-03 (docs product-strategy-tiers-api §4). Penagihan masih manual lewat tim: belum ada top up sendiri di app dan belum ada invoice otomatis. Untuk menambah kredit, email [email protected] dengan key_id-mu.

8. Error

Semua error berbentuk JSON { "error": "kode_mesin" }. error adalah kontrak yang stabil dan tidak berubah bahasa. Kalau ada teks untuk manusia, ia datang dua kali: message (Indonesia) dan message_en (Inggris). Kadang ada juga credits atau required. Kode di bawah ini yang benar-benar dikeluarkan hari ini.

API webhook

HTTPKodeArtinya & yang harus dilakukan
401missing_api_keyHeader Authorization kosong atau bukan format Bearer.
401invalid_api_keyKunci salah, sudah diganti, atau kunci lama. Buat kunci baru di halaman API.
400invalid_textTidak ada text (dan tidak ada lampiran) di body.
402quota_exhaustedKredit prabayar habis. Balasan menyertakan credits. Minta top up ke tim.
400callback_url_required_for_asyncmode: "async" dipakai padahal belum ada URL callback tersimpan.
429daily_chat_quotaBatas pesan harian API tersentuh (nama kode menyebut "chat"; di jalur API maksudnya batas API). Reset 07.00 WIB.
403webhook_channel_scale_onlyAkun ini belum punya langganan API. Nama kode ini menyebut paket karena sejarahnya; yang benar dibaca adalah required: "api_subscription" dan message/message_en di body.
403account_disabledAkun dinonaktifkan. Hubungi dukungan.
401unknown_sellerKunci menunjuk akun yang sudah tidak ada.
502agent_failedBackend Nata gagal menjawab. Kredit pesan itu dikembalikan; coba lagi.
504agent_timeoutNata kehabisan waktu. Kredit dikembalikan; coba lagi atau pakai mode: "async".
400page_cannot_commitHalaman belum punya simpan. Pilih mode Baca, atau tambahkan commit di page agent.
400attachments_not_supportedMode copilot tidak menerima lampiran; kirim hanya page dan message.
501copilot_not_configuredDeployment ini belum mengaktifkan copilot. Hubungi tim.
400too_many_attachments, attachment_too_large, unsupported_attachment_type, empty_attachment, bad_attachmentLampiran ditolak. Cek jumlah, ukuran, dan tipe di bagian 7.

Kunci/callback (dari halaman API)

HTTPKodeArtinya
403webhook_channel_scale_onlyAkun ini belum berhak membuat kunci.
404webhook_key_missingMenyimpan URL callback sebelum ada kunci.
400callback_url_must_be_httpsURL callback bukan https.
400invalid_callback_urlURL bukan alamat publik (localhost/IP privat/.local) atau tidak bisa diparse.

Katalog

HTTPKodeArtinya
401invalid_api_keyKunci tidak dikenali (termasuk kunci publik toko — untuk katalog pakai kunci nata_whk_).
400nothing_to_storeTidak ada item dengan title yang bisa disimpan.

Widget

HTTPKodeArtinya
400invalid_keydata-key hilang atau bukan format nata_pub_….
404widget_unavailableKunci tidak terdaftar atau widget dimatikan di halaman widget.
403origin_not_allowedOrigin pengirim tidak ada di daftar yang diizinkan.
403widget_plan_requiredPaket akun tidak mengizinkan widget.
400invalid_messagePesan kosong.
413message_too_longPesan lebih dari 1.600 karakter.
429slow_downBatas per IP atau per situs tersentuh. Coba lagi sebentar.
429store_daily_capKuota harian pembeli toko habis.
502unavailableNata gagal menjawab. Kuota pembeli dikembalikan.

9. Kalau macet

GejalaPenyebab paling mungkinPerbaikan
401 invalid_api_key setiap kaliKunci tersalin tidak utuh, ada tanda kutip/spasi ikut tersalin, atau kunci sudah digantiSalin ulang dari halaman API (kunci hanya tampil sekali — buat baru kalau perlu) dan pastikan header-nya persis Bearer nata_whk_…
Widget tidak muncul, console penuh error CORSOrigin tidak terdaftar, atau ditulis dengan path/trailing slashTambahkan https://host saja di daftar origin, muat ulang halaman
404 widget_unavailableKunci publik salah toko, atau widget dimatikanCek data-key dan centang "Aktifkan widget untuk pembeli"
403 widget_plan_requiredPaket turun/pendingCek paket di halaman Akun; hubungi tim kalau baru membayar
Callback tidak pernah datangURL bukan https publik, atau server callback lambat/redirectPakai https publik; balas 2xx segera; kami hanya mencoba 2 kali
Callback datang dua kaliKamu mengisi URL callback dan memakai mode sinkronPilih satu: kosongkan URL callback, atau pakai mode: "async"
429 daily_chat_quota padahal API baru dipakaiBatas harian API tersentuh (turunan volume 100.000/bulan), bukan jatah chat paketmuTunggu reset 07.00 WIB, atau minta batas harian lebih tinggi lewat [email protected]
402 quota_exhaustedKredit prabayar habisMinta top up ke [email protected]
Nata bilang datanya belum adaKatalog belum pernah dikirim/dibacaPUT /webhook/nata/knowledge atau "Baca katalog dari website" di halaman widget
Balasan terpotongBatas 8.000 karakter untuk pesan masukKirim pesan lebih pendek, atau ringkas di sisimu

10. Dukungan

Email [email protected] — sertakan key_id (bukan kunci rahasianya), waktu kejadian (WIB), endpoint yang dipanggil, dan body respons mentahnya. Untuk permintaan top up kredit, tulis "Top up kredit API" di subjek.