# Earnice API v1 — Dokumentasi Lengkap

> Dokumen ini ditujukan untuk developer **dan agen AI (mis. Claude Code)** yang membangun integrasi
> dengan Earnice dari proyek web lain. Isinya diturunkan dari kode API yang berjalan di produksi
> (versi 31 Oktober 2026).

---

## 1. Ringkasan

Earnice adalah platform **CPA shortlink** berbahasa Indonesia: pemilik akun menempel URL tujuan,
mendapat **link pendek**, lalu membagikannya. Pengunjung yang membuka link pendek mengerjakan
**misi** singkat dari jaringan pengiklan; setelah misi selesai, URL tujuan terbuka dan pemilik link
mendapat bagi hasil di dashboard.

API v1 memungkinkan program lain, atas nama **satu akun Earnice**, untuk:

| Bisa | Tidak bisa |
|---|---|
| Melihat info akun dasar (`GET /me`) | Menghapus atau mengubah link |
| Melihat daftar link milik akun (`GET /links`) | Melihat saldo, pendapatan, atau riwayat kredit |
| Melihat detail dan statistik klik/misi satu link (`GET /links/{code}`) | Mengajukan atau melihat penarikan saldo |
| Membuat shortlink baru (`POST /links`) | Mengakses postback atau data jaringan pengiklan |
| | Mengakses link milik akun lain, membuat akun, atau login atas nama user |

**Base URL**

```
https://earnice.com/api/v1
```

- Hanya HTTPS. Gunakan host `earnice.com` (tanpa `www`).
- Semua respons berformat JSON (`Content-Type: application/json; charset=utf-8`), tidak di-cache
  (`Cache-Control: no-store`).
- API tidak memakai cookie/sesi. Setiap permintaan wajib membawa token.

---

## 2. Autentikasi

### 2.1 Mendapatkan token

Token dibuat oleh **pemilik akun** dari situs, bukan lewat API:

1. Masuk ke https://earnice.com/sign-in
2. Buka menu **API** di dashboard (`https://earnice.com/api-token`).
3. Isi **Nama** token (mis. `blog-saya-produksi`), klik **Buat token**.
4. **Salin token saat itu juga**. Token hanya ditampilkan **sekali**. Server hanya menyimpan
   hash-nya; token yang hilang tidak bisa dilihat lagi dan harus dibuat baru.

Aturan token:

- Format: `en_` diikuti **40 karakter heksadesimal huruf kecil**, total 43 karakter.
  Regex: `^en_[0-9a-f]{40}$`
- Maksimal **5 token aktif** per akun. Cabut salah satu (tombol **Cabut**) untuk membuat yang baru.
- Token yang dicabut langsung tidak berlaku.
- Token tidak bisa dipakai bila akun diblokir.

### 2.2 Mengirim token

Kirim di header HTTP **`Authorization`** dengan skema **Bearer**:

```
Authorization: Bearer en_0123456789abcdef0123456789abcdef01234567
```

Token **tidak** diterima lewat query string, cookie, atau body.

### 2.3 Keamanan token (WAJIB dipatuhi integrasi)

- **Simpan token hanya di server** (variabel lingkungan / secret manager), mis. `EARNICE_API_TOKEN`.
  Jangan commit ke git, jangan tulis ke log, jangan kirim ke browser.
- **Jangan panggil API langsung dari JavaScript di browser.** Walau server mengirim
  `Access-Control-Allow-Origin: *`, memanggil dari browser berarti token terlihat oleh pengunjung.
  Selalu lewat backend proyek Anda.
- Satu token per lingkungan/proyek (mis. `produksi`, `staging`) agar mudah dicabut tanpa
  mengganggu yang lain.
- Bila token bocor: cabut di halaman **API** dashboard, lalu buat yang baru.

---

## 3. Format respons

### 3.1 Sukses

```json
{
  "ok": true,
  "data": { ... }
}
```

Untuk daftar berhalaman, ada tambahan `meta` (lihat `GET /links`).

### 3.2 Gagal

```json
{
  "ok": false,
  "error": {
    "code": "validation_failed",
    "message": "Data tidak valid.",
    "details": {
      "url": "Link tujuan harus diawali http:// atau https://"
    }
  }
}
```

- `error.code`: kode mesin yang stabil; **gunakan ini untuk logika program**.
- `error.message`: pesan berbahasa Indonesia untuk manusia (boleh berubah).
- `error.details`: opsional; peta `nama_field` ke pesan untuk kesalahan validasi.

### 3.3 Objek Link

Semua endpoint link mengembalikan objek dengan bentuk berikut:

