Beyond Dynamic
Masuk
Dokumentasi

Dokumentasi API

Buat video atau gambar personal langsung dari sistem lain (CRM, e-commerce, aplikasi internal): kirim data pelanggan, terima satu MP4 (atau PNG/JPEG) dan satu tautan per baris.

Mau dipakai dari ChatGPT atau Claude? Lihat panduan connector AI (MCP).

Terakhir diperbarui 2 Oktober 2026

Alurnya

  1. Simpan desain di editor, lalu salin ID-nya dari alamat editor (/editor/[id]).
  2. Buat token API di Pengaturan → Token API (khusus pemilik ruang kerja).
  3. Kirim data ke POST /api/render/batch.
  4. Tunggu webhook render.finished, atau cek status lewat GET /api/render/jobs/[id].
  5. Unduh ZIP berisi semua hasil, atau ambil tautan per penerima.

Autentikasi

Kirim token di header Authorization pada setiap permintaan. Token bisa dicabut kapan saja di Pengaturan. Jangan simpan token di aplikasi browser atau mobile.

Autentikasi
Authorization: Bearer bd_…

Membuat video atau gambar

Satu permintaan = satu job. Setiap baris di rows menjadi satu video (atau satu gambar untuk desain gambar). Baris yang datanya tidak lengkap atau tidak valid dilewati dan dilaporkan; baris lain tetap dibuat.

FieldKeterangan
projectIdID desain yang tersimpan di ruang kerja ini. Versi terakhir yang disimpan yang dirender. Wajib, kecuali kamu mengirim project.
projectAlternatif projectId: seluruh JSON desain (seperti yang dikirim editor).
rowsDaftar baris data (1–1.000). Nilai boleh teks, angka, true/false, atau null (kosong).
columnMappingOpsional. Pasangan kolom data desain → nama field di rows. Jika tidak dikirim, setiap kolom data membaca field dengan nama yang sama.
fileNameTemplateOpsional. Pola nama file dari kolom data, mis. {{kota}}-{{nama_pelanggan}}.
webhookUrlOpsional. Alamat HTTPS yang dipanggil saat job selesai.

Contoh

Contoh
curl -X POST https://<server>/api/render/batch \
  -H "Authorization: Bearer $BEYOND_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "AbC123xyz",
    "rows": [
      { "nama_pelanggan": "Budi", "diskon": "20%", "kode_promo": "BUDI20" },
      { "nama_pelanggan": "Sari", "diskon": 25, "kode_promo": "SARI25" }
    ],
    "fileNameTemplate": "{{nama_pelanggan}}",
    "webhookUrl": "https://crm.contoh.id/hooks/beyond"
  }'

Desain gambar

projectId boleh menunjuk desain gambar (dibuat lewat Buat → Gambar). Job-nya membuat satu PNG (atau JPEG, sesuai pengaturan desain) per baris dengan antrean, tautan, ZIP, dan webhook yang sama; job.medium bernilai "image" dan nama file berakhiran .png/.jpg. Gambar dan video dihargai terpisah (GET /api/credits/pricing). Mengirim project langsung: sertakan "medium": "image" dan tepat satu scene tanpa audio.

Respons

202 berarti job sudah masuk antrean. Simpan job.id untuk mengecek status.

Respons
HTTP/1.1 202 Accepted

{
  "job": {
    "id": "Xq3vT9aLm2Pk",
    "kind": "batch",
    "medium": "video",
    "state": "queued",
    "total": 2,
    "done": 0,
    "failed": 0,
    "skipped": 0,
    "progress": 0,
    "errors": [],
    "error": null,
    "createdAt": "2026-10-01T08:00:00.000Z",
    "finishedAt": null,
    "downloadable": false,
    "deleteAfter": "2026-10-31T08:00:00.000Z",
    "voice": { "pending": 2, "total": 2 }
  },
  "credits": { "charged": 8, "videos": 4, "voice": 4, "balance": 42 }
}

Kode error umum:

  • 400 Isi permintaan tidak valid (detail di issues).
  • 401 Token tidak ada atau sudah dicabut.
  • 402 Kredit tidak cukup (params: saldo dan kebutuhan). Harga video (menurut durasi), gambar, dan halaman ada di GET /api/credits/pricing, suara AI dinamis dihitung per karakter; tukar kode promo di halaman Kredit.
  • 403 Desain memakai template, elemen, atau suara AI Pro, sementara paket ruang kerja Free.
  • 404 Desain tidak ditemukan di ruang kerja ini.
  • 413 Terlalu banyak baris atau video terlalu panjang.
  • 422 Tidak ada baris yang lengkap, atau ada media yang tidak bisa diambil.
  • 429 Terlalu banyak permintaan. Coba lagi setelah Retry-After detik.

