Z STORE Dokumentasi API

Memeriksa akses...

Sebentar, kami memeriksa akun kamu.

Dokumentasi API

Pesan Cek Plagiasi dan Cek AI Turnitin dari aplikasimu sendiri. Lengkap dengan parameter, contoh respons, dan contoh kode.

Pengantar

Dengan API ini kamu bisa memesan Cek Plagiasi Turnitin dan Cek AI Turnitin langsung dari aplikasimu sendiri: bot, website toko, atau skrip otomatis, tanpa perlu membuka dashboard setiap kali. API aktif selama add on Akses API Key di akunmu masih berlaku.

Base URL__BASE__/api/v1
FormatJSON (kecuali unggah PDF dan unduh hasil)
ProtokolHTTPS
PembayaranDipotong dari saldo akunmu. Tidak ada QRIS lewat API, jadi pastikan saldo cukup.

Tidak ada webhook. Setelah mengirim order, kamu memantau statusnya dengan polling sampai done, lalu mengunduh PDF hasilnya.

Mulai dari mana

  1. Aktifkan add on. Beli add on Akses API Key di menu Add On pada dashboard. Bisa dibayar dari saldo.
  2. Buat API key. Buka halaman API key, beri nama, lalu salin key yang muncul. Key hanya tampil sekali.
  3. Isi saldo. Order lewat API memotong saldo akunmu. Top up di dashboard bila perlu.
  4. Kirim order pertamamu. Salin fungsi pada bagian Klien siap pakai, lalu ikuti contoh di bagian Buat order.

Autentikasi

Setiap request wajib membawa API key. Pilih salah satu header:

HeaderIsi
AuthorizationBearer zs_live_xxxxxxxx (disarankan)
X-API-Keyzs_live_xxxxxxxx

Aturan key

  • Key dibuat di halaman API key. Key asli hanya tampil sekali, simpan di tempat aman.
  • Maksimal 2 key aktif per akun. Key yang dicabut langsung tidak berlaku.
  • Simpan key di server, jangan di kode yang berjalan di browser atau aplikasi mobile.
  • Allowlist IP (opsional): jika diisi, request dari IP lain ditolak dengan 403 ip_not_allowed.
  • Jika add on berakhir, semua key ditolak dengan 403 addon_expired. Key aktif lagi setelah add on diperpanjang.

Batas request

BatasNilaiJika terlewati
Per key30 request / menit429 rate_limited dan header Retry-After
Per IP300 request / menit429 too_many_requests
Key salah10 kali / menit / IP429 too_many_requests
Order aktif10 order API bersamaan429 too_many_active_orders
Order per hari100 order API per 24 jam429 daily_order_limit

Klien siap pakai

Contoh di halaman ini memakai fungsi kecil zstore() yang menambahkan header autentikasi. Simpan key di environment variable ZSTORE_KEY, lalu salin fungsi ini ke proyekmu.

<?php
const ZSTORE_BASE = '__BASE__/api/v1';

// Mengembalikan ['status' => int, 'body' => string]
function zstore(string $method, string $path, ?string $body = null, array $headers = []): array
{
    $headers[] = 'Authorization: Bearer ' . getenv('ZSTORE_KEY');
    $ctx = stream_context_create(['http' => [
        'method'        => $method,
        'header'        => implode("\r\n", $headers),
        'content'       => $body ?? '',
        'ignore_errors' => true,   // tetap baca body saat HTTP 4xx / 5xx
        'timeout'       => 60,
    ]]);
    $raw = file_get_contents(ZSTORE_BASE . $path, false, $ctx);
    preg_match('#HTTP/\S+\s+(\d{3})#', $http_response_header[0] ?? '', $m);
    return ['status' => (int) ($m[1] ?? 0), 'body' => $raw === false ? '' : $raw];
}
// Node.js 18 atau lebih baru (fetch bawaan)
const BASE = '__BASE__/api/v1';
const KEY  = process.env.ZSTORE_KEY;

// Mengembalikan objek Response. Pakai res.status, await res.json(), atau await res.arrayBuffer()
function zstore(method, path, { body, headers } = {}) {
  return fetch(BASE + path, {
    method,
    body,
    headers: { Authorization: `Bearer ${KEY}`, ...headers },
  });
}
import os
import requests

BASE = "__BASE__/api/v1"
session = requests.Session()
session.headers["Authorization"] = "Bearer " + os.environ["ZSTORE_KEY"]