```json
{
  "code": "a1b2c3",
  "short_url": "https://earnice.com/a1b2c3",
  "original_url": "https://contoh.com/artikel-saya",
  "link_type": 1,
  "hits": 42,
  "created_date": "2026-10-31 14:30:05",
  "last_visit_date": "2026-10-31 15:02:11",
  "created_via": "api",
  "source": "telegram-bot"
}
```

| Field | Tipe | Keterangan |
|---|---|---|
| `code` | string | Kode unik link pendek. Link buatan API: 6 karakter heksadesimal huruf kecil. Link lama bisa berbeda (maks 10 karakter alfanumerik). |
| `short_url` | string | URL yang dibagikan ke pengunjung. |
| `original_url` | string | URL tujuan yang terbuka setelah pengunjung menyelesaikan misi. |
| `link_type` | integer | Jenis link. Saat ini selalu `1` (CPA shortlink). Hanya informasi, tidak bisa dipilih lewat API. |
| `hits` | integer | Jumlah kunjungan ke link pendek. |
| `created_date` | string | Waktu dibuat, format `YYYY-MM-DD HH:MM:SS`, **zona waktu server (WIB, UTC+7)**, tanpa offset. |
| `last_visit_date` | string atau null | Kunjungan terakhir, format sama; `null` bila belum pernah dikunjungi. |
| `created_via` | string atau null | Dari mana link dibuat: `"web"` (form dashboard), `"api"` (API ini), atau `null` bila tidak tercatat (sebagian link lama). |
| `source` | string atau null | Label sumber pembuatan, mis. `"Web"`, `"telegram-bot"`, `"situsku.com"`. Lihat 4.3. `null` bila tidak tercatat. |

---

## 4. Endpoint

### 4.1 `GET /me`: info akun

Memeriksa token dan mengetahui akun pemiliknya.

```bash
curl -s https://earnice.com/api/v1/me \
  -H "Authorization: Bearer $EARNICE_API_TOKEN"
```

Respons `200`:

```json
{ "ok": true, "data": { "id": 1234, "username": "budi" } }
```

Tidak ada data sensitif (email, nomor HP, saldo, rekening) di endpoint ini.

---

### 4.2 `GET /links`: daftar link

Mengembalikan link milik akun, **terbaru dulu**, **20 per halaman**.

| Query | Wajib | Keterangan |
|---|---|---|
| `page` | tidak | Nomor halaman, mulai `1` (default `1`). Nilai di luar rentang disesuaikan ke halaman valid terdekat. |

```bash
curl -s "https://earnice.com/api/v1/links?page=1" \
  -H "Authorization: Bearer $EARNICE_API_TOKEN"
```

Respons `200`:

```json
{
  "ok": true,
  "data": [
    {
      "code": "a1b2c3",
      "short_url": "https://earnice.com/a1b2c3",
      "original_url": "https://contoh.com/artikel-saya",
      "link_type": 1,
      "hits": 42,
      "created_date": "2026-10-31 14:30:05",
      "last_visit_date": "2026-10-31 15:02:11",
      "created_via": "api",
      "source": "telegram-bot"
    }
  ],
  "meta": { "page": 1, "per_page": 20, "total": 57, "total_pages": 3 }
}
```

Catatan: `per_page` tetap 20. Untuk mengambil semua link, ulangi sampai `page == total_pages`.

---

### 4.3 `POST /links`: buat shortlink

Body bisa **JSON** (disarankan) atau `application/x-www-form-urlencoded`.

Aturan validasinya **sama persis** dengan form "Buat link" di dashboard Earnice, jadi link buatan
API berperilaku identik: pengunjung melihat misi yang sama dan bagi hasil dihitung dengan cara yang sama.

| Field | Tipe | Wajib | Aturan |
|---|---|---|---|
| `url` | string | ya | URL valid berawalan `http://` atau `https://`; maks 2000 karakter; **tidak boleh** mengarah ke `earnice.com` sendiri. |
| `source` | string | tidak | Label asal pembuatan link, mis. `telegram-bot` atau `situsku.com`. Tampil di dashboard Anda sebagai "API · telegram-bot". Hanya huruf, angka, spasi, titik, dan strip (`_` menjadi `-`); karakter lain dibuang; maks 40 karakter. Alternatif: header `X-Client-Source` dengan aturan sama (bila keduanya ada, `source` di body dipakai). |