Suara AI dinamis

Desain dengan suara AI yang berisi kolom data (mis. Halo {{nama_pelanggan}}) membuat satu suara per baris sebelum videonya dirender (job.voice menunjukkan baris yang masih menunggu suaranya). Biayanya per karakter setelah kolom diisi — spasi dan tanda baca dihitung, spasi/baris baru berurutan dihitung satu, tag <…> tidak — dibulatkan ke atas per suara, sesuai harga yang diatur admin. Estimasi menulis jumlahnya di voice.characters; voice.words masih dikirim tapi usang dan akan dihapus; suara yang sama persis tidak ditagih dua kali. Kredit suara dipotong bersama kredit video saat job dibuat (credits.voice) dan dikembalikan untuk baris yang suaranya gagal dibuat.

Untuk mengetahui biaya sebelum membuat job, kirim isi yang sama ke POST /api/render/estimate. Tidak ada yang masuk antrean dan tidak ada kredit yang dipotong.

Suara AI dinamis
POST /api/render/estimate   (same body as /api/render/batch)

HTTP/1.1 200 OK

{
  "estimate": {
    "medium": "video",
    "items": 2,
    "creditsPerItem": 2,
    "durationSeconds": 12.5,
    "credits": 4,
    "videos": 2,
    "voice": { "clips": 2, "characters": 48, "words": 8, "credits": 4 },
    "total": 8,
    "balance": 50
  }
}

Harga kredit

Harga diatur admin dan bisa berubah; ambil yang berlaku sekarang dari GET /api/credits/pricing (sesi atau token API). Harga video mengikuti durasi video jadi: rentang dengan batas atas upToSeconds (inklusif, naik), rentang terakhir (null) untuk semua video yang lebih panjang. Job menyimpan harga saat dibuat, jadi perubahan harga tidak memengaruhi job yang sudah berjalan atau pengembaliannya. Estimasi membawa creditsPerItem dan durationSeconds (null untuk gambar). Tiap suara AI punya harga per karakter sendiri; voice.fromCreditsPerChar adalah yang termurah (null bila suara AI mati). Kredit dibeli (top up) dan langganan Pro dibayar pemilik ruang kerja di halaman Kredit (invoice, QRIS/e-wallet/kartu/gerai retail); tidak ada API publik untuk pembayaran.

Harga kredit
GET /api/credits/pricing

HTTP/1.1 200 OK

{
  "pricing": {
    "video": [
      { "upToSeconds": 10, "credits": 1 },
      { "upToSeconds": 30, "credits": 2 },
      { "upToSeconds": null, "credits": 5 }
    ],
    "image": 1,
    "landingPage": 1
  },
  "voice": { "fromCreditsPerChar": 0.08 }
}

Status dan hasil

state berubah dari queued ke rendering lalu done (atau failed/cancelled). progress bernilai 0–1. Hasil tersedia setelah job selesai dan dihapus otomatis sesuai masa simpan.

  • GET /api/render/jobs/{id} Status job dan masalah per baris.
  • GET /api/render/jobs/{id}/download ZIP berisi semua MP4 atau gambar, _laporan.txt, _tautan.csv, dan _tautan.xlsx.
  • GET /api/render/jobs/{id}/links Tautan tonton per penerima (halaman tonton menampilkan gambar untuk job gambar). Sebagai file dengan ?format=csv atau ?format=xlsx.

Campaign