# Mengembalikan objek Response. Pakai r.status_code, r.json(), atau r.content
def zstore(method, path, **kwargs):
    return session.request(method, BASE + path, timeout=60, **kwargs)

Daftar endpoint

MethodPathFungsi
GET/balanceCek saldo akun
GET/priceHarga dan batas tiap produk
POST/orderBuat order (unggah PDF)
GET/ordersDaftar order milik akun
GET/order?id=Detail satu order
GET/order/download?id=Unduh PDF hasil
POST/order/cancel?id=Batalkan order dan kembalikan saldo

Semua path di atas berada di bawah Base URL, misalnya __BASE__/api/v1/balance.

Cek saldo

GET/balanceMengembalikan saldo akun yang dipakai untuk membayar order.

Respons 200

{
  "ok": true,
  "username": "budi",
  "balance": 125000
}
$r = zstore('GET', '/balance');
$data = json_decode($r['body'], true);
echo $data['balance'];
const res = await zstore('GET', '/balance');
const data = await res.json();
console.log(data.balance);
data = zstore("GET", "/balance").json()
print(data["balance"])

Harga produk

GET/priceHarga dan batas tiap produk. Dipakai untuk menghitung biaya sebelum membuat order.

Parameter query

NamaWajibIsi
productTidakturnitin (bawaan) atau turnitin-ai

Respons 200

{
  "ok": true,
  "product": "Turnitin Check",
  "id": "turnitin",
  "price": 3000,
  "currency": "IDR",
  "products": [
    { "id": "turnitin",    "name": "Turnitin Check",  "price": 3000 },
    { "id": "turnitin-ai", "name": "Cek AI Turnitin", "price": 25000, "maxWords": 30000 }
  ]
}

Kolom products berisi semua produk yang bisa dipesan. maxWords adalah batas kata per dokumen untuk Cek AI.

$r = zstore('GET', '/price?product=turnitin-ai');
$data = json_decode($r['body'], true);
echo $data['price'], ' ', $data['currency'];
const res = await zstore('GET', '/price?product=turnitin-ai');
const data = await res.json();
console.log(data.price, data.currency);
data = zstore("GET", "/price", params={"product": "turnitin-ai"}).json()
print(data["price"], data["currency"])

Buat order

POST/orderMengunggah PDF dan membuat order. Saldo langsung dipotong sesuai harga produk.

Header

HeaderWajibIsi
Content-TypeYaapplication/pdf
Idempotency-KeyYaString unik 8 sampai 64 karakter (huruf, angka, _, -). Satu key untuk satu file.

Body

Isi file PDF mentah (bukan multipart, bukan base64). Ukuran maksimal 4 MB.

Parameter query

NamaBerlakuIsiBawaan
productSemuaturnitin atau turnitin-aiturnitin
filenameSemuaNama file yang tampil di order dan hasildocument.pdf
class_typeturnitinclassic atau newclassic
exclude_bibliographyturnitintrue / falsetrue
exclude_quotesturnitintrue / falsetrue
exclude_citedturnitintrue / false (hanya mode new)true
exclude_small_matchesturnitintrue / falsefalse
small_match_valueturnitinAngka. 0 berarti nilai standar0
small_match_unitturnitinkata atau persenkata
allow_deferredturnitinJika kuota harian penuh, proses setelah 00.00 WIBfalse

Cek AI (product=turnitin-ai) tidak memakai filter, class_type, maupun kuota harian. Dokumen dibatasi jumlah kata sesuai maxWords dari /price.

Respons 201: Cek Plagiasi

{
  "ok": true,
  "orderId": "TRN-1791697852995-B24A459A",
  "product": "turnitin",
  "status": "paid",
  "price": 3000,
  "balance": 122000,
  "deferred": false
}

Respons 201: Cek AI

{
  "ok": true,
  "orderId": "TRN-AI-1791708356221-4AEC3E80",
  "product": "turnitin-ai",
  "status": "manual_process",
  "price": 25000,
  "balance": 97000,
  "deferred": false,
  "wordCount": 12840
}

Respons 200: permintaan diulang

Jika Idempotency-Key yang sama dikirim lagi dengan file dan produk yang sama, API mengembalikan order yang sudah ada dan tidak memotong saldo lagi.

{
  "ok": true,
  "idempotent": true,
  "orderId": "TRN-1791697852995-B24A459A",
  "status": "processing"
}

Contoh: Cek Plagiasi