**Dari mana label sumber ditentukan.** Urutannya: (1) `source` di body atau header `X-Client-Source`;
(2) domain header `Origin` atau `Referer` (bila ada, hanya nama domain); (3) jenis klien dari `User-Agent`
(Telegram, curl, Python, Node.js, PHP, Postman, Go, Java, Browser); (4) nama token; (5) `API`.
Label ini hanya informasi untuk Anda, **tidak diverifikasi** dan tidak memengaruhi bagi hasil. Disarankan selalu
mengirim `source` agar link dari bot atau situs Anda mudah dikenali dan difilter di halaman "Link Saya".

```bash
curl -s -X POST https://earnice.com/api/v1/links \
  -H "Authorization: Bearer $EARNICE_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://contoh.com/artikel-saya","source":"telegram-bot"}'
```

Respons **`201 Created`**: `data` berisi objek Link yang baru dibuat (lihat 3.3).

Kemungkinan gagal:

| Status | `error.code` | Sebab |
|---|---|---|
| 422 | `validation_failed` | `details.url` berisi alasannya. |
| 422 | `invalid_json` | `Content-Type: application/json` tetapi body bukan JSON valid. |
| 429 | `rate_limited` | Lebih dari **30 link dibuat dalam 10 menit** oleh akun ini (lintas semua token, termasuk link buatan dashboard). Lihat header `Retry-After` (600 detik). |

> **Tidak ada idempotency key.** Jika permintaan `POST` timeout atau terputus, **jangan langsung
> mengulang**, karena bisa menghasilkan link ganda. Periksa dulu `GET /links?page=1` apakah link
> dengan `original_url` yang sama baru saja terbuat, baru ulangi bila tidak ada. Duplikat URL
> diizinkan oleh sistem, jadi pencegahan duplikat adalah tanggung jawab klien.

---

### 4.4 `GET /links/{code}`: detail link dan statistik

```bash
curl -s https://earnice.com/api/v1/links/a1b2c3 \
  -H "Authorization: Bearer $EARNICE_API_TOKEN"
```

Respons `200`: objek Link ditambah `stats`:

```json
{
  "ok": true,
  "data": {
    "code": "a1b2c3",
    "short_url": "https://earnice.com/a1b2c3",
    "original_url": "https://contoh.com/artikel-saya",
    "link_type": 1,
    "hits": 42,
    "created_date": "2026-10-31 14:30:05",
    "last_visit_date": "2026-10-31 15:02:11",
    "created_via": "api",
    "source": "telegram-bot",
    "stats": {
      "total_visits": 42,
      "total_missions": 9,
      "daily": [
        { "date": "2026-10-02", "clicks": 0, "missions": 0 },
        { "date": "2026-10-31", "clicks": 12, "missions": 3 }
      ]
    }
  }
}
```

| Field `stats` | Keterangan |
|---|---|
| `total_visits` | Seluruh kunjungan yang tercatat untuk link ini. |
| `total_missions` | Seluruh misi yang berstatus selesai. |
| `daily` | 30 hari terakhir (termasuk hari ini), urut dari terlama; tiap hari berisi `clicks` (kunjungan) dan `missions` (misi selesai pada kunjungan hari itu). Hari tanpa data bernilai 0. |

Statistik **hanya berupa jumlah**. Pendapatan, saldo, dan nilai kredit tidak pernah ditampilkan
lewat API; lihat di dashboard Earnice.

Catatan: data kunjungan lama dibersihkan berkala oleh sistem, sehingga `daily` dan `total_*`
menggambarkan data yang masih tersimpan.

`404 not_found` bila kode tidak ada **atau milik akun lain** (sengaja tidak dibedakan).
`code` di path: alfanumerik 1-10 karakter, **peka huruf besar/kecil**.

---

### 4.5 `OPTIONS` (preflight)

`OPTIONS` ke endpoint mana pun membalas `204` tanpa autentikasi, dengan header:
`Access-Control-Allow-Origin: *`, `Access-Control-Allow-Headers: Authorization, Content-Type`,
`Access-Control-Allow-Methods: GET, POST, OPTIONS`.
(Tetap ingat 2.3: jangan panggil dari browser.)

---

## 5. Batas pemakaian (rate limit)

| Batas | Nilai | Lingkup | Respons bila lewat |
|---|---|---|---|
| Permintaan umum | **60 permintaan / 60 detik** | per **akun** (semua token akun berbagi kuota) | `429 rate_limited` + `Retry-After` |
| Pembuatan link | **30 link / 10 menit** | per akun (termasuk via dashboard) | `429 rate_limited` + `Retry-After: 600` |
| Autentikasi gagal | **20 kegagalan / 10 menit** | per **alamat IP** | `429 rate_limited` + `Retry-After`; berlaku juga untuk token yang benar dari IP itu sampai jendela habis |

Header pada setiap respons yang lolos autentikasi:

```
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 57
X-RateLimit-Reset: 1793012345      (Unix timestamp, detik)
```

Saran klien:
- Baca `X-RateLimit-Remaining`; bila mendekati 0, tunggu sampai `X-RateLimit-Reset`.
- Saat `429`, tunggu **minimal `Retry-After` detik** sebelum mencoba lagi.
- Jangan mengulang otomatis permintaan yang gagal `401`: token salah yang diulang terus akan
  memblokir IP server Anda 10 menit.

---

## 6. Daftar kode error

| HTTP | `error.code` | Arti | Tindakan klien |
|---|---|---|---|
| 401 | `unauthorized` | Header `Authorization` tidak ada / format salah. | Perbaiki header; jangan ulang otomatis. |
| 401 | `invalid_token` | Token tidak dikenal. | Periksa token; jangan ulang otomatis. |
| 401 | `token_revoked` | Token sudah dicabut. | Buat token baru di dashboard. |
| 403 | `account_blocked` | Akun pemilik token diblokir. | Hentikan; hubungi support. |
| 403 | `account_banned` | Akun pemilik token diban oleh admin. | Hentikan; ajukan keberatan lewat halaman Kontak. |
| 403 | `email_not_verified` | Email akun pemilik token belum diverifikasi. | Buka https://earnice.com/verifikasi-email, selesaikan verifikasi, lalu ulangi. |
| 404 | `not_found` | Endpoint tidak ada, atau link tidak ada/bukan milik akun. | Periksa path/kode. |
| 405 | `method_not_allowed` | Metode HTTP salah. Header `Allow` berisi metode yang benar. | Perbaiki metode. |
| 422 | `validation_failed` | Data tidak valid; lihat `error.details`. | Perbaiki data; jangan ulang tanpa perubahan. |
| 422 | `invalid_json` | Body JSON rusak. | Perbaiki body. |
| 429 | `rate_limited` | Kena batas pemakaian. | Tunggu `Retry-After`. |
| 500 | `server_error` | Kesalahan tak terduga di server. | Ulangi dengan backoff (kecuali `POST`, lihat 4.3). |
| 503 | `service_unavailable` | Layanan sementara tidak tersedia. | Ulangi dengan backoff eksponensial (mis. 2s, 4s, 8s, 16s). |

---

## 7. Contoh klien

### 7.1 PHP (tanpa library)

```php
<?php
function earnice(string $method, string $path, ?array $body = null): array
{
    $token = getenv('EARNICE_API_TOKEN');           // simpan di env, bukan di kode
    $ch = curl_init('https://earnice.com/api/v1' . $path);
    $headers = ['Authorization: Bearer ' . $token, 'Accept: application/json'];
    if ($body !== null) {
        $headers[] = 'Content-Type: application/json';
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
    }
    curl_setopt_array($ch, [
        CURLOPT_CUSTOMREQUEST  => $method,
        CURLOPT_HTTPHEADER     => $headers,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 15,
    ]);
    $raw    = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    curl_close($ch);
    $json = json_decode((string)$raw, true) ?: ['ok' => false, 'error' => ['code' => 'bad_response', 'message' => 'Respons bukan JSON']];
    $json['_status'] = $status;
    return $json;
}

// Buat shortlink
$r = earnice('POST', '/links', ['url' => 'https://contoh.com/artikel-saya']);
if ($r['ok']) {
    echo $r['data']['short_url'];
} else {
    error_log('Earnice: ' . $r['error']['code'] . ' - ' . $r['error']['message']);
}
```

### 7.2 Node.js (18+, `fetch` bawaan)

```js
const BASE = 'https://earnice.com/api/v1';

export async function earnice(method, path, body) {
  const res = await fetch(BASE + path, {
    method,
    headers: {
      Authorization: `Bearer ${process.env.EARNICE_API_TOKEN}`,
      ...(body ? { 'Content-Type': 'application/json' } : {}),
    },
    body: body ? JSON.stringify(body) : undefined,
    signal: AbortSignal.timeout(15000),
  });
  const json = await res.json().catch(() => ({ ok: false, error: { code: 'bad_response' } }));
  if (res.status === 429) {
    const wait = Number(res.headers.get('Retry-After') || 60);
    throw Object.assign(new Error('rate_limited'), { retryAfter: wait });
  }
  return { status: res.status, ...json };
}

// Statistik satu link
const r = await earnice('GET', '/links/a1b2c3');
console.log(r.data.stats.total_missions);
```

### 7.3 Python 3 (`requests`)