Campaign menyimpan satu desain, daftar penerima, dan hasil render per baris. Buat campaign di menu Campaign (atau POST /api/campaigns), lalu dari CRM kamu tambahkan penerima dan mulai render. Kunci tiap baris adalah nama kolom data desain dan kolom tambahan campaign (bukan judul kolom CSV). Hanya satu render per campaign yang bisa berjalan; Mulai saat render lain berjalan dijawab 409.

  • GET /api/campaigns Daftar campaign (?projectId= untuk satu desain).
  • POST /api/campaigns Buat campaign: name, description, kind (video | landing | landing_video | image | landing_image), projectId (desain video untuk video/landing_video, desain gambar untuk image/landing_image), landingPageId (landing page), slug (wajib untuk jenis landing; unik global), fields, fileNameTemplate.
  • PUT /api/campaigns/{id}/rows Tambah (mode append) atau ganti (mode replace) penerima, maksimal 500 baris per permintaan.
  • GET /api/campaigns/{id}/rows Semua baris dengan status (pending, invalid, queued, voice, rendering, done, failed, skipped, cancelled, live), keterangan, tautan tonton, slug, dan pageUrl (halaman penerima).
  • POST /api/campaigns/{id}/estimate Perkiraan kredit untuk Mulai, tanpa memotong kredit.
  • POST /api/campaigns/{id}/start Mulai render; kredit dipotong saat itu (402 creditInsufficient jika kurang).
  • POST /api/campaigns/{id}/cancel Batalkan render yang berjalan; kredit untuk hasil yang belum jadi dikembalikan.
  • GET|POST /api/campaigns/{id}/publish Jenis landing: GET = perkiraan (halaman, halaman baru, kredit); POST = terbitkan semua baris valid dengan landing page versi sekarang. Landing: harga per halaman baru dari GET /api/credits/pricing (402 jika kurang); landing_video dan landing_image: gratis. Opsional webhookUrl → event campaign.published.
  • POST /api/campaigns/{id}/unpublish Hentikan semua halaman campaign (404) tanpa menghapus data; terbitkan ulang tidak menagih lagi.
  • GET /api/campaigns/slug-available Cek apakah slug campaign bisa dipakai (?slug=, opsional &campaign= untuk campaign yang sedang diubah).
  • GET /api/campaigns/{id}/export CSV berisi semua kolom, status, keterangan, file, tautan tonton, dan jumlah tayang; Excel dengan ?format=xlsx.
  • GET /api/campaigns/{id}/analytics Analitik (Phase 40): penerima, siap, ditonton/dilihat/diklik (unik = baris, total = kejadian), email terkirim/diklik, WhatsApp terkirim/diterima/dibaca/gagal per broadcast dan channel, dan angka per hari dalam zona waktu ?tz= (IANA, bawaan Asia/Jakarta). Kunjungan tim sendiri dan pratinjau tautan tidak dihitung.
  • GET /api/campaigns/{id}/analytics/rows Keterlibatan per baris (?cursor=, ?limit= ≤ 1000): jumlah tonton, waktu pertama/terakhir, halaman, status email dan WhatsApp terakhir — tanpa nilai kolom data. nextCursor = halaman berikutnya.
  • GET /api/campaigns/{id}/analytics/export CSV analitik per baris; waktu dalam ?tz=, kolom data hanya dengan ?data=1; Excel dengan ?format=xlsx.
Contoh campaign
# 1. Add recipients (keys = data fields of the design + extra fields; ≤ 500 rows per request)
curl -X PUT https://<server>/api/campaigns/Cmp42aBcD9xy/rows \
  -H "Authorization: Bearer $BEYOND_TOKEN" -H "Content-Type: application/json" \
  -d '{ "mode": "append", "rows": [ { "nama_pelanggan": "Budi", "no_hp": "0812 3456 7890" } ] }'

# 2. Render every row without a video yet ("scope": "pending" | "all" | "rows" + "rows": [0, 4])
curl -X POST https://<server>/api/campaigns/Cmp42aBcD9xy/start \
  -H "Authorization: Bearer $BEYOND_TOKEN" -H "Content-Type: application/json" \
  -d '{ "scope": "pending", "webhookUrl": "https://crm.contoh.id/hooks/beyond" }'

HTTP/1.1 202 Accepted
{ "campaign": { "id": "Cmp42aBcD9xy", "status": "running", … }, "job": { "id": "Xq3vT9aLm2Pk", … },
  "credits": { "charged": 2, "videos": 2, "voice": 0, "balance": 48 } }

Berbeda dengan /api/render/batch, data penerima campaign disimpan di server selama campaign ada (atau sampai masa simpan server habis) dan ikut terhapus saat campaign dihapus. Webhook dari render campaign menyertakan campaignId.

Campaign jenis landing, landing_video, dan landing_image membuat satu halaman per baris di /campaign/<slug campaign>/<slug baris>. Kolom slug wajib di tiap baris: nilainya dinormalkan (Budi Santoso → budi-santoso) dan slug yang sama diberi kode acak. Halaman hanya menampilkan kolom data landing page (kolom tambahan tidak pernah tampil), tidak diindeks mesin pencari, dan tautan tombol dicatat lewat pengalihan untuk statistik klik. Di landing_image, gambar personal baris juga menjadi gambar pratinjau tautan (og:image).

