# Murit CMS > Headless CMS engineered for modern web teams and vibe coders. Murit CMS provides an isolated, multi-tenant headless content management engine built on Payload 3 and Next.js 16. It serves clean, read-only Content API v1 DTOs directly to client frontends without requiring website rebuilds when editors publish content. ## Database storage monitoring Workspace media totals use database aggregates across all matching media records, with one grouped query for the authorized website list. Totals still sum original-file sizes and exclude BYOS sites from the platform pool; they are not cloud-billing measurements. Public post-summary and sitemap queries omit article bodies from database reads. Response shapes, permissions, cache headers, and MCP tools are unchanged. Platform admins see CMS database storage on Overview. Authenticated GET /api/workspace includes databaseUsage only for platform admins: { status, usedBytes, limitBytes, usagePercent, checkedAt }. Status is healthy, warning (80%), critical (90%), urgent (95%), full (100%), or unavailable (null usage). It measures connected-database tables and indexes (including article text, drafts, revisions, and operational indexes); it excludes media files stored in object storage. The default budget is 5,000,000,000 bytes; operators can set CMS_DATABASE_STORAGE_LIMIT_BYTES after an infrastructure change. Refresh is manual or every 24 hours while Overview is open. This is a storage warning, not a publishing block, backup, or billing guarantee. Public Content API and MCP tools do not expose these private metrics. ## Project setup and storage General offers default 200 MB workspace Murit CMS storage or optional BYOS. Next opens review with optional webhook, SEO and localization settings. Only the final create action creates the site key. Provider guides: /docs/storage. Cloudflare R2 is recommended; S3 and GCS are supported, Azure Blob is not. Provider allowances and billing apply to BYOS; switching storage does not migrate existing files. ## Key Links Legacy media migration supports 100% self-serve in-browser media ingestion in the Admin Console (one-click "Ingest All Media Assets into Storage" trigger) and REST API (GET/POST /api/workspace/migrate-media with batchSize: 5 and skipUrls). CLI ingestion is also available via "npm run media:migrate -- --website --rewrite-mode relative". Optional sourceOrigin resolves root-relative image paths; preview/import return mediaPreservation (strategy preserve-urls, sourceOrigin, urls, hostingVerified false, notice). Frontend clients can serve paths via same-origin reverse proxy (/wp-content/uploads/:path* -> bucket) for 100% SEO preservation with zero redirects. Edit featured references in Media and inline externalImage blocks in articles (ingestion upgrades them to native upload cards that keep alt text, captions, links, alignment and width). Public image URLs must not be rebased to the CMS or new bucket before ingestion. RJLS must retain its upload tree and adapt its generated-media cleanup before cutover. - Main Website: https://www.murit.space - Developer Documentation: https://www.murit.space/docs - AI & Vibe-Coding Hub: https://www.murit.space/docs/ai - Create an account: https://www.murit.space/signup - OpenAPI 3.1 Spec: https://www.murit.space/openapi.json - Full LLM Specification: https://www.murit.space/llms-full.txt - Sitemap Index: https://www.murit.space/sitemap.xml - AI Agent Standards: AGENTS.md, CLAUDE.md, OPENCODE.md, opencode.json, .cursorrules ## Content API v1 Specification Base URL: {CMS_BASE_URL} (e.g. process.env.CMS_BASE_URL or https://www.murit.space) Public Site Key: {CMS_SITE_KEY} (e.g. process.env.CMS_SITE_KEY) ### Endpoints - GET /api/content/v1/sites/{siteKey} Returns site metadata (name, defaultLocale, blogBasePath, primaryURL, timezone, seoDefaults). Supports root '/' blog base paths. - GET /api/content/v1/sites/{siteKey}/posts?limit={1-50}&cursor={opaque}&category={slug}&tag={slug}&format={default|astro} Returns paginated published post summaries. When format=astro is provided, returns flat Astro-ready posts. - GET /api/content/v1/sites/{siteKey}/posts/{slug}?format={default|astro} Returns full post with pre-rendered, sanitized lexical HTML (contentHtml), categories, tags, and SEO metadata. Automatically responds with HTTP 308 Permanent Redirect and Location header if queried by a legacyUrl or redirectUrls entry. - GET /api/content/v1/sites/{siteKey}/legacy-posts Returns { posts: [AstroPost] } of published posts optimized for Astro 5 and SSG with preserved legacy URLs, canonical URLs, redirectUrls, and rich media. - GET /rjls-api/posts?siteKey={siteKey} Backward-compatibility alias for the pre-migration RJLS frontend. Requires an explicit registered siteKey and returns only that website's published posts. - GET /api/content/v1/sites/{siteKey}/sitemap?limit={1-500}&cursor={opaque} Returns list of published post loc URLs and lastmod timestamps for sitemap generation. - GET /api/content/v1/sites/{siteKey}/preview?token={signedToken} Redeems an ephemeral 5-minute cryptographic preview token to inspect saved drafts. - POST /api/workspace/migrate Authenticated endpoint for bulk legacy blog migration (WordPress, Payload, custom feeds) with slug, SEO preservation, automatic normalization of Payload 3 block aliases, conversion of 'Also Read' links to RelatedArticle blocks, conversion of Twitter/social blockquotes to SocialEmbed blocks, conversion of unsupported blocks into native Lexical elements, and automatic HTML entity decoding across titles, excerpts, Lexical AST text nodes, categories, and tags. The same normalization runs whenever a post is read or saved in the editor, so supported blocks stored as plain paragraphs (Also Read links, bare social/YouTube/Vimeo URLs) are promoted to editable native blocks. Existing collections can be normalized via "npm run repair:entities". Migration (CMS-side, POST /api/workspace/migrate): fully supported sources are WordPress (source "wordpress", siteURL + page, 25 posts per page with _embed), Shopify (source "shopify-admin" with shopDomain *.myshopify.com + read_content Admin API token + cursor, or "shopify-feed" with feedURL), and Payload CMS (source "payload", siteURL + page, 100 posts per page; supporting standard /api/posts and custom RJLS /rjls-api/posts endpoints), plus Murit/Payload JSON. Rendered HTML is parsed as a document tree: images (captions, links), YouTube/Vimeo/MP4 video, Spotify/SoundCloud/Google Maps/TikTok/CodePen embeds, notices/alerts/callouts, button groups, FAQ/accordion groups, image galleries, simple tables, X/Instagram/Facebook/LinkedIn embeds, Also Read links, Gutenberg columns, separators and
 code (code block { code, language }, language kept, unknown -> plaintext) become native blocks; link rel nofollow/sponsored/ugc is kept on link nodes (fields.rel); anything else becomes a legacyHtml block instead of flattened text. Responses include report { articles, cleanArticles, elements, preserved, needsReview } and skippedCount; import returns postIds, and action "resolve-links" with postIds links Also Read blocks across batches. Block nodes are always stored as Lexical version 2 (version-1 nodes are upgraded on read).
  Migration is best-effort: importing creates a copy and never modifies the source post. Only markup with a reliable structural, platform-class, or schema.org signal becomes a native block; everything else is preserved in a sanitized legacyHtml block and flagged for review. Detection can be wrong, so review the full article before publishing and keep a source backup; re-publishing an imported post without review may surface an incorrect or unusual result. Inline images stay hotlinked until media ingestion runs.
