API Referansı
VeloCMS'in iki farklı API yüzü var: biri POCKETBASE_URL adresinizdeki standart PocketBase REST endpoint'lerini içeren PocketBase tenant API'si, diğeri ise VeloCMS'in kendi webhook API'si (/api/stripe/webhook ve /api/member-webhook/[tenantSlug]). Bu kılavuzda her ikisini de ele alacağız. Özellikle kimlik doğrulama modeline, webhook imzalamaya ve entegrasyon geliştirirken en çok işinizin düşeceği üç endpoint'e odaklanacağız.
Collection'lara Genel Bakış
| Collection | Amacı | Kimlik Doğrulama Gerekli mi? |
|---|---|---|
| `_superusers` | VeloCMS blog sahipleri (yöneticiler) | Admin |
| `blog_members` | Blog okurları (aboneler) | Üye |
| `posts` | Blog yazıları | Yok (herkese açık yazılar için) |
| `media` | Yüklenen görseller ve dosyalar | Admin |
| `site_settings` | Tenant bazlı site yapılandırması | Admin |
| `stripe_products` | Stripe ürün/fiyat eşleştirmesi | Admin |
| `stripe_customers` | Stripe müşteri ID eşleştirmesi | Admin |
| `stripe_subscriptions` | Stripe abonelik ID eşleştirmesi | Admin |
Kimlik Doğrulama Modeli
VeloCMS'te birbiriyle asla karıştırılmaması gereken iki ayrı kimlik doğrulama (auth) sistemi bulunur (P-022). Yönetici kimlik doğrulaması blog sahipleri içindir; giriş bilgileri PocketBase'in dahili `_superusers` collection'ında tutulur ve oturum bilgisi bir `pb_auth` cookie'sinde saklanır. Üye (yani okur) kimlik doğrulaması ise aboneler içindir; bu bilgiler `blog_members` içinde saklanır ve oturum için `pb_member_auth` cookie'si kullanılır. Bir yönetici her şeyi okuyabilirken, bir üye sadece herkese açık yazıları ve kendi üyelik kaydını görebilir.
# Get an admin auth token
curl -X POST '$POCKETBASE_URL/api/admins/auth-with-password' -H 'Content-Type: application/json' -d '{"identity":"[email protected]","password":"yourpassword"}'
# Response includes a token field — use it as a Bearer header:
# Authorization: Bearer <token># Member (reader) login — against blog_members collection
curl -X POST '$POCKETBASE_URL/api/collections/blog_members/auth-with-password' -H 'Content-Type: application/json' -d '{"identity":"[email protected]","password":"memberpassword"}'Yazıları Listeleme
`posts` collection'ı, PocketBase'in filtreleme, sıralama ve sayfalama için kullandığı standart sorgu sözdizimini destekler. Herkese açık olarak yayınlanmış yazıları okumak için kimlik doğrulaması gerekmez:
curl '$POCKETBASE_URL/api/collections/posts/records?filter=(status="published")&sort=-published_at&page=1&perPage=20'Yazı Oluşturma
Bir yazı oluşturmak için yönetici kimlik doğrulaması şart. `content_json` alanı TipTap'in JSON AST'sini tutarken, `content_html` ise gösterim için render edilmiş HTML'i barındırır. İkisini birden veya sadece birini ayarlayabilirsiniz, ama bir şeyi unutmayın: eğer sadece `content_html`'i ayarlarsanız, editör bu yazıyı düzenlemek için tekrar açamaz.
curl -X POST '$POCKETBASE_URL/api/collections/posts/records' -H 'Content-Type: application/json' -H 'Authorization: Bearer <admin-token>' -d '{
"title": "My first post",
"slug": "my-first-post",
"content_html": "<p>Hello, world.</p>",
"status": "draft",
"visibility": "public",
"tags": ["intro"],
"tenant_id": "<your-tenant-id>"
}'Medya Yükleme
Medya yüklemeleri, `media` collection'ına `multipart/form-data` olarak yapılır. VeloCMS backend'i dosyayı Cloudflare R2'de saklar ve herkese açık URL'i `url` alanına kaydeder:
curl -X POST '$POCKETBASE_URL/api/collections/media/records' -H 'Authorization: Bearer <admin-token>' -F 'file=@/path/to/image.jpg' -F 'tenant_id=<your-tenant-id>'Webhook İmzalama — HMAC SHA-256
Her iki webhook endpoint'i de (`/api/stripe/webhook` ve `/api/member-webhook/[tenantSlug]`), gelen payload'ları HMAC-SHA256 imzalarıyla doğrular. Bu yöntem, "replay attack" olarak bilinen tekrar saldırılarını önler ve payload'un yolda değiştirilmediğinden emin olmamızı sağlar.
Stripe webhook'ları için imza, `Stripe-Signature` header'ında bulunur ve `STRIPE_WEBHOOK_SECRET`'ınız kullanılarak doğrulanır (aman dikkat, CLI'dan gelen değil, Stripe Dashboard'daki endpoint'ten aldığınız secret olmalı). `member-webhook` endpoint'i içinse imza `X-VeloCMS-Signature` header'ındadır ve tenant başına özel olarak `site_settings.member_webhook_secret` içinde saklanan HMAC secret'ını kullanır.
import { createHmac } from "crypto";
function verifyMemberWebhook(
rawBody: string,
signature: string,
secret: string
): boolean {
const expected = createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
return `sha256=${expected}` === signature;
}Üye Webhook'u Payload'ı
`/api/member-webhook/[tenantSlug]` endpoint'i, bir okurun aboneliğinde değişiklik olduğunda Stripe'tan olayları (event) alır. Gelen payload'un yapısı olayın türüne göre değişiklik gösterir:
| Olay Türü | Payload Alanları | Gerçekleşen Eylem |
|---|---|---|
| `customer.subscription.created` | `id`, `customer`, `items` | Yeni bir abonelik oluşturuldu. |
| `customer.subscription.updated` | `id`, `customer`, `items` | Mevcut bir abonelik güncellendi (örneğin, plan değişikliği). |
| `customer.subscription.deleted` | `id`, `customer` | Bir abonelik iptal edildi veya sona erdi. |
HMAC ile Korunan Abonelikten Çıkma Linkleri
Bülten aboneliğinden çıkma linkleri, linkin VeloCMS tarafından belirli bir e-posta adresi için üretildiğini kanıtlayan bir HMAC token'ı içerir. Bu sayede bir abonenin başka bir abonenin aboneliğini sonlandırması engellenmiş olur. Token sunucu tarafında oluşturulur ve `/member/unsubscribe` rotasında doğrulanır:
import { createHmac } from "crypto";
function generateUnsubscribeToken(email: string, secret: string): string {
return createHmac("sha256", secret).update(email).digest("hex");
}
// Link format: /member/unsubscribe?email=<encoded>&token=<hmac>Referans
Collection'lara Genel Bakış
| Collection | Anahtar Alanlar | Kimlik Doğrulama | Varsayılan Listeleme Kuralı |
|---|---|---|---|
| `_superusers` | `email`, `password` | Admin | Sadece Admin |
| `blog_members` | `email`, `stripe_customer_id` | Üye | `@request.auth.id = id` |
| `posts` | `slug`, `title`, `published_at`, `visibility` | Yok (herkese açık) | `visibility = "public" && published_at != null` |
| `media` | `file`, `url` | Admin | Sadece Admin |
| `site_settings` | `slug`, `custom_domain` | Admin | Sadece Admin |
PocketBase REST Endpoint Kalıpları
| İşlem | Metot | URL Kalıbı | Kimlik Doğrulama |
|---|---|---|---|
| Kayıtları listele | GET | `/api/collections/[collection]/records` | Değişken |
| Kaydı görüntüle | GET | `/api/collections/[collection]/records/[id]` | Değişken |
| Kayıt oluştur | POST | `/api/collections/[collection]/records` | Değişken |
| Kaydı güncelle | PATCH | `/api/collections/[collection]/records/[id]` | Değişken |
| Kaydı sil | DELETE | `/api/collections/[collection]/records/[id]` | Değişken |
Filtreleme Sözdizimi
# Simple equality
?filter=(status="published")
# Combined with AND
?filter=(status="published" && visibility="public")
# Date range
?filter=(published_at >= "2026-01-01" && published_at < "2026-02-01")
# Relation field (tenant isolation)
?filter=(tenant_id.owner_id = "{userId}")
# Full-text search (requires @search index on field)
?filter=(title ~ "velocms")
# Multiple values (OR)
?filter=(status="published" || status="draft")Sayfalama Parametreleri
| Parametre | Tür | Varsayılan | Açıklama |
|---|---|---|---|
| `page` | integer | `1` | Döndürülecek sayfa numarası |
| `perPage` | integer | `30` | Sayfa başına kayıt sayısı |
| `sort` | string | `created` (azalan) | Sıralama düzeni (`-` ön eki azalan sıralama içindir) |
| `filter` | string | (yok) | PocketBase filtre ifadesi |
| `expand` | string | (yok) | Genişletilecek ilişkilerin virgülle ayrılmış listesi |
API Hata Kodları
| HTTP Kodu | PB Hata Kodu | Anlamı | Yaygın Neden |
|---|---|---|---|
| 400 | `validation_invalid_data` | Geçersiz istek gövdesi veya sorgu parametreleri | Zorunlu bir alan eksik veya yanlış türde veri gönderildi. |
| 401 | `unauthorized` | Eksik veya geçersiz kimlik doğrulama token'ı | İstekle birlikte geçerli bir `pb_auth` veya `pb_member_auth` cookie'si gönderilmedi. |
| 403 | `forbidden` | Kimlik doğrulama token'ı bu işlem için gerekli izne sahip değil | Bir üye, admin yetkisi gerektiren bir endpoint'e erişmeye çalışıyor. |
| 404 | `not_found` | Kayıt veya collection bulunamadı | İstek yapılan ID veya collection adı mevcut değil. |
Curl Örnekleri — posts Collection'ı
curl -G '$POCKETBASE_URL/api/collections/posts/records' --data-urlencode 'filter=(status="published" && visibility="public")' --data-urlencode 'sort=-published_at' --data-urlencode 'page=1' --data-urlencode 'perPage=20' --data-urlencode 'fields=id,title,slug,excerpt,published_at,tags'curl '$POCKETBASE_URL/api/collections/posts/records/{id}'curl -X PATCH '$POCKETBASE_URL/api/collections/posts/records/{id}' -H 'Content-Type: application/json' -H 'Authorization: Bearer <admin-token>' -d '{"status":"published","published_at":"2026-04-27T12:00:00Z"}'curl -X POST '$POCKETBASE_URL/api/collections/blog_members/records' -H 'Content-Type: application/json' -H 'Authorization: Bearer <admin-token>' -d '{
"email": "[email protected]",
"name": "Jane Reader",
"tier": "free",
"newsletter": true,
"tenant_id": "<tenant-id>"
}'VeloCMS Webhook Endpoint'leri
| Endpoint | Metot | İmza Header'ı | Amacı |
|---|---|---|---|
| `/api/stripe/webhook` | POST | `Stripe-Signature` | Stripe'tan abonelik olaylarını alır |
| `/api/member-webhook/[tenantSlug]` | POST | `X-VeloCMS-Signature` | İşlenmiş Stripe olaylarını sizin kendi backend'inize yönlendirir |