$pdf   = file_get_contents('skripsi.pdf');
$query = http_build_query([
    'filename'             => 'skripsi.pdf',
    'class_type'           => 'classic',
    'exclude_bibliography' => 'true',
    'exclude_quotes'       => 'true',
]);

$r = zstore('POST', '/order?' . $query, $pdf, [
    'Content-Type: application/pdf',
    'Idempotency-Key: skripsi-2026-0001',
]);

$data = json_decode($r['body'], true);
if ($r['status'] === 201 || $r['status'] === 200) {
    echo 'Order: ', $data['orderId'], PHP_EOL;
} else {
    echo 'Gagal: ', $data['error'], PHP_EOL;
}
import fs from 'node:fs';

const query = new URLSearchParams({
  filename: 'skripsi.pdf',
  class_type: 'classic',
  exclude_bibliography: 'true',
  exclude_quotes: 'true',
});

const res = await zstore('POST', `/order?${query}`, {
  body: fs.readFileSync('skripsi.pdf'),
  headers: {
    'Content-Type': 'application/pdf',
    'Idempotency-Key': 'skripsi-2026-0001',
  },
});

const data = await res.json();
if (res.status === 201 || res.status === 200) {
  console.log('Order:', data.orderId);
} else {
  console.error('Gagal:', data.error);
}
with open("skripsi.pdf", "rb") as f:
    pdf = f.read()

r = zstore(
    "POST", "/order",
    params={
        "filename": "skripsi.pdf",
        "class_type": "classic",
        "exclude_bibliography": "true",
        "exclude_quotes": "true",
    },
    data=pdf,
    headers={
        "Content-Type": "application/pdf",
        "Idempotency-Key": "skripsi-2026-0001",
    },
)

data = r.json()
if r.status_code in (200, 201):
    print("Order:", data["orderId"])
else:
    print("Gagal:", data["error"])

Contoh: Cek AI

$r = zstore('POST', '/order?product=turnitin-ai&filename=skripsi.pdf',
    file_get_contents('skripsi.pdf'),
    ['Content-Type: application/pdf', 'Idempotency-Key: skripsi-ai-0001']
);
$data = json_decode($r['body'], true);
echo $data['orderId'], ' (', $data['wordCount'] ?? '-', ' kata)';
const res = await zstore('POST', '/order?product=turnitin-ai&filename=skripsi.pdf', {
  body: fs.readFileSync('skripsi.pdf'),
  headers: { 'Content-Type': 'application/pdf', 'Idempotency-Key': 'skripsi-ai-0001' },
});
const data = await res.json();
console.log(data.orderId, `(${data.wordCount ?? '-'} kata)`);
r = zstore(
    "POST", "/order",
    params={"product": "turnitin-ai", "filename": "skripsi.pdf"},
    data=open("skripsi.pdf", "rb").read(),
    headers={"Content-Type": "application/pdf", "Idempotency-Key": "skripsi-ai-0001"},
)
data = r.json()
print(data["orderId"], "(%s kata)" % data.get("wordCount", "-"))

Daftar order

GET/ordersDaftar order milik akun, terbaru lebih dulu.

Parameter query

NamaIsiBawaan
limitJumlah data, 1 sampai 5020
offsetLewati sejumlah data awal0
productturnitin atau turnitin-ai untuk menyaring. Kosong berarti semua.-

Respons 200

{
  "ok": true,
  "total": 2,
  "limit": 20,
  "offset": 0,
  "orders": [
    {
      "orderId": "TRN-AI-1791708356221-4AEC3E80",
      "product": "turnitin-ai",
      "status": "done",
      "statusMsg": "Selesai.",
      "fileName": "skripsi.pdf",
      "price": 25000,
      "createdAt": "2026-10-11T12:45:56.221Z",
      "paidAt": "2026-10-11T12:45:56.221Z",
      "doneAt": "2026-10-11T13:02:10.000Z",
      "resultReady": true,
      "wordCount": 12840
    },
    {
      "orderId": "TRN-1791697852995-B24A459A",
      "product": "turnitin",
      "status": "done",
      "statusMsg": "Similarity 12%",
      "fileName": "skripsi.pdf",
      "price": 3000,
      "createdAt": "2026-10-11T09:50:52.995Z",
      "paidAt": "2026-10-11T09:50:52.995Z",
      "doneAt": "2026-10-11T10:03:41.000Z",
      "resultReady": true,
      "classType": "classic",
      "similarity": 12
    }
  ]
}
$r = zstore('GET', '/orders?limit=20&product=turnitin-ai');
foreach (json_decode($r['body'], true)['orders'] as $o) {
    echo $o['orderId'], ' ', $o['status'], PHP_EOL;
}
const res = await zstore('GET', '/orders?limit=20&product=turnitin-ai');
const { orders } = await res.json();
for (const o of orders) console.log(o.orderId, o.status);
r = zstore("GET", "/orders", params={"limit": 20, "product": "turnitin-ai"})
for o in r.json()["orders"]:
    print(o["orderId"], o["status"])