- GET /api/workspace/migrate-media?websiteId={websiteId}
  Authenticated endpoint for auditing legacy media assets, returning total, already uploaded, pending count, storage provider label, preview samples, and complete asset lists with source and storage URLs.
- POST /api/workspace/migrate-media
  Authenticated endpoint for incremental batch legacy media ingestion into CMS or BYOS storage (Cloudflare R2, AWS S3, GCS) with automatic post Lexical AST rewriting, retry, and 404 skip handling.
- GET /api/workspace/social-preview?url={url}&platform={platform}
  Authenticated endpoint for fetching server-side social post metadata and previews (LinkedIn, X/Twitter, Instagram, Facebook), preventing client-side browser tracking blocks.
- GET /api/workspace/resolve-image?url={url}
  Authenticated endpoint that resolves a pasted link to a direct image URL. Direct image links pass through; web page links (e.g. Unsplash photo pages) are resolved from their OpenGraph preview image. Images are referenced by URL and never copied to storage.
- POST /api/workspace/websites/{id}/delete-request
  Authenticated endpoint for requesting project deletion with a 48-hour grace period timeline. Requires confirmation by typing the project name or "DELETE". Pauses Content API delivery immediately while pending deletion.
- POST /api/workspace/websites/{id}/restore
  Authenticated endpoint for reversing a project deletion request within the 48-hour grace period, immediately reactivating the project and resuming Content API delivery. Returns HTTP 410 after the deadline; the project is then permanently purged (website, posts, media including cloud storage, categories, tags, memberships) and disappears from /api/workspace.

