Dokumentasi/App Hosting
Panduan TerverifikasiApp HostingID

Pesan App Hosting lewat API

Panduan langkah demi langkah memesan dan men-deploy aplikasi App Hosting dengan API key organisasi: dari membuat key sampai membatalkan pesanan.

7 menit baca•Diperbarui 24/10/2026•/app-hosting/order-by-api

Untuk developer. Panduan ini butuh sedikit pemrograman. Kalau Anda hanya memakai console, mulai dari App Hosting: Online-kan Aplikasi Pertama Anda.

Panduan ini membawa Anda dari nol sampai aplikasi berjalan, hanya dengan HTTP request. Semua contoh memakai https://pfnapp.id. Daftar lengkap endpoint ada di API Reference.

Perhatian: langkah 4 memotong saldo organisasi Anda dengan uang sungguhan. Cek harga dan saldo dulu (langkah 2 dan 3).

flowchart LR
  A[1. Buat API key] --> B[2. Cek saldo]
  B --> C[3. Pilih plan dan template]
  C --> D[4. Kirim pesanan]
  D --> E[5. Pantau status]
  E --> F[6. Batalkan bila perlu]

1. Buat API key

  1. Buka Console > Kunci API (/console/api-keys) dan klik Buat kunci API.
  2. Isi Nama (misalnya order-app-hosting), pilih produk App Hosting atau Semua produk, dan akses Tulis.
  3. Klik Buat kunci, lalu salin kunci yang muncul. Kunci hanya ditampilkan sekali.

Daftar kunci API

Dialog buat kunci API

Kunci ditampilkan satu kali

Aturan key:

  • Akses Tulis diperlukan untuk memesan dan membatalkan. Akses Baca hanya bisa melihat data, dan mencoba memesan dengan key Baca menghasilkan 403 API_KEY_ACCESS_NOT_ALLOWED.
  • Hanya Owner organisasi yang bisa membuat key Tulis.
  • Key produk lain (misalnya WhatsApp) ditolak dengan 403 API_KEY_PRODUCT_NOT_ALLOWED.

Simpan key di variabel lingkungan, jangan di kode atau repositori:

BASH
export PFN_API_KEY="app_live_..."

Semua request berikutnya memakai header Authorization: Bearer $PFN_API_KEY.


2. Cek saldo

BASH
curl -s https://pfnapp.id/api/billing/account \
  -H "Authorization: Bearer $PFN_API_KEY"

Respons:

JSON
{ "ok": true, "currency": "IDR", "balance": "7107659.35" }

Bila saldo kurang saat memesan, API menjawab 402 INSUFFICIENT_BALANCE beserta topupUrl, dan tidak ada yang dibuat.


3. Pilih plan, region, dan template

Harga dan plan

BASH
curl -s https://pfnapp.id/api/billing/catalog \
  -H "Authorization: Bearer $PFN_API_KEY"

Cari produk dengan code APP_HOSTING. Dari sana ambil dua hal:

  • plans[].id, untuk resourcePlanId. Perhatikan juga plans[].resources.provisioning.storage (GB), karena sebagian template butuh ruang minimum.
  • plans[].offers[].regionId, untuk regionId. Tiap offer juga memuat billingPeriod dan periodPrice.

Ringkasan bentuk respons (dipotong):

JSON
1{
2 "currency": "IDR",
3 "products": [
4 {
5 "code": "APP_HOSTING",
6 "plans": [
7 {
8 "id": "<plan-id>",
9 "code": "PRO",
10 "name": "Pro",
11 "resources": {
12 "provisioning": { "cpu": 1000, "memory": 4096, "storage": 10 }
13 },
14 "offers": [
15 {
16 "billingPeriod": "MONTHLY",
17 "periodPrice": "75000",
18 "regionId": "<region-id>",
19 "regionCode": "INDONESIA"
20 }
21 ]
22 }
23 ]
24 }
25 ]
26}

Template

BASH
curl -s https://pfnapp.id/api/templates \
  -H "Authorization: Bearer $PFN_API_KEY"

Endpoint ini mengembalikan array polos (tanpa ok). Gunakan slug template sebagai templateId, misalnya 9router.

Plan terlalu kecil untuk template menghasilkan 422 INSUFFICIENT_PLAN_STORAGE dan tidak ada biaya. Template 9router butuh 10 GB, jadi plan 5 GB (Small, Starter) ditolak dan plan Pro ke atas diterima.


4. Kirim pesanan

Endpoint: POST /api/deploy/submit. Header Idempotency-Key wajib untuk API key. Buat satu nilai unik per pesanan, misalnya UUID.

cURL

BASH
1curl -X POST "https://pfnapp.id/api/deploy/submit" \
2 -H "Authorization: Bearer $PFN_API_KEY" \
3 -H "Idempotency-Key: $(uuidgen)" \
4 -H "Content-Type: application/json" \
5 -d '{
6 "sourceType": "MANAGED_TEMPLATE",
7 "templateId": "9router",
8 "name": "app-saya",
9 "resourcePlanId": "<plan-id>",
10 "billingPeriod": "MONTHLY",
11 "periodMonths": 1,
12 "regionId": "<region-id>"
13 }'

Node.js / TypeScript

