CDN Thiết Bị Hưng Phát
Trang chủ / API documentation
/api/v1

API documentation

REST API v1 cho CDN. Auth bằng API key, scope-based (read / write / admin), rate-limited per-key. Tất cả response JSON.

Base URL

https://cdn.thietbihungphat.com/api/v1

Authentication

Mọi endpoint (trừ /ping/docs) yêu cầu header X-API-Key.

X-API-Key: ckd_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Tạo key tại /api-keys (đăng nhập, vào menu "API Keys" → "Tạo key mới"). Chuỗi key chỉ hiển thị 1 lần khi tạo — hệ thống chỉ lưu SHA-256 hash, mất là phải tạo key mới.

Tuỳ chọn: gắn IP allowlist (CIDR) khi tạo key — nếu key bị lộ, allowlist là tuyến phòng thủ thứ 2. Request từ IP không khớp → 403 IP_NOT_ALLOWED.

Scopes

Mỗi key có 1 scope. Scope cao kế thừa scope thấp:

ScopeCho phép
readGET mọi resource public (categories, albums, tags, images)
writeread + tạo/sửa album, upload ảnh — chỉ resource thuộc owner của key
adminwrite + delete album/ảnh, tạo category, bypass moderation queue

Contributor tạo key chỉ thao tác được trên album mình tạo. Admin user có thể tạo admin-scope key thao tác toàn hệ thống.

Rate limit

Mỗi key có giới hạn rate_limit_per_minute (mặc định 60, configurable 1–600). Vượt → HTTP 429.

Response headers:

  • X-RateLimit-Limit — giới hạn cấu hình
  • X-RateLimit-Remaining — còn lại trong cửa sổ 60s
  • Retry-After — (chỉ khi 429) số giây phải đợi

Error format

Mọi lỗi trả JSON envelope thống nhất:

{
  "error": {
    "code": "FORBIDDEN",
    "message": "Key cần scope 'write'.",
    "details": { "required_scope": "write", "granted": ["read"] }
  }
}
HTTPCodeKhi nào
401UNAUTHENTICATEDThiếu / sai / revoked key
403FORBIDDENScope không đủ HOẶC không phải owner
403IP_NOT_ALLOWEDIP của request không trong allowlist của key
404NOT_FOUNDResource không tồn tại / chưa publish
422VALIDATION_FAILEDBody sai format — details chứa field errors
422ALBUM_FULL / BAD_MIME / QUOTA_EXCEEDEDDomain-level lỗi upload
429RATE_LIMIT_EXCEEDEDdetails.retry_after_seconds kèm theo
500INTERNAL_ERRORLỗi server (kiểm tra storage/logs/laravel.log)

Pagination

GET list endpoints dùng Laravel paginator. Response shape:

{
  "data": [ ... ],
  "links": { "first": "...", "last": "...", "prev": null, "next": "..." },
  "meta": { "current_page": 1, "last_page": 5, "per_page": 20, "total": 100 }
}

Endpoints

Health

GET /ping                                            # no auth

Categories

GET    /categories?parent_id=<id|root>&per_page=50    # scope: read
GET    /categories/{id}                              # scope: read
POST   /categories                                   # scope: admin
       body: {parent_id?, name, slug?, description?, meta_*?}

Albums

GET    /albums?category_id=&tag=&sku=&model=&q=&per_page=20   # scope: read
GET    /albums/{id}                                  # scope: read — bao gồm images đầy đủ
POST   /albums                                       # scope: write
       body: {category_id, name, sku?, model?, slug?, description?,
              visibility?, tags?, meta_*?}
PATCH  /albums/{id}                                  # scope: write (owner) hoặc admin
DELETE /albums/{id}                                  # scope: admin (owner)

Images + Variants

POST   /albums/{id}/images                           # scope: write (owner)
       multipart: file=<binary>, alt_text=<string>
       trả 201, original_url + variants {avif, webp, thumb_*}
DELETE /images/{id}                                  # scope: admin (owner)

GET    /images/{id}/variants                         # scope: read
       trả list các variant đã generate + pending_variants
GET    /images/{id}/variants/{key}                   # scope: read
       302 redirect tới URL variant; 202 nếu enabled nhưng chưa gen;
       404 UNKNOWN_VARIANT / VARIANT_DISABLED

Variant keys mặc định: webp, avif (full-size, luôn có), thumb_sm (320w), thumb_md (640w), thumb_lg (1280w). Admin có thể bật thêm: social_og (1200×630), social_twitter, card_square (800×800), card_landscape, instagram_post, instagram_story, hero_1920, mobile_thumb — hoặc tạo variant custom tại /admin/settings → Hình ảnh.

Tags

GET    /tags?q=&per_page=50                          # scope: read

Examples

Tạo album + upload ảnh

KEY="ckd_xxxx..."
BASE="https://cdn.thietbihungphat.com/api/v1"

# 1. Tạo album trong category id=2 (Đèn LED)
ALBUM=$(curl -sS -H "X-API-Key: $KEY" -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"category_id":2,"name":"Đèn pha 200W","sku":"LED-200W-PHA","model":"Panasonic LP-200"}' \
  $BASE/albums | jq -r .data.id)

# 2. Upload ảnh
curl -sS -H "X-API-Key: $KEY" \
  -F "file=@/path/to/photo.jpg" \
  -F "alt_text=Đèn pha 200W góc nghiêng" \
  $BASE/albums/$ALBUM/images

# 3. Đọc lại album
curl -sS -H "X-API-Key: $KEY" $BASE/albums/$ALBUM | jq .

Tìm album theo SKU

# Exact match
curl -sS -H "X-API-Key: $KEY" "$BASE/albums?sku=LED-200W-PHA"

# Multi-field search (name + sku + model)
curl -sS -H "X-API-Key: $KEY" "$BASE/albums?q=Panasonic"

# By tag + category
curl -sS -H "X-API-Key: $KEY" "$BASE/albums?category_id=2&tag=led&per_page=10"

Reading public albums (no key needed via website)

Public website pages cũng phục vụ ảnh trực tiếp — nếu chỉ cần đọc, nhúng <img src> URL từ /storage/images/... đã có cache header immutable 1 năm. API chỉ cần khi cần JSON metadata hoặc thao tác ghi.

Webhooks

Đăng ký webhook tại /webhooks để nhận event khi:

  • album.published — admin/mod duyệt album
  • image.processed — job Imagick xong, AVIF/WebP variants ready
  • comment.created — comment mới được post

Mỗi delivery có header X-Webhook-Signature: sha256=<hmac_sha256(body, secret)>. Server bạn verify chữ ký rồi trả 2xx trong 10 giây. Không 2xx → retry với backoff 30s / 5m / 30m. Sau 3 lần fail → status=abandoned.

Limits

SettingMặc địnhMô tả
upload_max_mb10Kích thước file ảnh tối đa
album_max_images200Ảnh tối đa / album
default_quota_mb500Dung lượng tối đa / user
allowed_mimesjpeg, png, webp, avif, gifMIME được phép (validate bằng magic bytes)

Admin có thể điều chỉnh tại /admin/settings.

Versioning

Path prefix /api/v1/. Phá vỡ tương thích = tăng version (vd /v2/). Trong v1:

  • Field mới có thể được thêm — clients không nên reject extra fields
  • Field hiện tại sẽ không bị đổi nghĩa / xoá trong vòng đời v1
  • Status code không đổi

Cần hỗ trợ?

Phát hiện bug hoặc cần endpoint mới? Liên hệ qua email admin trong /admin/settings (operator). Hoặc thử endpoint trực tiếp trong Swagger UI để debug.