Concepts
Document formats
Article, planning item, newswire, image, workflow, AI operation - the shapes on the wire.
Document formats
Every editorial artefact in Tidenda - articles, planning items, newswire feeds, workflows themselves - is modelled as a document. This page describes the shapes integrators meet on the wire: in webhook payloads, in the public injection API, and in the snapshot JSON the platform signs on publish.
All documents share the same envelope: id, type, title,
meta, content, links, and history. What changes between types
is what lives under meta and content. Admin-internal document
types (API keys, webhook configs, user records) are not documented
here - they're not part of the integrator-facing surface.
The shapes below are sketches for quick reference. The runtime
validates incoming documents against the canonical schemas — if your
payload doesn't match, the response carries an INVALID_PAYLOAD
error with the failure reason.
The shared envelope
Every document carries this envelope before its type-specific content.
{
"id": "string", // UUID, tenant-scoped
"type": "string", // e.g. "core/article"
"title": "rich text", // serialises to plain string in JSON
"meta": {
"priority": 3, // 1-6, lower is more important
"visibility": "internal", // or "public"; optional
"time": [ /* TimeObjectSchema entries */ ],
"lang": "en", // optional BCP47
// Workflow-stamped fields. Set by the server when the doc is
// bound to a workflow; absent on pre-workflow docs.
"status": "draft",
"version": 1,
"workflowId": "wf_...",
"workflowVersion": 1,
"publishedVersion": 3, // present iff the doc has ever published
"aiPending": { /* present while an AI transition is in flight */ }
},
"content": { /* type-specific */ },
"links": [
{ "type": "core/planning-item", "rel": "planning", "uri": "doc:..." }
],
"history": [ /* append-only history entries; see Workflows */ ]
}
Reserved statuses
Four status names carry platform-wide semantics; tenant-defined statuses are otherwise opaque codes.
| Status | Meaning |
|---|---|
draft |
Entry status. Always present in every workflow. |
usable |
Read-only, snapshotted. Integrator signal: "publish this". |
withheld |
Read-only, snapshotted. Embargoed / scheduled release. |
cancelled |
Read-only, soft delete. Integrator signal: "unpublish". |
Transitions into usable and withheld produce a signed snapshot;
the signature lands on the history entry's snapshot field. See
Receiving webhooks for the wire
format.
core/article
The publishable story - what ends up on a site, in a feed, or in a print system.
{
"id": "art_...",
"type": "core/article",
"title": "rich text", // headline
"meta": {
"priority": 3,
"time": [
// Must include `target` (intended publish moment) or `slot`.
{ "type": "target", "time": "2026-06-15T09:00:00Z", "timeZone": "Europe/Stockholm" }
],
"status": "usable",
"publishedVersion": 3
},
"content": {
"text": "rich text" // article body
},
"links": [
// Both links are required.
{ "type": "core/planning-item", "rel": "planning", "uri": "doc:pln_..." },
{ "type": "core/planning-assignment", "rel": "assignment", "uri": "doc:asg_..." }
]
}
Notable
meta.timemust contain either atargetor aslotentry; validation rejects an article without one.linksmust include exactly onecore/planning-itemlink and onecore/planning-assignmentlink. Articles are not free-floating.- The body lives at
content.textas rich text. In webhook snapshot bytes it serialises to a JSON node tree — see Receiving webhooks for the rendering recipe.
When you'll see this
- Arrives in
document.usablewebhook payloads as the signeddocument.snapshotByteswhenever an article transitions tousable(orwithheld). See Receiving webhooks.
core/planning-item
The editorial plan a story is produced against. Holds the slugline, description, and the set of assignments that flesh it out.
{
"id": "pln_...",
"type": "core/planning-item",
"title": "rich text",
"meta": {
"priority": 3, // required, 1-6
"time": [
// Both startdate and enddate required. Date-only strings.
{ "type": "startdate", "time": "2026-06-15", "timeZone": "Europe/Stockholm" },
{ "type": "enddate", "time": "2026-06-16", "timeZone": "Europe/Stockholm" }
],
"status": "usable"
},
"content": {
"slugline": "string",
"description": "rich text",
"assignments": [ /* core/planning-assignment, inline */ ]
},
"links": []
}
Notable
meta.timemust contain both astartdateand anenddateentry, as date-only strings (not full timestamps).meta.priorityis required and must be 1-6.- Assignments live inline under
content.assignmentsas full sub-documents, not as link references.
When you'll see this
- Arrives in
document.usablewebhooks the same way articles do, and is referenced by every article via the requiredcore/planning-itemlink.
core/planning-assignment
A sub-document inside a planning item's content.assignments. Ties
the plan to a specific media output and the window it should
publish in.
{
"id": "asg_...",
"type": "core/planning-assignment",
"title": "rich text",
"meta": {
"type": "text", // text | flash | image | graphic | video | audio
"priority": 3,
"time": [
// Optional planning hint. Once the article exists, the
// article's own meta.time[target] is authoritative.
{ "type": "target", "time": "2026-06-15T09:00:00Z", "timeZone": "Europe/Stockholm" }
]
},
"content": {
"slugline": "string",
"description": "rich text"
},
"links": []
}
Notable
meta.typeis fixed to one oftext,flash,image,graphic,video,audio.- Assignments are never stored as top-level documents - they live
inside a parent
core/planning-item. They keep their ownidso articles can link back to a specific assignment. meta.timeon an assignment is a planning hint; the produced article'smeta.time[target]overrides it.
When you'll see this
- Inline inside any
core/planning-itempayload, and referenced by the requiredcore/planning-assignmentlink on every article.
core/image
A tenant-stored image asset. The bytes live in object storage at
images/<id>/<variant>; the document carries metadata, rights,
provenance, and the signing record. See Media for the
upload model, format support, and the rights / provenance contract.
{
"id": "img_...",
"type": "core/image",
"title": "Spring festival - opening parade",
"meta": {
"priority": 3,
"status": "draft", // workflow status
"version": 1,
"workflowId": "wf_...",
"workflowVersion": 1,
// Identity. Server-stamped on finalize.
"contentHash": "9f86d081884c7d659...", // SHA-256, lowercase hex
"originalFormat": "image/jpeg",
"originalSize": 4823651, // bytes
"dimensions": { "width": 4032, "height": 3024 },
// Authorship and rights.
"copyright": "(C) 2026 Jane Doe / Reuters",
"photographer": "Jane Doe",
"credit": "Reuters",
"jobId": "REU-2026-04127",
"restrictions": true, // server-computed
"instructions": "Free for editorial use during Q2 2026.",
"usageTerms": "Editorial use only. No derivatives.",
// Capture (optional - present when EXIF carried them).
"capturedAt": "2026-04-15T11:23:54Z",
"capturedAtTimezone": "Europe/Stockholm",
"geo": { "lat": 59.3293, "lon": 18.0686 },
"camera": { "make": "Canon", "model": "EOS R5", "lens": "RF24-70mm" },
// Provenance.
"c2paManifest": {
"present": true,
"issuer": "Adobe",
"claimGenerator": "Adobe Photoshop 2026",
"validatedAt": "2026-04-15T11:30:00Z"
},
"aiGenerated": false, // tri-state: true / false / absent
"signature": {
"alg": "ed25519",
"keyId": "key_...",
"sig": "base64..."
},
// Processing.
"processingStatus": "ready", // pending | ready | failed
"variants": ["original", "thumb", "preview", "delivery"]
},
"content": {
"altText": "Crowd lines the parade route under a cloudy sky.",
"caption": "rich text", // shown beside the image
"rights": "rich text" // longer-form usage notes
},
"links": [
{ "type": "core/planning-item", "rel": "planning", "uri": "doc:plan_..." },
{ "type": "core/planning-assignment", "rel": "assignment", "uri": "doc:asg_..." }
]
}
Notable
meta.restrictionsis server-computed:truewheninstructionsis set,usageTermsis set, oraiGeneratedistrue. Surfaces a warning marker on every image render; integrators should mirror that treatment downstream.meta.aiGeneratedis tri-state. Absent means "unknown", not "false" - don't conflate the two when filtering.meta.signatureis an Ed25519 signature over the domain- prefixed original bytes ("void-image-v1\0"+ original). It is in addition to any C2PA chain. See Signing.meta.processingStatus: 'ready'requirescontentHash,originalFormat,originalSize,dimensions, and at least adeliveryvariant - the platform refuses to flip an image to ready without them.
When you'll see this
- Returned alongside the bytes URL by the media archive endpoints,
and embedded in any
core/articlewhose body references an image. Webhook payloads carry the document on its workflow transitions like any other type.
core/newswire
Raw inbound material: wires, press releases, media submissions. Stored verbatim before any editorial work happens against it.
{
"id": "nws_...",
"type": "core/newswire",
"title": "rich text",
"meta": {
"priority": 3,
"origin": "wire", // wire | pressrelease | media
"source": "string", // upstream provider id, free text
"status": "draft"
},
"content": {
"text": "rich text" // wire body
},
"links": []
}
Notable
meta.originis one ofwire,pressrelease,media.meta.sourceis a free-text upstream identifier (e.g. the wire agency's slug); carried through but not validated against a list.- Newswires typically don't change after ingestion - editorial
work happens by spawning a
core/articlethat links back.
When you'll see this
- Push it in via the newswire ingestion endpoint. Shows up in webhook payloads if you subscribe a workflow event to this type; most tenants subscribe articles, not newswires.
core/workflow
The workflow definition itself - the statuses a document type can sit at and the transitions between them. Workflows are documents, so they version and ship through the same pipeline as the content they govern.
See Workflows for the conceptual model and the default-binding rules.
{
"id": "wf_...",
"type": "core/workflow",
"title": "rich text",
"meta": {
"priority": 3,
"appliesToType": "core/article", // the doc type this governs
"isDefault": true, // at most one default per (tenant, appliesToType)
"status": "usable" // only `usable` workflows are bindable
},
"content": {
"statuses": [
{
"name": "draft", // kebab-case ASCII machine identifier
"title": "Draft", // human label
"description": "Work in progress.",
"icon": "circle-dashed",
"color": "neutral"
}
],
"transitions": [
{
"from": "draft",
"to": "usable",
"title": "Publish", // action verb in the status menu
"requiredRoles": ["editor"],
"bumpsVersion": false,
"ai": { /* optional - see core/ai-operation */ },
"confirm": { /* optional confirmation dialog */ }
}
]
},
"links": []
}
Notable
- A status named
draftmust exist; it's the entry point for every document on the workflow. - Every transition's
from,to, and anyai.onSuccess/ai.onFailuremust reference a declared status. - Transitions leaving a read-only status (
usable,withheld,cancelled) to anything other thancancelledmust setbumpsVersion: true- re-opening a published doc creates a new editable iteration. - Workflows progress through their own hardcoded lifecycle (
draft -> usable -> cancelled). Onlyusableworkflows are bindable. TheisDefaultuniqueness across(tenant, appliesToType)is enforced server-side, not by the schema.
When you'll see this
- Returned by the workflow management endpoints, and identified on
every governed document by
meta.workflowIdandmeta.workflowVersionso consumers can reproduce the rules a transition was decided under.
core/ai-operation
A tenant-defined AI recipe: instructions, static context, the JSON
Schema the model's output must satisfy, and how that output writes
back into document space. Bound to a workflow transition via
transition.ai.operationRef.
See AI integrations for the lifecycle.
{
"id": "aiop_...",
"type": "core/ai-operation",
"title": "rich text",
"meta": {
"priority": 3,
"appliesToType": "core/article", // primary source doc type
"connectorId": "conn_...", // FK into ai_connectors
"exportName": "translate-to-swedish", // stable ref workflows bind to
"status": "usable" // only `usable` ops fire
},
"content": {
"instructions": "rich text", // system prompt, free-form prose
"documentProjection": { // what slice of the source to send
"mode": "fields", // or "full"
"paths": ["title", "content.text"]
},
"staticContext": [ // named JSON blobs in the prompt
{ "name": "glossary", "data": "rich text" }
],
"inputs": [ // optional user-supplied params
{
"kind": "enum", // or "string"
"name": "targetLanguage",
"label": "Target language",
"required": true,
"options": [{ "value": "sv", "label": "Swedish" }]
}
],
"outputSchema": { "schema": "rich text" }, // JSON Schema as text
"outputTargets": [ // where output JSON lands
{ "kind": "path", "from": "title", "to": "title" },
{ "kind": "textPatch", "from": "patches", "targetField": "content.text" },
{ "kind": "textbitReplace", "from": "body", "targetField": "content.text" },
{ "kind": "createDocument", "from": "extra", "documentType": "core/article" },
{ "kind": "inputPath", "from": "targetLanguage", "to": "meta.lang" }
],
"sampling": { "model": "claude-opus-4-7", "temperature": 0.2 },
"safety": { "maxInvocationsPerHour": 60, "maxAutoRetries": 2 }
},
"links": []
}
Notable
meta.exportNameis the stable, tenant-scoped name workflows bind to viatransition.ai.operationRef. Required (kebab-case) before promotion tousable; tolerated empty during draft edits.- Static-context entry names and input names must be unique within the operation - both render as named blocks in the prompt, so a collision would silently shadow one.
- Output targets must write to distinct destinations: no two
path/inputPathmay share ato; no twotextPatch/textbitReplacemay share atargetField. MultiplecreateDocumenttargets are fine. - An empty
outputTargetsis valid - the operation runs as advisory and the result lives only in the invocation log.
When you'll see this
- Authored in the AI operations editor; otherwise an internal asset.
Fires when a document arrives at a workflow status whose incoming
transition carries an
ai.operationRefmatching this operation'smeta.exportName.
Related pages
- Workflows — how a workflow resolves a transition, including AI-bound ones.
- AI integrations — the AI operation lifecycle: dispatch, retries, output write-back, invocation log.
- Receiving webhooks — webhook
payload shape, signature verification, and how
document.snapshotBytesrelates to the schemas above.