Detail order

GET/orderStatus dan hasil satu order. Dipakai untuk polling.

Parameter query

NamaWajibIsi
idYaID order, contoh TRN-AI-1791708356221-4AEC3E80

Respons 200

{
  "ok": true,
  "order": {
    "orderId": "TRN-1791697852995-B24A459A",
    "product": "turnitin",
    "status": "done",
    "statusMsg": "Similarity 12%",
    "fileName": "skripsi.pdf",
    "price": 3000,
    "createdAt": "2026-10-11T09:50:52.995Z",
    "paidAt": "2026-10-11T09:50:52.995Z",
    "doneAt": "2026-10-11T10:03:41.000Z",
    "resultReady": true,
    "classType": "classic",
    "similarity": 12
  }
}

Kolom order

KolomIsi
orderIdID order. Cek AI berawalan TRN-AI-, Cek Plagiasi berawalan TRN-
productturnitin atau turnitin-ai
statusLihat tabel status di bawah
statusMsgKeterangan status dalam bahasa Indonesia
resultReadytrue jika PDF hasil sudah bisa diunduh
similarityPersentase kemiripan. Hanya untuk Cek Plagiasi yang selesai, selain itu null
classTypeTampilan laporan. Hanya untuk Cek Plagiasi
wordCountJumlah kata dokumen. Hanya untuk Cek AI
createdAt, paidAt, doneAtWaktu format ISO 8601 (UTC). null jika belum terjadi
$r = zstore('GET', '/order?id=' . urlencode('TRN-1791697852995-B24A459A'));
$order = json_decode($r['body'], true)['order'];
echo $order['status'], ' ', $order['resultReady'] ? 'siap diunduh' : 'belum siap';
const res = await zstore('GET', '/order?id=TRN-1791697852995-B24A459A');
const { order } = await res.json();
console.log(order.status, order.resultReady ? 'siap diunduh' : 'belum siap');
r = zstore("GET", "/order", params={"id": "TRN-1791697852995-B24A459A"})
order = r.json()["order"]
print(order["status"], "siap diunduh" if order["resultReady"] else "belum siap")

Unduh hasil

GET/order/downloadMengunduh PDF hasil. Hanya bisa saat resultReady bernilai true.

Parameter query

NamaWajibIsi
idYaID order

Respons

Sukses mengembalikan 200 dengan Content-Type: application/pdf (isi file langsung, bukan JSON). Jika belum siap, respons 409 berikut:

{
  "ok": false,
  "error": "not_ready",
  "status": "processing"
}
$r = zstore('GET', '/order/download?id=' . urlencode($orderId));
if ($r['status'] === 200) {
    file_put_contents('hasil.pdf', $r['body']);
} else {
    echo 'Belum bisa diunduh: ', json_decode($r['body'], true)['error'];
}
import fs from 'node:fs';

const res = await zstore('GET', `/order/download?id=${orderId}`);
if (res.status === 200) {
  fs.writeFileSync('hasil.pdf', Buffer.from(await res.arrayBuffer()));
} else {
  console.error('Belum bisa diunduh:', (await res.json()).error);
}
r = zstore("GET", "/order/download", params={"id": order_id})
if r.status_code == 200:
    with open("hasil.pdf", "wb") as f:
        f.write(r.content)
else:
    print("Belum bisa diunduh:", r.json()["error"])

Batalkan order

POST/order/cancelMembatalkan order yang masih menunggu 00.00 WIB dan mengembalikan saldo penuh ke akunmu.

Parameter query

NamaWajibIsi
idYaID order buatan API milik akunmu

Kapan bisa dibatalkan

Status orderBisa dibatalkan?
paid_deferredYa, kapan saja sebelum mulai diproses setelah 00.00 WIB (hanya jalur ini yang bisa dibatalkan)
manual_processTidak. Order Cek AI dan order jalur manual tidak bisa dibatalkan sendiri
Status lainTidak. Respons 409 not_cancellable. Hubungi admin untuk refund