```python
import os, requests

BASE = "https://earnice.com/api/v1"
S = requests.Session()
S.headers["Authorization"] = f"Bearer {os.environ['EARNICE_API_TOKEN']}"

def semua_link():
    page, hasil = 1, []
    while True:
        r = S.get(f"{BASE}/links", params={"page": page}, timeout=15).json()
        if not r["ok"]:
            raise RuntimeError(r["error"])
        hasil += r["data"]
        if page >= r["meta"]["total_pages"]:
            return hasil
        page += 1
```

---

## 8. Panduan untuk agen AI / pengembang integrasi

1. **Mulai dengan `GET /me`** untuk memastikan token benar sebelum fitur lain.
2. **Token hanya di server** (`EARNICE_API_TOKEN` di `.env` yang tidak di-commit). Jangan cetak
   token ke log/console. Jangan buat endpoint publik yang meneruskan permintaan mentah ke Earnice.
3. **Validasi di sisi Anda** sebelum memanggil API: URL http/https, maks 2000 karakter, bukan
   `earnice.com`; maksimal 30 pembuatan link per 10 menit per akun.
4. **`POST /links` tidak idempoten**: cegah klik ganda di UI Anda, dan jangan retry otomatis
   setelah timeout tanpa memeriksa `GET /links` (lihat 4.3).
5. **Hormati `429`/`Retry-After`** dan jangan retry `401/403/404/422`.
6. **Simpan `code`** di database proyek Anda untuk menautkan konten Anda dengan link Earnice;
   ambil statistik terbaru dengan `GET /links/{code}` bila perlu.
7. **Waktu `created_date` dan `last_visit_date` adalah WIB tanpa offset**: konversikan dengan zona `Asia/Jakarta`.
8. **Tidak ada endpoint pendapatan, saldo, penarikan, atau webhook.** Semua itu hanya bisa dilihat
   pemilik akun di dashboard Earnice. Jangan merancang fitur yang bergantung padanya.
9. Link tidak bisa dihapus atau diubah lewat API; kelola dari dashboard.
10. Jangan membuat link secara massal untuk tujuan yang melanggar syarat Earnice atau kebijakan
    anti-fraud; akun bisa diblokir dan token ikut tidak berlaku.

---

## 9. Cara kerja dari sisi pengunjung (konteks bisnis)

1. Pengunjung membuka `short_url`.
2. Earnice menampilkan halaman **misi**: daftar misi dari jaringan pengiklan (pendaftaran, survei,
   atau tugas singkat). Pengunjung memilih dan mengerjakan satu misi.
3. Setelah misi tercatat selesai, `original_url` terbuka. Tujuan tidak terbuka bila misi belum selesai.
4. Nilai misi dikreditkan ke saldo pemilik link sesuai persentase bagi hasil yang berlaku
   (lihat https://earnice.com/cpa-shortlink). Saldo dan penarikan **tidak tersedia lewat API**;
   penjelasannya ada di https://earnice.com/tarik-saldo.

Aturan lengkap ada di https://earnice.com/syarat dan https://earnice.com/kebijakan-anti-fraud.

---

## 10. FAQ

### Apakah API ini bisa menampilkan pendapatan saya?
Tidak. Demi keamanan, API hanya memberi jumlah klik dan misi. Pendapatan, saldo, dan penarikan hanya
tampil di dashboard setelah login.

### Apakah link buatan API sama dengan link buatan dashboard?
Ya. Validasinya sama, kodenya dibuat dengan cara yang sama, dan pengunjung mendapat alur misi yang sama.
Link dari API juga muncul di menu **Link Saya**.

### Bisakah saya memilih jenis link atau mengatur kode sendiri?
Belum. `link_type` hanya informasi, dan kode dibuat acak oleh sistem.

### Bisakah saya menghapus atau mengubah URL tujuan lewat API?
Tidak. API v1 hanya membaca dan membuat.

### Token saya bocor, apa yang harus dilakukan?
Buka menu **API** di dashboard, klik **Cabut** pada token itu, lalu buat token baru dan perbarui
konfigurasi server Anda.

### Mengapa saya mendapat 429 padahal baru sedikit memanggil?
Kuota 60 permintaan per menit dibagi oleh semua token akun Anda. Autentikasi gagal juga dihitung per IP
(20 per 10 menit). Periksa header `Retry-After`.

### Di mana saya bisa bertanya?
Lewat halaman https://earnice.com/kontak.

---

## 11. Bantuan

- Kelola token: https://earnice.com/api-token (perlu login)
- Dokumentasi online: https://earnice.com/api-docs
- Panduan umum: https://earnice.com/blog/
- Kontak: https://earnice.com/kontak

*Versi dokumen: 2026-10-31 · API v1*
