Herkese Açık REST API
VeloCMS, Pro ve daha üstü planlarda kullanabileceğiniz, `/api/v1` altında versiyonlanmış bir REST API ile geliyor. Bu API'nin amacı ne mi? Yönetici arayüzüyle uğraşmadan, doğrudan kod üzerinden veri aktarmak, mobil uygulamalarla konuşmak veya üçüncü parti servislerle entegrasyon kurmak isteyenler için biçilmiş kaftan. Her bir istek, yalnızca izin verilen işlemleri yapabilen, size özel bir API anahtarıyla doğrulanıyor.
Plan Gereksinimleri
| Plan | API Erişimi | Günlük İstek Limiti |
|---|---|---|
| Free | Yok | 0 |
| Pro | Var | 10.000 |
| Business | Var | 100.000 |
| Enterprise | Var | Özel |
Kimlik Doğrulama
API anahtarlarınızı `Admin → Settings → API Keys` yolunu izleyerek oluşturabilirsiniz. Her anahtarın bir ismi ve belirli yetki alanları (scope'ları) bulunur ve size sadece bir defalığına gösterilir. Unutmayın, VeloCMS bu anahtarın kendisini değil, yalnızca SHA-256 özetini saklar. Yani anahtarı kaybederseniz, yapacak tek bir şey var: yenisini oluşturmak. Anahtarı `Authorization` başlığı içinde şu şekilde göndermeniz gerekiyor:
curl https://yourblog.velocms.org/api/v1/posts -H "Authorization: Bearer velo_<your-key-here>"Kapsamlar
Her API anahtarının yetkileri, oluşturma sırasında sizin belirlediğiniz kapsamlarla sınırlıdır. Örneğin, `posts:read` kapsamına sahip bir anahtar yazıları listeleyebilir ve okuyabilir, ama asla yeni bir yazı oluşturamaz veya mevcut olanı değiştiremez. Anahtarınızın yetkisi olmayan bir endpoint'e istek atarsanız, `INVALID_SCOPE` mesajıyla birlikte bir HTTP 403 hatası alırsınız.
| Kapsam | Verdiği Yetkiler |
|---|---|
| `posts:read` | Tüm yazılara okuma erişimi |
| `posts:write` | Yazı oluşturma, güncelleme ve silme |
| `uploads:read` | Tüm yüklemelere okuma erişimi |
| `uploads:write` | Yükleme yapma ve silme |
| `settings:read` | Tenant ayarlarına okuma erişimi |
| `settings:write` | Tenant ayarlarını güncelleme |
Hız Limitleri
API, her bir anahtar için dakikada 100 istek limiti uygular ve bunu kayan bir pencere sistemiyle takip eder. Limite takıldığınızda, HTTP 429 durum koduyla birlikte ne kadar beklemeniz gerektiğini saniye cinsinden belirten bir `Retry-After` başlığı alırsınız. Bu anlık limitin yanı sıra, bir de planınıza bağlı günlük kotalar var (Pro için günde 10 bin, Business için 100 bin). Günlük kotanızı aşarsanız da yine 429 hatası alırsınız, ama bu sefer mesaj `QUOTA_EXCEEDED` olur.
| Başlık | Açıklama |
|---|---|
| `X-RateLimit-Limit` | Mevcut zaman aralığı için hız limiti tavanı. |
| `X-RateLimit-Remaining` | Mevcut zaman aralığında kalan istek sayısı. |
| `X-RateLimit-Reset` | Mevcut zaman aralığının sıfırlanacağı UTC zaman damgası (saniye cinsinden). |
Hata Formatı
Tüm hata yanıtları aynı JSON yapısını kullanır. Bu sayede istemci tarafında hataları yönetmek oldukça kolaylaşır:
{
"error": {
"code": "INVALID_SCOPE",
"message": "This endpoint requires the 'posts:write' scope. Your key has: posts:read.",
"details": {}
}
}| Kod | HTTP durumu | Anlamı |
|---|---|---|
| `BAD_REQUEST` | 400 | İstek hatalı biçimlendirilmiş. |
| `UNAUTHORIZED` | 401 | API anahtarı eksik veya geçersiz. |
| `INVALID_SCOPE` | 403 | API anahtarı gerekli kapsama sahip değil. |
| `NOT_FOUND` | 404 | İstenen kaynak mevcut değil. |
| `RATE_LIMIT_EXCEEDED` | 429 | Dakikalık hız limitini aştınız. |
| `QUOTA_EXCEEDED` | 429 | Planınızın günlük kotasını aştınız. |
| `INTERNAL_SERVER_ERROR` | 500 | Bizim tarafta bir şeyler ters gitti. |
Endpoint Referansı
| Metot | Yol | Kapsam | Açıklama |
|---|---|---|---|
| GET | `/posts` | `posts:read` | Tüm yazıları listeler |
| POST | `/posts` | `posts:write` | Yeni bir yazı oluşturur |
| GET | `/posts/:id` | `posts:read` | Tek bir yazıyı getirir |
| PUT | `/posts/:id` | `posts:write` | Bir yazıyı günceller |
| DELETE | `/posts/:id` | `posts:write` | Bir yazıyı siler |
Sayfalama
Liste döndüren tüm endpoint'ler `page` ve `per_page` sorgu parametrelerini destekler. `per_page` için belirtebileceğiniz en yüksek değer 100'dür. Gelen yanıtlarda ise `items`, `page`, `per_page`, `total` ve `total_pages` alanlarını bulabilirsiniz.
# Page 2, 10 per page, published only
curl "https://yourblog.velocms.org/api/v1/posts?page=2&per_page=10&status=published" -H "Authorization: Bearer velo_<key>"Webhook Abonelikleri
Sürekli API'yi yoklamak yerine, tenant'ınızda bir olay gerçekleştiğinde HTTP POST bildirimi almak için bir webhook URL'i kaydedebilirsiniz. Bunun için `Admin → Settings → Webhooks` ekranına gidin. Bir HTTPS URL'i girin, hangi olayları dinlemek istediğinizi seçin ve kaydedin. VeloCMS size özel, 32-byte'lık rastgele bir 'secret' (gizli anahtar) oluşturacak ve bunu sadece bir kez gösterecek. Bu anahtarı mutlaka saklayın, çünkü gelen bildirimlerin gerçekten VeloCMS'ten geldiğini doğrulamak için ona ihtiyacınız olacak.
| Olay | Tetiklendiği durum |
|---|---|
| `post.created` | Yeni bir yazı oluşturulduğunda. |
| `post.updated` | Mevcut bir yazı güncellendiğinde. |
| `post.deleted` | Bir yazı silindiğinde. |
Webhook Payload Formatı
{
"event": "post.published",
"timestamp": 1714140000,
"payload": {
"post_id": "abc123",
"title": "My first post",
"slug": "my-first-post",
"published_at": "2026-04-26T10:00:00.000Z",
"tenant_id": "tenant_xyz"
}
}HMAC İmzasını Doğrulama
Her webhook teslimatı, `v1=<hex>` formatında bir değer içeren `X-VeloCMS-Signature` başlığıyla gelir. Bu imza, `v1:<timestamp>:<raw-body>` metninin HMAC-SHA256 ile özetlenmiş halidir. Buradaki `timestamp` değeri, `X-VeloCMS-Timestamp` başlığından gelir. Gelen payload'u işlemeden önce bu imzayı mutlaka doğrulayın. Bu sizi 'replay' ve 'spoofing' gibi saldırılardan koruyacaktır.
import { createHmac, timingSafeEqual } from "node:crypto";
function verifyWebhookSignature(
rawBody: string,
signature: string, // X-VeloCMS-Signature header value
timestamp: string, // X-VeloCMS-Timestamp header value
secret: string // Your webhook secret from the admin UI
): boolean {
const signingInput = `v1:${timestamp}:${rawBody}`;
const expectedSig = createHmac("sha256", secret)
.update(signingInput)
.digest("hex");
const expectedHeader = `v1=${expectedSig}`;
// Use constant-time comparison to prevent timing attacks
const a = Buffer.from(signature);
const b = Buffer.from(expectedHeader);
if (a.length !== b.length) return false;
return timingSafeEqual(a, b);
}Yeniden Deneme Davranışı
VeloCMS, başarısız olan webhook teslimatlarını üssel olarak artan bir gecikmeyle (1s, 2s, 4s) 3 kez daha dener. Bir teslimatın başarılı sayılması için endpoint'inizin 10 saniye içinde 2xx ile başlayan bir durum kodu dönmesi gerekir. Üst üste 5 kez hata alınırsa abonelik otomatik olarak devre dışı bırakılır ve yönetici ayarlarında bununla ilgili bir bildirim görürsünüz. Sorunu çözdükten sonra Webhooks panelinden aboneliği tekrar aktif hale getirebilirsiniz.
Örnek: Harici bir CMS'ten Yazıları İçe Aktarma
const API_KEY = process.env.VELOCMS_API_KEY;
const BASE = "https://yourblog.velocms.org/api/v1";
interface ExternalPost {
title: string;
body_html: string;
tags: string[];
}
async function importPost(p: ExternalPost): Promise<void> {
const res = await fetch(`${BASE}/posts`, {
method: "POST",
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
title: p.title,
content_html: p.body_html,
tags: p.tags,
status: "draft",
}),
});
if (!res.ok) {
const err = await res.json();
throw new Error(`Import failed: ${err.error.message}`);
}
const post = await res.json();
console.log(`Imported: ${post.id} — ${post.title}`);
}