Hanya order yang dibuat lewat API yang bisa dibatalkan di sini. Pembatalan bersifat idempotent: memanggil ulang untuk order yang sudah dibatalkan tidak mengembalikan saldo dua kali.

Respons

{
  "ok": true,
  "orderId": "TRN-1791697852995-B24A459A",
  "status": "refunded",
  "refunded": 3000,
  "balance": 12000
}
$r = zstore('POST', '/order/cancel?id=' . urlencode($orderId));
$d = json_decode($r['body'], true);
echo $r['status'] === 200 ? 'Saldo kembali: ' . $d['refunded'] : 'Gagal: ' . $d['error'];
const res = await zstore('POST', `/order/cancel?id=${orderId}`);
const data = await res.json();
console.log(res.status === 200 ? `Saldo kembali: ${data.refunded}` : `Gagal: ${data.error}`);
r = zstore("POST", "/order/cancel", params={"id": order_id})
d = r.json()
print("Saldo kembali:", d["refunded"] if r.status_code == 200 else "Gagal: " + d["error"])

Contoh alur lengkap

Kirim PDF, tunggu sampai selesai, lalu simpan hasilnya. Cek status tiap 15 detik. Cek Plagiasi biasanya selesai dalam 5 sampai 20 menit, Cek AI diproses manual sehingga bisa lebih lama.

<?php
// Gunakan fungsi zstore() dari bagian "Klien siap pakai"
$pdf  = file_get_contents('skripsi.pdf');
$idem = 'skripsi-' . md5($pdf);            // satu key per file

$r = zstore('POST', '/order?filename=skripsi.pdf', $pdf, [
    'Content-Type: application/pdf',
    'Idempotency-Key: ' . $idem,
]);
$data = json_decode($r['body'], true);
if (!in_array($r['status'], [200, 201], true)) {
    exit('Gagal membuat order: ' . $data['error'] . PHP_EOL);
}
$orderId = $data['orderId'];
echo "Order dibuat: $orderId\n";

// Polling
while (true) {
    sleep(15);
    $order = json_decode(zstore('GET', '/order?id=' . urlencode($orderId))['body'], true)['order'];
    echo "Status: {$order['status']}\n";

    if ($order['resultReady']) {
        $dl = zstore('GET', '/order/download?id=' . urlencode($orderId));
        file_put_contents("hasil-$orderId.pdf", $dl['body']);
        echo "Hasil disimpan.\n";
        break;
    }
    if (in_array($order['status'], ['error', 'cancelled'], true)) {
        exit("Order berhenti: {$order['statusMsg']}\n");
    }
}
import fs from 'node:fs';
import crypto from 'node:crypto';
// Gunakan fungsi zstore() dari bagian "Klien siap pakai"

const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

const pdf  = fs.readFileSync('skripsi.pdf');
const idem = 'skripsi-' + crypto.createHash('md5').update(pdf).digest('hex'); // satu key per file

const created = await zstore('POST', '/order?filename=skripsi.pdf', {
  body: pdf,
  headers: { 'Content-Type': 'application/pdf', 'Idempotency-Key': idem },
});
const data = await created.json();
if (![200, 201].includes(created.status)) {
  throw new Error('Gagal membuat order: ' + data.error);
}
const orderId = data.orderId;
console.log('Order dibuat:', orderId);

// Polling
while (true) {
  await sleep(15000);
  const { order } = await (await zstore('GET', `/order?id=${orderId}`)).json();
  console.log('Status:', order.status);

  if (order.resultReady) {
    const dl = await zstore('GET', `/order/download?id=${orderId}`);
    fs.writeFileSync(`hasil-${orderId}.pdf`, Buffer.from(await dl.arrayBuffer()));
    console.log('Hasil disimpan.');
    break;
  }
  if (['error', 'cancelled'].includes(order.status)) {
    throw new Error('Order berhenti: ' + order.statusMsg);
  }
}
import hashlib
import time
# Gunakan fungsi zstore() dari bagian "Klien siap pakai"

with open("skripsi.pdf", "rb") as f:
    pdf = f.read()
idem = "skripsi-" + hashlib.md5(pdf).hexdigest()   # satu key per file

r = zstore(
    "POST", "/order",
    params={"filename": "skripsi.pdf"},
    data=pdf,
    headers={"Content-Type": "application/pdf", "Idempotency-Key": idem},
)
data = r.json()
if r.status_code not in (200, 201):
    raise SystemExit("Gagal membuat order: " + data["error"])