Contoh campaign landing page
# 1. A landing page campaign (the slug is the first part of every recipient's URL)
curl -X POST https://<server>/api/campaigns \
  -H "Authorization: Bearer $BEYOND_TOKEN" -H "Content-Type: application/json" \
  -d '{ "name": "Promo Oktober", "kind": "landing", "landingPageId": "Lp7sK2mQ9xYz", "slug": "promo-oktober" }'

# 2. Recipients need a slug (their page address)
curl -X PUT https://<server>/api/campaigns/Cmp42aBcD9xy/rows \
  -H "Authorization: Bearer $BEYOND_TOKEN" -H "Content-Type: application/json" \
  -d '{ "mode": "append", "rows": [ { "slug": "budi-santoso", "nama_pelanggan": "Budi" } ] }'

# 3. Publish: https://<server>/campaign/promo-oktober/budi-santoso
curl -X POST https://<server>/api/campaigns/Cmp42aBcD9xy/publish \
  -H "Authorization: Bearer $BEYOND_TOKEN" -H "Content-Type: application/json" -d '{}'

HTTP/1.1 200 OK
{ "campaign": { "id": "Cmp42aBcD9xy", "status": "live", … }, "credits": { "charged": 1, "balance": 48 } }

Webhook

Saat job selesai, kami mengirim POST JSON ke webhookUrl. Setiap kiriman ditandatangani dengan rahasia webhook ruang kerja kamu (Pengaturan → Token API → Rahasia webhook).

Contoh kiriman webhook
POST <webhookUrl>
Content-Type: application/json
X-Beyond-Event: render.finished
X-Beyond-Timestamp: 1790000000
X-Beyond-Signature: v1=5d41402abc4b2a76b9719d911017c592…

{
  "event": "render.finished",
  "job": { "id": "Xq3vT9aLm2Pk", "state": "done", "total": 2, "done": 2, … },
  "campaignId": "Cmp42aBcD9xy",
  "downloadPath": "/api/render/jobs/Xq3vT9aLm2Pk/download",
  "linksPath": "/api/render/jobs/Xq3vT9aLm2Pk/links"
}

Webhook campaign.published (dari POST …/publish dengan webhookUrl) memakai tanda tangan yang sama, berisi campaignId, publishedAt, dan pages: [{ row, slug, pagePath }]. Webhook render.finished dari campaign landing_video menyertakan pages: [{ row, pagePath }].

Cara memeriksa di sistem penerima: hitung HMAC-SHA256 dari timestamp, titik, lalu body mentah, dengan rahasia webhook sebagai kunci. Bandingkan hasilnya (hex) dengan nilai setelah v1=, dan tolak kiriman yang timestamp-nya berselisih lebih dari 5 menit dari jam server kamu.

Contoh verifikasi
// Node.js (Express): use the raw body, not re-serialised JSON.
import { createHmac, timingSafeEqual } from "node:crypto";

app.post("/hooks/beyond", express.raw({ type: "application/json" }), (req, res) => {
  const timestamp = req.get("X-Beyond-Timestamp");
  const signature = req.get("X-Beyond-Signature") ?? "";
  const body = req.body.toString("utf8");
  const expected = "v1=" + createHmac("sha256", process.env.BEYOND_WEBHOOK_SECRET)
    .update(`${timestamp}.${body}`)
    .digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) <= 300;
  const valid = fresh && expected.length === signature.length
    && timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
  if (!valid) return res.sendStatus(401);
  const { job } = JSON.parse(body);
  // … update the CRM with job.id / job.state
  res.sendStatus(204);
});

Webhook dikirim sekali (batas waktu 10 detik). Jika sistem kamu sedang mati, cek status job lewat API.

Batasan

  • Setiap video dan gambar memakai kredit sesuai harga saat ini (video menurut durasinya; lihat GET /api/credits/pricing), dipotong saat job masuk antrean (respons berisi credits.charged dan credits.balance). Yang gagal atau dibatalkan dikembalikan otomatis.
  • 20 permintaan render per menit.
  • Maksimal 1.000 baris per job dan 5 menit per video (bisa berbeda di server kamu).
  • Media (gambar, video) di data harus bisa diakses publik lewat HTTPS; alamat jaringan internal ditolak.
  • Data baris hanya dipakai saat render dan dihapus setelah video selesai.
  • Suara AI dinamis memakai kredit per karakter, termasuk spasi (dibulatkan ke atas per suara), di luar kredit video; cek dulu dengan POST /api/render/estimate.