Todo lo que hace, en una página.

Verificado en el código que lo ejecuta, el 2 de septiembre de 2026. Nada aquí está planificado o por llegar.

El admin existe en inglés e italiano, así que estas capturas son el conjunto en inglés.

El esquema es el formulario.

Lo que escribes a la izquierda es lo que los editores reciben a la derecha.

  • 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()
Flags, no pantallas
required y localized son dos booleanos en el campo.
Slug desde el título
fields.slug({ from: 'title' }) se rellena solo; select lleva opciones fijas.
También desde el admin
Las colecciones personalizadas se crean en el admin y se guardan en la base de datos.

Un contenido, cuatro formas de leerlo.

Elige la que tu stack ya habla.

Las entradas públicas no piden clave. Los documentos de back-office piden una clave API o un bearer token.

shellHTTP
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 cliente tipado con el mismo sobre: data y pagination.

blog.tsTS
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 })

Solo consultas, por POST. GraphiQL está disponible en desarrollo.

graphqlGQL
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 }
  }
}

Las entradas llegan a las content collections de Astro al construir. Sin clave en el edge.

src/pages/index.astroASTRO
---
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>
))}
Leer el documento OpenAPI →

Publicas aquí, aparece allí.

Los borradores siguen privados. Una entrada publicada llega al endpoint público en un minuto, y un webhook puede reconstruir el sitio.

Tres estados
draft, published, archived. Las páginas añaden revisión, aprobación y programación.
Webhook firmado
HMAC-SHA256 sobre el cuerpo en x-osprey-signature. Nueve eventos, secreto cifrado en reposo.
Cabeceras de caché
Las lecturas públicas llevan max-age=60 con stale-while-revalidate, listas para cualquier caché delante.

Diez idiomas, una matriz.

Cada campo localizado, cada idioma, contados.

Idiomas de contenido
en, it, es, de, fr, nl, pl, sv, pt, ro, definidos por workspace.
Admin
Inglés e italiano, se cambian desde la barra superior.
Cobertura
Una matriz por página e idioma, y las cadenas que faltan en un CSV.
Entrega
Un parámetro de consulta elige el idioma; los alternates hreflang viajan con cada entrada.

Tu dominio, en cuatro pasos.

Hoy por API. La página de ajustes está en camino, y esta página lo dice.

  1. Reclama Haz POST del host al endpoint de dominios. Queda pendiente.
  2. Demuestra Añade el registro TXT _osprey-verify.<host> con el token recibido.
  3. Verifica Haz POST otra vez con verify: true. El host pasa al workspace.
  4. Apunta CNAME hacia la plataforma. El certificado se emite en la primera petición.
domainsHTTP
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=…" } }

Los dominios personalizados dependen del plan o del add-on de hosting.

Un control que muestra su trabajo.

Cada línea de abajo es una ruta y una pantalla, no una hoja de ruta.

Roles
admin, editor, author, contributor, viewer
Claves API
hash, mostradas una vez, flag de solo lectura, caducidad
Registro de auditoría
quién, qué, cuándo y desde dónde, por cada cambio de estado
Revisiones
historial de páginas con diff y restauración
Papelera
las páginas borradas esperan; las restauras o las borras del todo
Juntos
presencia y edición compartida de páginas, campo a campo
Consentimiento
banner de cookies y registro de consentimientos con IP en hash
Supresión y exportación
una llamada anonimiza a un interesado; exporta desde el admin o la CLI
Acceso
OpenID Connect por workspace, o contraseña local
Límites de tráfico
un bucket por familia de rutas, límites en las cabeceras de respuesta

Las piezas de alrededor.

Dos paquetes publicados, una CLI, un servidor MCP y un stack self-host.

@osprey/client
SDK tipado, renderizador de bloques React, gestor de consentimiento
@osprey/astro
loader de contenido, head SEO, sincronización de traducciones
CLI
setup, init, dev, build, generate-types, link, export
Servidor MCP
dieciséis herramientas por stdio, para el agente de IA que ya usas
Self-host
un archivo compose y un asistente de primer arranque en /setup
Asistente de IA
traduce huecos, completa el SEO, escribe los alt; apagado hasta que lo enciendes

Tu contenido. Tu servidor. Hoy.

Plan gratuito, sin tarjeta. Alojado en la UE, o en tus propias máquinas.