order_id = data["orderId"]
print("Order dibuat:", order_id)

# Polling
while True:
    time.sleep(15)
    order = zstore("GET", "/order", params={"id": order_id}).json()["order"]
    print("Status:", order["status"])

    if order["resultReady"]:
        dl = zstore("GET", "/order/download", params={"id": order_id})
        with open(f"hasil-{order_id}.pdf", "wb") as f:
            f.write(dl.content)
        print("Hasil disimpan.")
        break
    if order["status"] in ("error", "cancelled"):
        raise SystemExit("Order berhenti: " + order["statusMsg"])

Status order

StatusArtiFinal
paidDibayar, menunggu giliran diprosesTidak
processingSedang diprosesTidak
paid_deferredDibayar, akan diproses setelah 00.00 WIB (kuota harian penuh)Tidak
manual_processMenunggu diproses tim Z STORE (Cek AI selalu lewat jalur ini)Tidak
need_reviewSedang diperiksa tim Z STORETidak
doneSelesai. Unduh saat resultReady bernilai trueYa
errorGagal diproses. Saldo dikembalikan otomatis jika kegagalan dari sistemYa
cancelledDibatalkanYa
refundedDibatalkan dan saldo dikembalikan (lewat /order/cancel atau oleh admin)Ya

Kode error

Semua error memakai status HTTP yang sesuai dan body JSON berikut. Kolom message tidak selalu ada, jadikan error sebagai acuan di kodemu.

{
  "ok": false,
  "error": "insufficient_balance",
  "balance": 1500,
  "price": 3000
}
HTTPerrorArti dan tindakan
400invalid_productNilai product bukan turnitin atau turnitin-ai
400idempotency_key_requiredHeader Idempotency-Key kosong atau formatnya salah
400file_empty / file_must_be_pdfBody kosong, atau bukan file PDF
400too_many_wordsCek AI: dokumen melebihi batas kata. Lihat maxWords di respons
401invalid_api_keyKey salah atau sudah dicabut
402insufficient_balanceSaldo kurang. Respons berisi balance dan price. Top up di dashboard
403ip_not_allowedIP server tidak ada di allowlist key
403addon_expiredAdd on Akses API Key berakhir. Perpanjang di dashboard
403account_suspendedAkun ditangguhkan. Hubungi admin
403order_closedOrder sedang ditutup sementara
404order_not_foundID order tidak ada atau bukan milik akunmu
404product_not_foundProduk sedang tidak tersedia
409idempotency_key_conflictKey sama dipakai untuk file atau produk lain. Pakai key baru
409not_cancellableOrder sudah diproses atau lewat batas pembatalan. Hubungi admin untuk refund
409not_readyHasil belum siap diunduh. Cek status lalu coba lagi
413file_too_largeFile lebih dari 4 MB
415content_type_must_be_application_pdfHeader Content-Type harus application/pdf
429rate_limited / too_many_requestsTerlalu banyak request. Tunggu sesuai header Retry-After
429daily_order_limitBatas 100 order API per 24 jam tercapai. Coba lagi besok
429too_many_active_ordersSudah ada 10 order API aktif. Tunggu salah satu selesai
503maintenanceProduk sedang maintenance. Coba lagi nanti
503slot_fullKuota harian Cek Plagiasi penuh. Kirim ulang dengan allow_deferred=true
500internal_server_errorKesalahan di sisi server. Saldo tidak terpotong. Coba lagi dengan Idempotency-Key yang sama

Praktik terbaik

  • Satu Idempotency-Key per file. Jika koneksi putus saat mengirim, kirim ulang dengan key yang sama. Saldo tidak terpotong dua kali. Menurunkan key dari hash isi file adalah cara paling aman.
  • Polling secukupnya. Cukup cek status tiap 15 sampai 30 detik. Batas 30 request per menit per key dipakai bersama semua endpoint.
  • Hentikan polling di status final (done, error, cancelled, refunded).
  • Cek saldo sebelum mengirim. Bandingkan /balance dengan harga dari /price supaya tidak terkena 402.
  • Jaga key. Simpan di environment variable atau secret manager. Jika bocor, cabut di halaman API key lalu buat yang baru.
  • Salah kirim? Hanya order paid_deferred yang bisa dibatalkan lewat /order/cancel. Pastikan file sudah benar sebelum mengirim.
  • Pakai allowlist IP bila server kamu punya IP tetap.