Batik Soehadi · Developer Docs
Content API v1 — Integration Guide
Panduan integrasi untuk developer website ekosistem. API memberikan metadata artikel ringan (thumbnail, judul, deskripsi, kategori, tanggal, slug, URL asli) — bukan konten penuh. Setiap kartu artikel yang Anda tampilkan link langsung ke artikel asli di batiksoehadi.com, yang tetap menjadi sumber kanonik dan tujuan traffic utama.
https://batiksoehadi.com/api/v1Authorization: Bearer <API_KEY> · server-to-serverJSON murni — API selalu mengembalikan
application/json; tidak pernah HTML siap-tampil. Website ekosistem merender sendiri kartunya dari data JSON.1. Prinsip Utama
- Jangan tampilkan konten penuh di website ekosistem — hanya kartu ringkasan.
- Jangan hardcode URL artikel sendiri. Selalu pakai field
urldari response. - Panggil API dari server Anda (server-to-server). API key tidak boleh ada di browser/frontend.
- Cache response di server Anda minimal 10 menit. Jangan fetch per page-view.
- Satu key = satu website. Key dipakai bersama beberapa website = bisa kena rate limit.
2. Authentication
Kirim API key Anda di header Authorization pada setiap request. Simpan key sebagai environment variable di server (mis. BATIKSOEHADI_API_KEY) — jangan pernah commit ke Git atau tampilkan di frontend.
Authorization: Bearer <API_KEY_ANDA>Tanpa key atau key salah → 401:
{
"error": {
"code": "UNAUTHORIZED",
"message": "API authentication required. Pass a valid API key via the Authorization: Bearer header."
}
}3. Endpoint
GET https://batiksoehadi.com/api/v1/articles — daftar kartu artikel tanpa body konten (endpoint utama).
GET https://batiksoehadi.com/api/v1/articles/{slug} — detail satu artikel, menambahkan body dan english_url. Untuk kebutuhan khusus saja.
GET https://batiksoehadi.com/api/v1/categories — daftar kategori + jumlah artikel.
GET https://batiksoehadi.com/api/v1/tags — daftar tag + jumlah artikel.
4. Query Parameters
| Param | Tipe | Default | Keterangan |
|---|---|---|---|
page | integer ≥ 1 | 1 | Nomor halaman (offset pagination) |
per_page | integer 1–100 | 20 | Item per halaman (max 100) |
cursor | string opaque | — | Keyset cursor dari meta.next_cursor — lihat §7. Tidak boleh digabung dengan page. |
category | string | — | Slug kategori, mis. motif, seragam (lihat /categories) |
tag | string | — | Filter tag (substring match) |
featured | true / false | — | Hanya artikel unggulan (kurasi editorial) |
search | string 2–80 | — | Cari di judul + deskripsi + focus keyword |
sort | latest / oldest | latest | Urut berdasarkan tanggal publikasi |
Parameter invalid mengembalikan 400 dengan pesan yang menjelaskan param mana yang salah.
5. Response
{
"data": [
{
"id": "866976b5-4f9e-4a2e-9e46-1b8d5b6f8a01",
"title": "Mengenal Filosofi Batik Parang",
"slug": "motif-batik-parang",
"description": "Mengenal sejarah, filosofi, dan makna dari motif Batik Parang.",
"thumbnail": "https://s3.nevaobjects.id/batiksoehadi-bucket/articles/batik-fabric/batik-parang-pattern.jpg",
"category": { "name": "motif", "slug": "motif" },
"tags": ["parang batik", "motif klasik"],
"featured": false,
"published_at": "2026-08-13T00:00:00.000Z",
"updated_at": "2026-08-13T00:00:00.000Z",
"url": "https://batiksoehadi.com/artikel/motif-batik-parang/"
}
],
"meta": {
"page": 1,
"per_page": 2,
"total": 432,
"total_pages": 216,
"next_cursor": "eyJ0IjoiMjAyNi0wOC0xM1QwMDowMDowMC4wMDBaIiwiaWQiOiI4NjY5NzYiLCJkIjoibGF0ZXN0In0"
}
}| Field | Arti |
|---|---|
url | URL artikel asli di batiksoehadi.com. Selalu pakai ini untuk link "Baca Selengkapnya". Selalu absolut dan canonical. |
thumbnail | URL gambar absolut. Boleh langsung dipakai sebagai <img src>. Bisa null. |
description | Excerpt singkat untuk kartu. Bisa null. |
category | Objek {name, slug} atau null. |
published_at / updated_at | ISO 8601 UTC. |
meta.next_cursor | Ada hanya saat masih ada data berikutnya. Hilang = data habis. |
6. Pagination Offset
Untuk browsing / UI dengan nomor halaman. Pakai meta.total_pages untuk render tombol halaman.
GET https://batiksoehadi.com/api/v1/articles?page=1&per_page=12
GET https://batiksoehadi.com/api/v1/articles?page=2&per_page=127. Pagination Cursor
cursor adalah keyset pagination: stabil saat artikel baru terbit di tengah walk, tanpa duplikat atau baris terlewat. Cocok untuk sinkronisasi awal atau job berkala yang menarik seluruh dataset.
GET https://batiksoehadi.com/api/v1/articles?per_page=100 → halaman 1 + meta.next_cursor
GET https://batiksoehadi.com/api/v1/articles?per_page=100&cursor=... → halaman berikutnyaWalk lengkap (Node.js):
let url = "https://batiksoehadi.com/api/v1/articles?per_page=100";
while (url) {
const res = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.BATIKSOEHADI_API_KEY}` },
});
if (res.status === 429) {
await new Promise((r) => setTimeout(r, 60_000)); // tunggu window reset
continue;
}
const { data, meta } = await res.json();
for (const card of data) {
// simpan/proses kartu...
}
url = meta.next_cursor
? `https://batiksoehadi.com/api/v1/articles?per_page=100&cursor=${encodeURIComponent(meta.next_cursor)}`
: null;
}cursordanpagemutually exclusive (gabungan = 400).- Cursor invalid/garbled → 400. Selalu pakai nilai
next_cursorapa adanya; jangan karang sendiri. - Pertahankan
sortyang sama selama satu walk (cursor terikat ke arah sort).
8. Rate Limit
| Limit | 60 request / menit per API key (sliding window) |
| Header | X-RateLimit-Limit, X-RateLimit-Remaining |
| Saat kena limit | HTTP 429 {"error":{"code":"RATE_LIMITED",...}} + header Retry-After (detik) |
Wajib cache di sisi Anda. Dengan cache 10 menit, satu website butuh ≤ 6 request/menit untuk melayani semua visitor — jauh di bawah limit.
9. Caching & Freshness
- Response bersifat per-identitas (auth per key), sehingga tidak di-cache CDN — cache di server Anda per key Anda sendiri.
- Data sisi kami di-cache 10 menit (in-process), jadi artikel baru muncul dalam ≤ ~10 menit.
- Cache 10–60 menit di sisi Anda memberikan freshness wajar tanpa membakar rate limit.
10. Menampilkan Kartu Artikel
<a href="{card.url}" rel="noopener">
<img src="{card.thumbnail}" alt="{card.title}" loading="lazy" />
<h3>{card.title}</h3>
<p>{card.description}</p>
<span>Baca Selengkapnya di Batik Soehadi →</span>
</a>- Link =
card.url— jangan bentuk URL sendiri, jangan proxy/relink. - Thumbnail langsung dari
card.thumbnail(sudah absolut). - Next.js dengan
next/image: daftarkan remote pattern{ protocol: "https", hostname: "batiksoehadi.com" }diimages.remotePatterns— atau pakai<img>biasa / URL optimizer kami (/_next/image?url=<encoded>&w=640&q=75).
11. Contoh Lengkap — Next.js (App Router)
// app/artikel-terkini/page.tsx (di website ekosistem)
export const revalidate = 600; // cache 10 menit
async function getArticles() {
const res = await fetch(
"https://batiksoehadi.com/api/v1/articles?page=1&per_page=12",
{
headers: { Authorization: `Bearer ${process.env.BATIKSOEHADI_API_KEY}` },
next: { revalidate: 600 },
}
);
if (!res.ok) return [];
const { data } = await res.json();
return data;
}
export default async function Page() {
const articles = await getArticles();
return (
<section>
{articles.map((card) => (
<a key={card.id} href={card.url}>
{card.thumbnail && <img src={card.thumbnail} alt={card.title} loading="lazy" />}
<h3>{card.title}</h3>
<p>{card.description}</p>
<span>Baca Selengkapnya →</span>
</a>
))}
</section>
);
}12. Error Reference
Semua error punya bentuk konsisten: { "error": { "code": "<KODE>", "message": "..." } }
| HTTP | Code | Penyebab | Yang harus dilakukan |
|---|---|---|---|
| 400 | INVALID_PARAMETER | Param query tidak valid (mis. per_page=101, cursor rusak) | Perbaiki param sesuai pesan error |
| 401 | UNAUTHORIZED | Key hilang/salah | Cek header Authorization: Bearer |
| 404 | NOT_FOUND | Slug detail tidak ada | Slug salah / artikel dihapus |
| 429 | RATE_LIMITED | >60 req/menit | Hormati Retry-After, tambah caching |
| 500 | INTERNAL_ERROR | Kesalahan sisi kami | Retry dengan backoff; kontak tim kalau menetap |
13. Checklist Go-Live
- API key disimpan sebagai env var di server, tidak pernah di frontend/Git
- Semua request server-to-server (tidak ada panggilan dari browser)
- Cache ≥ 10 menit di server Anda
- Semua link artikel memakai card.url apa adanya
- Handling 429 dengan Retry-After
- Handling 401 diperlakukan sebagai config error (key salah/expired) — alert ke tim
- Tidak ada konten penuh ditampilkan ulang — hanya kartu + link ke batiksoehadi.com
14. FAQ
Bisakah saya ambil full artikel dan tampilkan di website saya?
Tidak. API list sengaja tidak menyediakan body, dan menampilkan ulang konten penuh akan menduplikasi konten SEO yang merugikan kedua website. Tampilkan kartu, link ke sumber.
Berapa lama artikel baru muncul di API?
≤ ~10 menit setelah terbit (karena data cache di sisi kami).
Apakah bisa request tanpa API key?
Tidak — production menegakkan auth. Hubungi tim Batik Soehadi untuk key per website Anda.
Bagaimana kalau saya butuh lebih dari 100 item per halaman?
Tidak bisa via per_page (max 100) — gunakan cursor pagination (§7) untuk menarik dataset penuh secara bertahap.
Apakah URL gambar bisa berubah?
Path gambar bisa berubah saat artikel diedit; selalu render dari thumbnail terbaru yang Anda fetch, jangan simpan URL gambar secara permanen tanpa refresh berkala.
15. Pola SEO-Friendly Halaman Detail Ekosistem
Bagi website ekosistem yang ingin menyediakan rute halaman sendiri (misalnya /artikel/[slug]/), gunakan pola berikut agar halaman terindeks Google dengan judul dan ringkasan yang kaya keyword, namun aman 100% dari penalty duplicate content dan mengalirkan traffic ke batiksoehadi.com:
// app/artikel/[slug]/page.tsx (di website ekosistem)
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params;
const article = await fetchArticleBySlug(slug);
if (!article) return {};
return {
title: `${article.title} — ${siteConfig.name}`,
description: article.description,
alternates: {
canonical: article.url, // Mengarahkan crawler ke https://batiksoehadi.com/artikel/{slug}/
},
};
}- Tag Kanonik Wajib: Selalu set
alternates.canonical = article.urlagar Googlebot mengakui Batik Soehadi sebagai pemilik sah artikel. - Ringkasan Terbaca: Render
<h1>{article.title}</h1>, cover image, dan teks ringkasan<p>{article.description}</p>di halaman agar bot Google mengindeks kata kuncinya. - CTA Baca Lengkap: Sediakan tombol menonjol "Baca Selengkapnya di Batik Soehadi" dengan tautan ke
article.url(membuka website utama).