## Integration Principles
CMS operators: production requires the registered 20260909_122610_legacy_redirects schema migration for legacyUrl, redirectUrls, and nullable blogBasePath. Frontend integrations do not run CMS database migrations.
1. Public Site Key is an identifier, not a secret. It is safe to use in client code.
2. Posts contain pre-rendered, sanitized 'contentHtml' ready for direct dangerouslySetInnerHTML / set:html injection. Embeds (YouTube, Vimeo, Social Embeds for Twitter/X, Instagram, LinkedIn, and Facebook with responsive 50% compact sizing), video players (uploaded and streaming MP4/WebM), Also Read cards, checklists, code blocks (
, HTML-escaped), inline images, and responsive column grids are already rendered; style the emitted classes (.content-columns--50-50, .content-columns__column, .content-video, .content-video-wrap, .content-embed, .content-embed--social, etc.) and let cells stack on narrow viewports. Images and videos resized in the editor carry their saved percentage: images as an inline 'style="width:{n}%"' and videos wrapped in '.content-video-wrap' with the same inline width; full width emits no width so your own responsive CSS applies. Links keep SEO rel tokens (nofollow, sponsored, ugc) set in the editor or imported from WordPress/Shopify, e.g. rel="nofollow sponsored"; new-tab links append noopener noreferrer (rel="sponsored noopener noreferrer"). Do not strip rel.
   Imported articles: images may carry a 
and a link, plus optional content-image--left/right/center alignment classes with an inline float/text-align style and a percentage width; captioned YouTube/Vimeo embeds are wrapped in figure.content-embed-figure; markup with no safe native block (complex or linked tables, mixed-content galleries, prose-wrapped button groups, plugin widgets) arrives as allowlist-sanitized HTML inside div.content-legacy-html (no scripts, event handlers or javascript: URLs; iframes only from YouTube, Vimeo, Spotify, SoundCloud, Google Maps and the social networks). Style these like ordinary article HTML. 3. Content API responses set 'Cache-Control: public, s-maxage=60, stale-while-revalidate=300' and include 'ETag' headers. 4. Media & Content Storage Architecture: Workspaces start on the Free Starter Tier (200 MB included persistent storage shared across projects with zero setup and global edge CDN). For unlimited asset storage and full data sovereignty, tenants connect Bring Your Own Storage (BYOS) via Cloudflare R2, AWS S3, or Google Cloud Storage. Under this Thin Index Database + Cloud JSON Storage architecture, rich-text article bodies and SEO live in tenant storage (sites/{siteKey}/posts/{postId}/thick_content.json), and the CMS database retains only thin operational indexes. 5. For full schemas, TypeScript types, and framework snippets, fetch https://www.murit.space/llms-full.txt. 6. Cross-Origin Resource Sharing (CORS): All public Content API routes, media delivery, and compatibility endpoints return 'Access-Control-Allow-Origin: *' with 'OPTIONS' preflight support, enabling direct client-side fetch from browsers, SPAs, and mobile web apps. 7. HTML Entity Sanitization & Clean Editor Text: Murit CMS automatically decodes HTML entities (e.g. ' -> ', " -> ", & -> &, and curly quotes ‘/’/“/”) across post titles, excerpts, Lexical editor AST text nodes, categories, tags, and media alt texts during import and via real-time collection hooks (beforeValidate and afterRead). Existing database entries can be repaired via "npm run repair:entities".