How the CDN works
Three ideas everything builds on: namespaces (an API-side tag), the per-account host (where files serve), and the backend-mints / client-uploads split (who does what).
Namespaces are API-side — not a host
A namespace in Oblien is an API-side way to organize, filter, and quota your resources — the same concept you use for workspaces, pages, etc. For the CDN it's a tag on each upload:
- Organize / sort — filter files and stats by namespace (
?namespace=customer-123). - Quota — storage + monthly caps are per namespace, enforced at token mint (API-side).
That's it. A namespace is not a host, is not a subdomain, and never appears in a URL. Use one per tenant, per environment, per asset class — whatever you want to group and quota separately.
The per-account host
Serving is per Oblien account (user) — you get one CDN host, shared across all your namespaces:
- Auto subdomain — your account is assigned one slug →
https://<slug>.cdn.oblien.com. Every upload (any namespace) serves from it:https://<slug>.cdn.oblien.com/static/<file>. - Custom domain (optional) — bring your own
cdn.yourbrand.com, also account-level. See Custom domains.
You rarely type the host — the upload permit carries it and returned URLs already point at it. Because the host is the account, delivery traffic attributes to your account as one meter, and it's the single kill-switch on overage.
Namespace ≠ host. All your files, across every namespace, serve from your one account host. The namespace is recorded on each file (for quota, listing, sorting) — it doesn't change where the file lives or its URL.
Backend mints, client uploads
The CDN never sees your API credentials. Your backend mints a short-lived, single-use upload permit (a JWT); your client uploads directly to your CDN host with that permit; the CDN records the upload back to Oblien so your backend sees it authoritatively.
┌──────────────────┐ POST /cdn/token ┌──────────────────┐
│ Your backend │ ───────────────────▶ │ Oblien API │
│ (X-Client-ID/ │ ◀─────────────────── │ quota gate → │
│ X-Client-Secret) │ permit (JWT) │ 1-min permit │
└────────┬─────────┘ └──────────────────┘
│ hand permit to the client
▼
┌──────────────────┐ Bearer <permit> ┌───────────────────────────┐
│ Your client │ ───── file ────────▶ │ <slug>.cdn.oblien.com/api │
│ (browser/app) │ ◀── { url, … } ───── │ (your account CDN host) │
└──────────────────┘ └───────────┬───────────────┘
records (async, durable) │
▼
┌──────────────────┐
│ Oblien API │
│ GET /cdn/files │
└──────────────────┘- Your backend authenticates with its API key and calls
POST /cdn/token, optionally passing anamespace(the tag for quota/sort). - Oblien runs the quota gate for that namespace and returns a ~1-minute, single-use permit carrying your account host.
- Your client uploads to
https://<slug>.cdn.oblien.com/apiwithAuthorization: Bearer <permit>. - The CDN stores the file (+ any variants), returns URLs on your host, and records the upload (with its namespace tag) to Oblien.
- Your backend reads it via
GET /cdn/files(filter bynamespace) — the authoritative record.
End-to-end
// ─── Your backend (runs on your server with the API key) ───────────
import Oblien from 'oblien';
const oblien = new Oblien({
clientId: process.env.OBLIEN_CLIENT_ID!,
clientSecret: process.env.OBLIEN_CLIENT_SECRET!,
});
// Mint a permit; tag it with a namespace for quota/sort, then hand to the client
const { token } = await oblien.cdn.token({ namespace: 'customer-123' });
// ─── Your client (runs in the user's browser) ──────────────────────
const form = new FormData();
form.append('file', fileInput.files[0]);
const res = await fetch(`https://${cdnHost}/api`, { // your account host, from your backend
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
body: form,
});
const { url } = await res.json(); // https://<slug>.cdn.oblien.com/static/…
// ─── Back on your backend: read it, filtered by the namespace tag ──
const { data } = await oblien.cdn.list({ namespace: 'customer-123' });Server-side only? oblien.cdn.upload(file, { namespace }) mints the permit and uploads in one call. Use the two-step flow above only when the bytes live in the browser/app.
# ── Your backend: mint a permit (namespace = quota/sort tag) ──
POST /cdn/token
X-Client-ID: <id>
X-Client-Secret: <secret>
{ "namespace": "customer-123" }
# → { "token": "<permit>", "expiresIn": "1m", ... }# ── Your client: upload to your account host ──
POST https://<slug>.cdn.oblien.com/api
Authorization: Bearer <permit>
Content-Type: multipart/form-data (field: "file")
# → { "url": "https://<slug>.cdn.oblien.com/static/…", "recorded": true }# ── Your backend: read it (filter by the namespace tag) ──
GET /cdn/files?namespace=customer-123
X-Client-ID: <id>
X-Client-Secret: <secret># ── Your backend: mint a permit ──
TOKEN=$(curl -s -X POST https://api.oblien.com/cdn/token \
-H "X-Client-ID: $OBLIEN_CLIENT_ID" -H "X-Client-Secret: $OBLIEN_CLIENT_SECRET" \
-H "Content-Type: application/json" -d '{"namespace":"customer-123"}' | jq -r .token)
# ── Your client: upload to your account host ──
curl -X POST "https://$CDN_HOST/api" -H "Authorization: Bearer $TOKEN" -F "file=@./logo.png"
# ── Your backend: read it ──
curl "https://api.oblien.com/cdn/files?namespace=customer-123" \
-H "X-Client-ID: $OBLIEN_CLIENT_ID" -H "X-Client-Secret: $OBLIEN_CLIENT_SECRET"Backend vs. client
| Your backend | Your client (browser/app) | |
|---|---|---|
| Auth | API key (X-Client-ID + X-Client-Secret) | The minted permit (Authorization: Bearer) |
| Talks to | api.oblien.com (/cdn/*) | <slug>.cdn.oblien.com/api (your CDN host) |
| Does | Mint permits, read/list files, stats/usage, manage domains & quotas | Upload the bytes |
| Never has | — | Your API credentials |
Never ship your API key to the browser. The client only ever holds a permit — short-lived, single-use, and quota-checked for its namespace tag. Mint one per upload operation.
Next: the full upload contract & variants.