PUBLISH API LAB / Code & formats

Design a JSON content contract that travels

Define field meaning, ownership, body formats, validation, and versioning before connecting content to multiple destinations.

Updated

GOOD JSON. GREAT CONTENT. — original neon PublishAPI.com featured image

A publishing workflow needs a shared understanding of what a content record means. JSON can carry that record between an editor, a validation step, a renderer, and a destination adapter, but the format alone does not provide the understanding. Two systems can exchange perfectly valid JSON while disagreeing about whether a body contains HTML, whether an empty excerpt should be displayed, or whether a status value authorizes publication.

This guide proposes a small content contract that can grow without turning into a platform-specific dumping ground. It is not a universal standard or a live PublishAPI.com schema. The objective is to make editorial intent portable while keeping destination behavior explicit. A good contract lets each participant reject something it does not understand instead of silently guessing at the meaning.

Begin with one content type

Choose the first type your publication actually needs, such as an article. Describe its purpose in a sentence and list the fields required to render and review it. Resist adding every conceivable property for podcasts, landing pages, newsletters, and social posts before those workflows exist. Premature generality creates optional fields whose relationships nobody can explain.

A proposed first record might contain an internal identifier, revision, title, excerpt, body, body format, and editorial state. Add an image object if images are part of the publication. Keep a schema version separate from the content revision: one describes the contract, while the other describes an edit to a particular story.

Write examples alongside the field definitions. Include a valid ordinary article and several invalid records with explanations. An example showing why a record must be rejected often communicates the contract more clearly than another paragraph of abstract requirements.

Define ownership, not only data types

A string called status can be technically well formed and operationally dangerous. Decide which component is allowed to set it and what the value permits. A model-assisted drafting step should not gain publishing authority by returning an approved value. The application should control the transition after the appropriate review decision.

Apply the same discipline to identifiers, canonical addresses, author attribution, and destination references. Editors may propose some values, while the application normalizes or assigns others. Keep the distinction visible in your implementation rather than hoping every caller follows an unwritten convention.

Separate internal editorial state from destination state. Your application might consider a record approved while the destination still holds a draft. That is not necessarily a conflict; it can be an intentional stage in the release workflow. The contract should make both meanings clear instead of overloading one field to represent them.

Make body representation explicit

Do not name a field content and leave its representation ambiguous. A destination adapter needs to know whether it receives plain text, HTML, Markdown, or another structured representation. Choose a supported set for the first version and specify how each is validated and rendered.

Keep transformations attributable. If Markdown becomes HTML, record which renderer or transformation rules produced the output in your private build metadata where appropriate. Avoid repeated conversions between several rich-text formats unless the project has tested the resulting changes. A round trip that preserves most paragraphs can still lose important captions or semantic structure.

The HTML publishing route explores the distinction between carrying markup and rendering a useful document. A portable record should preserve the meaning needed by that renderer without requiring every destination to accept identical markup.

Distinguish missing, empty, and null

Decide what it means when an optional excerpt is absent. Does the renderer omit it, derive a preview from the body, or return the record for editorial completion? Decide separately what an intentionally empty value means. Treating every case as equivalent may hide the editor’s intent.

The official JSON Schema object reference explains object properties, required properties, and controls such as additionalProperties. Declaring a property in properties does not by itself make it required. Use those mechanisms deliberately to describe accepted record shapes, then add application rules for meaning that structural validation cannot establish.

Return errors that name the problem. “Excerpt is required for newsletter issues” is more helpful than “invalid object.” Include the relevant field path and the rule violated. Do not expose unrelated private content in the error simply because the validator had access to the entire record.

Keep destination mappings outside the core

A CMS may represent categories differently from a static website. A newsletter may require a subject line that an article does not use. Keep these differences in explicit adapters or destination-specific projections rather than filling the core record with a growing set of vaguely named platform fields.

Start with a mapping table for each destination. For every required destination value, identify its source or explain how the adapter derives it. Mark unsupported features honestly. If a target cannot represent a particular content element, the workflow should flag the limitation instead of silently dropping something important.

Keep the remote identifier separate from the internal identifier. Store the association with the destination context so the same source story can have several legitimate remote records. This also gives the correction process a way to find each published variant later.

Version changes according to meaning

Not every edit requires a new contract version. Correcting an article title changes the content revision, not the schema. Changing the meaning of a field or introducing a newly required property is different because existing callers may no longer produce a record the receiver understands.

Document how versions are accepted and retired. A small project can start with a simple explicit version field and a narrow set of supported versions. The important part is that an unsupported record is rejected clearly rather than interpreted under whichever rules happen to be current.

Write migration examples when meaning changes. Show an old record, the transformed record, and any information that cannot be recovered automatically. Some changes require an editor’s decision. A migration should not invent a missing author, publication permission, or source attribution just to make a validator pass.

Test contracts at trust boundaries

Validate when content enters a component that makes new assumptions about it. A record accepted by an editor interface may still need validation before rendering or delivery, especially if another process can modify it. Keep those checks consistent by sharing the contract definition where your architecture supports it.

Build a fixture set that covers ordinary content, absent optional fields, unexpected properties, unknown versions, long strings, malformed references, and disallowed state transitions. Include at least one record that is structurally valid but editorially unacceptable. That example reminds the team that schema validation is not a truth test.

The AI review guide uses the same distinction for generated drafts. A predictable structure can make review more efficient, but a structurally correct claim still needs evidence and an appropriate reviewer.

Give the contract an owner

A content contract should have a named maintenance responsibility within the project. Editors need a way to request a new field, and developers need a way to explain the cost of making it portable across destinations. Without an owner, the contract often becomes whatever the newest adapter happened to require.

Review actual usage before expanding it. A field that repeatedly remains empty may be unnecessary or poorly defined. A recurring manual workaround may indicate a missing concept. Use those observations to improve the record instead of treating the original schema as either untouchable or endlessly flexible.

Test three records before adding another field

Start with three local fixtures for your proposed article contract. The first is a complete record. The second omits the title. The third supplies a body value in a format your renderer does not accept. Write the expected outcome beside each fixture before running the validator.

The complete record should proceed to review; the other two should produce understandable errors rather than guessed replacements. Next, add a field that looks like a publication approval. Confirm that an untrusted content input cannot use it to bypass your application-owned decision.

When a destination needs an extra property, decide whether it belongs in the shared content contract or that destination’s mapping. Update the relevant fixture, version the changed definition when necessary, and rerun the older examples. A short, explainable test set is more useful than a growing collection of fields with no agreed meaning.

Conclusion: make meaning portable

JSON is useful transport, but the durable asset is the agreement around the record. Define a small content type, assign field ownership, name body formats, distinguish missing values, and keep destination projections explicit. Version meaningful changes and test both valid and invalid examples. Start with the JSON Publish API topic and design a contract that a new editor or adapter developer can understand without reverse-engineering the last successful request.

Have a correction or a useful perspective? Get in touch ↗

Before your next release, work through the six publishing checkpoints.