Features

Built for the people who ship.

Osprey gives developers schema-as-code and content teams a real editor, on infrastructure that stays in Europe.

Everything your
content team needs.

content/article.ts
import { defineCollection, fields } from '@osprey/core'

export const articles = defineCollection({
  slug: 'articles',
  fields: {
    title:   fields.text({ required: true, localized: true }),
    content: fields.richText({ localized: true }),
    cover:   fields.media(),
    status:  fields.select({ options: ['draft', 'published'] }),
  }
})

Schema in code, not settings panels.

Define content types in TypeScript. Typed fields, version-controlled in your repo, diffable in pull requests. No GUI surprises when a field shape changes.

StringRichText RelationMedia
GET /api/v1/collections GET /api/v1/collections/{slug}/documents POST /api/v1/graphql GET /api/v1/media/{id} WH content.published

Delivered from Europe. Fast.

REST, GraphQL, TypeScript SDK: same data, your choice. EU-only CDN with smart invalidation. Webhooks on every mutation, HMAC-signed with exponential retry.

<50ms
TTFB, EU edge

Real-time collaboration

Multiple editors, inline comments, conflict-free concurrent writes.

Multi-locale native

Dozens of languages with smart fallbacks and per-locale field overrides.

GDPR-native by design

EU-only hosting. No data leaves Europe. DPA included on all paid plans.

Ultra-fast delivery

Global CDN, smart cache invalidation. TTFB under 50ms. Your frontend never waits.

One API.
Three styles.

Use whichever fits your stack: TypeScript SDK, REST, or GraphQL. Same data, same power, zero lock-in.

import { OspreyClient } from '@osprey/client'

const osprey = new OspreyClient({
  baseUrl:  'https://cms.altovar.net/api/v1',
  tenantId: 'acme',
  apiKey:   process.env.OSPREY_API_KEY,
})

const { data, pagination } = await osprey
  .collection('articles')
  .find({ where: { status: 'published' }, limit: 10 })
GET /api/v1/collections/articles/documents
  ?status=published&limit=10

x-api-key: sk_live_...

200 OK
{ "data": [{ "id": "doc_9f2a", "status": "published" }],
  "total": 42, "page": 1, "limit": 10 }
query Articles($limit: Int) {
  documents(collectionSlug: "articles",
            status: "published", limit: $limit) {
    data { id status data }
    pagination { total }
  }
}

Your content. Your server. Ship today.

Free plan, no card required. Running in the EU: your data never leaves Europe.