Production synchronization
Use this protected machine-client route to validate or synchronize a complete publication bundle. It is intended for an authorized source machine and a target publication owner; it is not a replacement for the single-document import workflow.
Authentication
POST /import/production requires a bearer token with all of these properties:
- Its client ID matches the source machine ID in the bundle and the source machine configured for the service.
- Its
principal_typeisoauth_clientand itsmachine_owner_proxyclaim istrue. - Its
audclaim contains the configured production-import audience. The default ishttps://api.blog.service.m7.org/v1/; an installation can configure a different audience, which must match exactly.
For an existing target publication, the authenticated proxy principal also needs import.production. When the target publication does not exist, the principal needs publication.create.
Route
| Route | Required fields | Optional fields | Success result |
|---|---|---|---|
POST /import/production |
bundle |
apply, sync_mode |
Dry-run plan or applied synchronization summary. |
Request fields
| Field | Required | Type | Description |
|---|---|---|---|
bundle |
Yes | Object | m7_blog_production_bundle version 1, at most 100 MiB when JSON encoded. |
apply |
No | Boolean | Defaults to false. Set true only after reviewing a dry run. |
sync_mode |
No | merge or overwrite |
Defaults to merge. |
The bundle must contain:
| Bundle field | Required | Description |
|---|---|---|
format |
Yes | Exactly m7_blog_production_bundle. |
version |
Yes | Exactly 1. |
source.machine_id |
Yes | UUID that matches the authenticated client ID. |
publication.source_id |
Yes | Source publication UUID. |
publication.slug |
Yes | Target publication slug. |
publication.name |
Yes | Target publication name. |
data |
Yes | Publication data object. |
integrity.data_sha256 |
Yes | Lowercase SHA-256 of the final compact JSON encoding of data. |
data is required, but its sections are optional. Omitted record sections behave as empty arrays, and omitted settings behaves as an empty settings value. With merge, omitted sections leave target records unchanged; with overwrite, the bundle is authoritative and empty sections remove the corresponding target records.
Every record section is an array. Rows in every section except post_authors and post_tags require a unique source_id UUID. References use these source IDs and must point to a record included in the same bundle.
Data records and references
Use the following fields for a complete bundle. Fields described as optional may be omitted from a row; all other fields in the table are required when that row type is present.
| Section | Required fields | Optional fields |
|---|---|---|
authors |
source_id, slug, name |
bio, avatar_media_source_id, external_url, socials, metadata |
categories |
source_id, slug, name |
description, summary, active, featured_media_source_id, seo, metadata |
tags |
source_id, slug, name |
description, seo, metadata |
posts |
source_id, slug, title |
excerpt, featured_media_source_id, category_source_id, body, body_format, status, visibility, published_at, scheduled_at, archived, archive_note |
post_authors |
post_source_id, author_source_id |
position |
post_tags |
post_source_id, tag_source_id |
— |
pages |
source_id, slug, title |
body, body_format, status, visibility, published_at, archived, archive_note |
redirects |
source_id, source_path, destination_path |
status_code |
media_items |
source_id, kind, state |
title, alt_text, caption, metadata, deleted_at |
media_renditions |
source_id, media_source_id, source, state, role |
asset_id, external_url, original_filename, mime_type, byte_size, width, height, duration_ms, metadata, deleted_at |
content_media |
source_id, media_source_id, content_type, content_source_id |
usage, sort_order, alt_text, caption, metadata |
domain_verifications |
source_id, host, verification_method, token, status, expires_at |
last_checked_at, verified_at |
domains |
source_id, host, verification_source_id, verification_method, verified_at |
— |
revisions |
source_id, content_type, content_source_id, kind, snapshot |
— |
imports |
source_id, source_name, detected_format, state |
source_content, parsed_payload, parse_errors, applied_post_source_id |
exports |
source_id, format, state |
selection, archive_filename |
settings is optional. Send an object to update settings; omit it or use an empty array when it has no settings to apply. publication.featured_media_source_id, avatar_media_source_id, featured_media_source_id, category_source_id, media_source_id, content_source_id, verification_source_id, and applied_post_source_id are source-ID references; omit or set an optional reference to null when it has no target.
Posts use draft, review, scheduled, or published status; pages use draft or published; and their visibility is public, unlisted, or private. Media items use draft, ready, failed, or deleted; renditions use bigfs or external as source and reserved, ready, failed, or deleted as state. Domain verification methods are http or dns, and verification status is pending, verified, or expired. Revisions use saved or autosave; imports use markdown_front_matter or unknown and ready, invalid, applied, or cancelled; exports use markdown_zip and ready or cancelled.
Date-time fields use YYYY-MM-DD HH:MM:SS with optional microseconds, or null where allowed. The import does not preserve an exported archived_at; it applies the requested archived state and records the target-side archival time.
Dry run and apply
Start with a dry run. This is a structurally valid empty bundle; the checksum is the SHA-256 of its compact data value, {"settings":[]}. Replace the fake UUIDs with the configured machine ID and your publication source ID before sending it.
{
"bundle": {
"format": "m7_blog_production_bundle",
"version": 1,
"source": { "machine_id": "11111111-1111-1111-1111-111111111111" },
"publication": {
"source_id": "22222222-2222-2222-2222-222222222222",
"slug": "engineering-notes",
"name": "Engineering Notes"
},
"integrity": { "data_sha256": "845d7dd3ba293b9cb483a452d4f0c3af713d47eb9bf5e08176d6a68322d8126d" },
"data": { "settings": [] }
},
"apply": false,
"sync_mode": "merge"
}
Calculate integrity.data_sha256 only after assembling the final data value. Serialize it as compact JSON, calculate the SHA-256 digest of those bytes, and do not mutate its values or key order before sending the request.
The dry-run result has dry_run: true, applied: false, publication_id (or null when it will be created), publication_slug, publication_action (create or update), authenticated_proxy_actor_id, verified_source_machine_id, bundle_source_machine_id, sync_mode, summary, and warnings. It makes no data changes.
When the review is acceptable, resend the same bundle with apply: true. merge writes bundle records and keeps target records not represented in the bundle. overwrite makes the bundle source-authoritative and removes target publication records absent from the bundle. An applied result includes all dry-run identity fields with dry_run: false and applied: true, plus imported. imported contains the per-section imported counts, settings_saved, sync_mode, and a removed map for records deleted by an overwrite.