Tout ce qu'il fait, sur une page.
Vérifié dans le code qui le fait tourner, le 2 septembre 2026. Rien ici n'est prévu ou à venir.
L'admin existe en anglais et en italien, donc ces captures sont la série anglaise.
Le schéma, c'est le formulaire.
Ce que tu écris à gauche est ce que les rédacteurs obtiennent à droite.
fields.text()fields.textarea()fields.richText()fields.number()fields.boolean()fields.datetime()fields.slug()fields.select()fields.relation()fields.media()fields.group()fields.array()fields.json()fields.blocks()
- Des drapeaux, pas des écrans
- required et localized sont deux booléens sur le champ.
- Slug depuis le titre
- fields.slug({ from: 'title' }) se remplit seul ; select porte des options fixes.
- Aussi depuis l'admin
- Les collections personnalisées se créent dans l'admin et vivent en base de données.
Un contenu, quatre façons de le lire.
Prends celle que ta stack parle déjà.
Les entrées publiques ne demandent pas de clé. Les documents de back-office demandent une clé API ou un bearer token.
curl 'https://cms.altovar.net/api/v1/collections/blog-post/entries?locale=en'
{ "data": [ { "id": "fdoc_16d9i3ef", "locale": "en",
"data": { "title": "EU-first content infrastructure", … },
"seo": { "metaTitle": …, "metaDescription": …, "robots": "index,follow" } } ],
"pagination": { "page": 1, "limit": 20, "total": 3, "totalPages": 1 } }
# The back-office route is the same shape, with a key:
curl -H 'x-api-key: sk_…' \
'https://cms.altovar.net/api/v1/collections/blog-post/documents?limit=10' Un client typé avec la même enveloppe : data et pagination.
import { OspreyClient } from '@osprey/client'
const osprey = new OspreyClient({
baseUrl: 'https://cms.altovar.net',
tenantId: 'altovar',
})
const { data, pagination } = await osprey
.collection('blog-post')
.listEntries({ locale: 'en', limit: 10 }) Requêtes seulement, en POST. GraphiQL est disponible en développement.
POST /api/v1/graphql
x-api-key: sk_…
query Posts($limit: Int) {
documents(collectionSlug: "blog-post", status: "published", limit: $limit) {
data { id status data }
pagination { total }
}
} Les entrées arrivent dans les content collections d'Astro au build. Aucune clé sur l'edge.
---
import { getCollection } from 'astro:content'
const posts = (await getCollection('blog'))
.filter((p) => p.data.locale === 'en')
.sort((a, b) => b.data.publishedAt.localeCompare(a.data.publishedAt))
---
{posts.map((p) => (
<article>
<a href={'/' + p.data.slug}>{p.data.title}</a>
<p>{p.data.excerpt}</p>
</article>
))} Tu publies ici, ça apparaît là.
Les brouillons restent privés. Une entrée publiée atteint l'endpoint public en moins d'une minute, et un webhook peut reconstruire le site.
- Trois statuts
- draft, published, archived. Les pages ajoutent relecture, approbation et planification.
- Webhook signé
- HMAC-SHA256 sur le corps dans x-osprey-signature. Neuf événements, secret chiffré au repos.
- En-têtes de cache
- Les lectures publiques portent max-age=60 avec stale-while-revalidate, prêtes pour n'importe quel cache en amont.
Dix langues, une matrice.
Chaque champ localisé, chaque langue, comptés.
- Langues de contenu
- en, it, es, de, fr, nl, pl, sv, pt, ro, définies par espace de travail.
- Admin
- Anglais et italien, à changer depuis la barre du haut.
- Couverture
- Une matrice par page et par langue, et les chaînes manquantes en CSV.
- Diffusion
- Un paramètre de requête choisit la langue ; les alternates hreflang voyagent avec chaque entrée.
Ton domaine, en quatre étapes.
Par l'API aujourd'hui. La page de réglages arrive, et cette page le dit.
- Réclamer Envoie l'hôte en POST à l'endpoint des domaines. Il reste en attente.
- Prouver Ajoute l'enregistrement TXT _osprey-verify.<host> avec le jeton reçu.
- Vérifier Renvoie un POST avec verify: true. L'hôte passe sur l'espace de travail.
- Pointer CNAME vers la plateforme. Le certificat est émis à la première requête.
POST /api/v1/admin/domains
Authorization: Bearer <jwt>
{ "host": "www.example.com" }
{ "status": "pending",
"verification": { "type": "TXT",
"name": "_osprey-verify.www.example.com",
"value": "osprey-verify=…" } } Les domaines personnalisés dépendent de l'offre ou de l'add-on d'hébergement.
Un contrôle qui montre son travail.
Chaque ligne ci-dessous est une route et un écran, pas une feuille de route.
- Rôles
- admin, editor, author, contributor, viewer
- Clés API
- hachées, montrées une fois, drapeau lecture seule, expiration
- Journal d'audit
- qui, quoi, quand et d'où, pour chaque changement d'état
- Révisions
- historique des pages avec diff et restauration
- Corbeille
- les pages supprimées attendent ; tu les restaures ou les supprimes pour de bon
- Ensemble
- présence et édition partagée des pages, champ par champ
- Consentement
- bandeau cookies et journal des consentements avec IP hachée
- Effacement et export
- un appel caviarde une personne concernée ; export depuis l'admin ou la CLI
- Connexion
- OpenID Connect par espace de travail, ou un mot de passe local
- Limites de débit
- un seau par famille de routes, limites dans les en-têtes de réponse
Les pièces autour.
Deux paquets publiés, une CLI, un serveur MCP et une pile auto-hébergée.
- @osprey/client
- SDK typé, rendu de blocs React, gestionnaire de consentement
- @osprey/astro
- loader de contenu, head SEO, synchronisation des traductions
- CLI
- setup, init, dev, build, generate-types, link, export
- Serveur MCP
- seize outils via stdio, pour l'agent IA que tu utilises déjà
- Auto-hébergement
- un fichier compose et un assistant de premier lancement sur /setup
- Assistant IA
- traduit les manques, complète le SEO, écrit les alt ; éteint tant que tu ne l'allumes pas
Ton contenu. Ton serveur. Aujourd'hui.
Offre gratuite, sans carte. Hébergé dans l'UE, ou sur tes propres machines.