TYPESCRIPT
1import { randomUUID } from "node:crypto"
2
3const res = await fetch("https://pfnapp.id/api/deploy/submit", {
4 method: "POST",
5 headers: {
6 Authorization: `Bearer ${process.env.PFN_API_KEY}`,
7 "Idempotency-Key": randomUUID(),
8 "Content-Type": "application/json",
9 },
10 body: JSON.stringify({
11 sourceType: "MANAGED_TEMPLATE",
12 templateId: "9router",
13 name: "app-saya",
14 resourcePlanId: "<plan-id>",
15 billingPeriod: "MONTHLY",
16 periodMonths: 1,
17 regionId: "<region-id>",
18 }),
19})
20console.log(res.status, await res.json())

Isi body

FieldKeterangan
sourceTypeTEMPLATE, MANAGED_TEMPLATE, atau PUBLIC. GITHUB ditolak dengan 422 SOURCE_TYPE_NOT_ALLOWED_FOR_KEY.
templateIdslug dari GET /api/templates.
nameNama aplikasi.
resourcePlanIdplans[].id dari katalog.
billingPeriod, periodMonthsMisalnya MONTHLY dan 1.
regionIdoffers[].regionId dari katalog.

Mode tagihan ditentukan server dari katalog, jadi billingMode yang Anda kirim diabaikan.

Respons sukses

JSON
1{
2 "ok": true,
3 "data": {
4 "stackId": "...",
5 "stackSlug": "app-saya",
6 "deploymentId": "...",
7 "status": "...",
8 "hourlyCost": "..."
9 }
10}

Saldo dipotong dan deployment pertama langsung berjalan.


5. Idempotency: aman untuk retry

SituasiHasil
Request sama dengan Idempotency-Key samaJawaban pertama dikembalikan, dengan header Idempotent-Replayed: true. Tidak memesan dua kali.
Idempotency-Key sama dengan body berbeda422 IDEMPOTENCY_KEY_REUSED
Request pertama masih berjalan409 IDEMPOTENCY_IN_PROGRESS
Pesanan gagalKey dibebaskan, Anda boleh mengulang dengan key yang sama

Key disimpan 24 jam. Jika koneksi putus setelah mengirim pesanan, kirim ulang request yang sama persis dengan Idempotency-Key yang sama. Jangan membuat key baru, karena itu akan memesan lagi.


6. Pantau status

BASH
curl -s https://pfnapp.id/api/deploy/apps/app-saya \
  -H "Authorization: Bearer $PFN_API_KEY"

Respons memuat data.stack (status aplikasi, resourcePlanId, subdomain) dan data.latestDeployment (status deployment terbaru). Ulangi sampai status menjadi running.

Aplikasi yang sama juga muncul di console:

Aplikasi hasil pesanan lewat API


7. Batalkan

BASH
curl -X POST "https://pfnapp.id/api/deploy/apps/app-saya/cancel" \
  -H "Authorization: Bearer $PFN_API_KEY" \
  -H "Content-Type: application/json"

Tidak perlu body. Pembatalan dijadwalkan di akhir periode berjalan. Aplikasi tetap aktif sampai tanggal itu, lalu semua resource-nya dihapus permanen, dan tidak ada refund. Ekspor data penting sebelum tanggal itu.


8. Biaya periode pertama

Periode pertama dihitung prorata sampai akhir bulan. Contoh dari uji coba: plan Pro dengan harga bulanan Rp75.000 yang dipesan pada tanggal 16 (16 dari 31 hari) ditagih Rp38.709,68. Setelah itu penagihan mengikuti periode yang Anda pilih.


9. Daftar error

StatusKodeArtinya
400IDEMPOTENCY_KEY_REQUIRED, IDEMPOTENCY_KEY_INVALID, atau PARSE_ERRORHeader Idempotency-Key tidak ada atau formatnya salah, atau JSON rusak.
401UNAUTHORIZEDKey tidak ada, salah, atau sudah dicabut.
402INSUFFICIENT_BALANCESaldo kurang. Buka topupUrl dari respons.
403API_KEY_PRODUCT_NOT_ALLOWEDKey milik produk lain (misalnya VPN-only atau WhatsApp), bukan App Hosting.
403API_KEY_ACCESS_NOT_ALLOWEDKey akses Baca dipakai untuk memesan atau membatalkan.
409IDEMPOTENCY_IN_PROGRESSRequest dengan key yang sama masih diproses.
422IDEMPOTENCY_KEY_REUSEDKey yang sama dipakai dengan body berbeda.
422SOURCE_TYPE_NOT_ALLOWED_FOR_KEYGITHUB tidak boleh lewat API key.
422INSUFFICIENT_PLAN_STORAGEPlan terlalu kecil untuk template.
429RATE_LIMITEDMaksimal 60 request per menit per key.

10. Praktik aman

  • Simpan key di secret manager atau variabel lingkungan, bukan di kode.
  • Pakai key Baca untuk dashboard atau monitoring, dan key Tulis hanya untuk proses yang memesan.
  • Buat Idempotency-Key baru untuk setiap pesanan, dan pakai ulang hanya saat me-retry pesanan yang sama.
  • Cabut key yang tidak dipakai di Console > Kunci API. Panggilan dengan key yang dicabut langsung menjawab 401.