API Guidelines
Rules

Payload Conventions

Rules for JSON structure, media types, and consistent payload conventions.

These rules standardize JSON payload shapes so clients can parse consistently and services can evolve safely.

Related guides:

Related reference:

JSON Structure & Properties

#704 - Use Standard Non-JSON Media Types

Intent: support downloads/exports without forcing JSON.

When non-JSON content types are needed (e.g., binary files, images, CSV), use standard content negotiation with appropriate Content-Type headers and follow HTTP conventions.

Patterns:

  • File download endpoint:
GET /reports/123/content
Accept: application/pdf
HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename="report-123.pdf"
  • Binary upload endpoint:
PUT /avatars/123
Content-Type: image/png

Large exports (recommended):

  • Prefer an async job resource:
    • POST /exports202 Accepted + status URL
    • GET /exports/{id} → status + link to download

Rules:

  • Always set the correct Content-Type.
  • Use Content-Disposition for downloads when appropriate.
  • Keep JSON for error responses even when the success body is non-JSON (see [#404]).

#705 - Use Standard Media Types

Intent: maximize compatibility across tools and clients.

APIs SHOULD use standard IANA-registered media types.

Defaults:

  • Success JSON: application/json
  • Error responses: application/problem+json (see [#404])

Common non-JSON cases:

  • File download: application/pdf, text/csv, application/zip, etc.
  • Binary upload/download: application/octet-stream
  • Multipart upload: multipart/form-data

Rules:

  • Always set the correct Content-Type for responses.
  • Require clients to send the correct Content-Type for requests with bodies.
  • Avoid custom/vendor media types unless you have a strong compatibility reason.

Testability:

  • OpenAPI declares correct content types for request/response bodies.

#706 - Define Maps with additionalProperties

Intent: keep schemas valid and codegen-friendly when keys are dynamic.

When representing a map/dictionary with dynamic keys in JSON, model it as an object with additionalProperties describing the value type.

Requirements:

  • Document the key space (what keys mean, how they are constructed, constraints).
  • Document whether keys are stable (contracted) or arbitrary (user-provided).
  • Avoid maps when the key set is actually bounded/known: use explicit properties or an array of objects instead.

OpenAPI examples:

  • Map of string → string:
type: object
description: Map from feature flag name to state.
additionalProperties:
  type: string
  • Map of string → object:
type: object
description: Map from attribute name to attribute metadata.
additionalProperties:
  $ref: "#/components/schemas/AttributeMetadata"

Alternative (prefer when ordering/duplication matters):

If clients need ordering, duplicates, or additional per-entry metadata, prefer an array:

{
  "attributes": [
    { "key": "department", "value": "Engineering" },
    { "key": "region", "value": "US" }
  ]
}

Notes:

  • Free-form maps make filtering, sorting, and partial updates harder; use them intentionally.
  • If keys are user-provided, call out any normalization rules (case sensitivity, allowed characters).

#708 - Avoid Nested Objects

Intent: reduce payload complexity and improve evolvability.

APIs SHOULD minimize deeply nested object structures.

Preferred patterns:

  • Use references to separate resources (IDs or links) instead of embedding full graphs.
  • If you embed related data, embed only a small, stable summary (e.g., {id, name}), not the full related object.

When nesting is acceptable:

  • Small, intrinsic value objects that are not meaningful as standalone resources (e.g., address, coordinates).
  • Stable summary/reference shapes.

Anti-patterns:

  • Deeply nested graphs where changes to one domain object ripple into many responses.
  • Embedding large sub-objects that make pagination/filtering/patching difficult.

Example (preferred):

{
  "id": "123",
  "ownerRef": { "id": "U1", "name": "Ada" }
}

Anti-example: embedding entire related graphs several levels deep.

#709 - Define Defaults for Optional Properties

Intent: remove ambiguity when clients omit optional fields.

Optional properties MUST have documented default behavior when omitted.

Guidance:

  • If a default is truly stable, declare it in OpenAPI using default.
  • If a default is contextual (depends on tenant config, caller, feature flags), document that explicitly in prose (do not pretend it is constant).
  • Avoid "magic defaults" that vary unpredictably across environments.

Boolean-specific guidance:

  • Every optional boolean property MUST have a documented default behavior that applies when the property is absent.
  • Booleans MUST never be null (see [#700]).
  • If omitted, the server MUST behave as if the default value was supplied.
  • Document the default in OpenAPI (use default when it is truly stable) and in prose (especially if default is contextual).

Examples:

  • includeDisabled (optional): default is false.
  • count (optional): default is false.

Testability:

  • Reviewers can determine what happens when an optional field is omitted.
  • Reviewers can determine the default behavior for every optional boolean.

#710 - Define Required Fields per OpenAPI

Intent: ensure clients and tooling know which inputs are mandatory.

APIs MUST model requiredness explicitly in OpenAPI.

Rules:

  • Request bodies and parameters MUST mark required inputs using OpenAPI required (properties) and required: true (parameters).
  • Responses SHOULD also be modeled accurately: if a field is always present, mark it required; if not always present, do not mark it required.

Create vs update:

  • Do not overload one schema for every operation if requiredness differs.
  • Use separate schemas (e.g., CreateAccountRequest vs UpdateAccountRequest) when the set of required fields differs.

OpenAPI example:

type: object
required: [id, name]
properties:
  id:
    type: string
  name:
    type: string

Testability:

  • A reviewer can generate SDKs/validators that correctly enforce required inputs.
  • Required fields in the spec match actual server behavior.

Boolean & Null Handling

#700 - Handle Null Values Correctly

Intent: avoid confusion, simplify client code, and ensure schemas match runtime behavior.

This rule defines how to handle null values across different data types and contexts.

Part 1: Booleans

Boolean properties MUST never be null.

Rules:

  • If a boolean is required, it MUST be present and MUST be true or false.
  • If a boolean is optional and not applicable, omit it (do not send null).
  • If you truly need three states, use an explicit enum (e.g., "ENABLED" | "DISABLED" | "UNKNOWN") and document the semantics.

Examples:

  • { "enabled": true }
  • {} (if enabled is optional and not applicable)
  • { "enabled": null }

Part 2: Arrays

Empty collections MUST be represented as empty arrays ([]), not null.

Examples:

  • { "items": [] }
  • { "items": null }

Notes:

  • If the property itself is optional, omission is allowed; but if the property is present, it must be an array.

Part 3: Null vs Absent Semantics

Default policy:

  • Treat null and "absent" as equivalent for optional properties unless you explicitly document a different meaning.
  • Prefer omitting optional properties over sending null.
  • If null is allowed, it must not change semantics relative to omission.

Boolean-specific guidance:

  • If a boolean is not applicable, omit it rather than returning null (see Part 1).

Examples:

  • Response payloads: prefer {} over { "field": null } when a field is not present.
  • Request payloads: avoid overloading null vs absent to mean different things (e.g., "clear this value") unless the API explicitly defines that contract.

If you need "clear" semantics:

  • Do not overload null vs absent informally.
  • Prefer explicit PATCH semantics (e.g., JSON Patch) or a documented request contract that clearly defines how values are cleared.

Part 4: OpenAPI Modeling

If an API can legitimately return or accept null for a field, the OpenAPI schema MUST model that.

Rules:

  • If null is allowed at runtime, it must be modeled.
  • If null is not modeled, it MUST NOT appear at runtime.
  • Prefer omitting optional fields rather than returning null (see Part 3).
  • Booleans MUST never be nullable (see Part 1).

OpenAPI guidance:

  • For OpenAPI 3.0.x: use nullable: true.
  • For OpenAPI 3.1.x: use JSON Schema unions (e.g., type: [string, "null"]).

Example (OAS 3.0):

middleName:
  type: string
  nullable: true

Testability:

  • If responses contain null, the spec reflects it.
  • If the spec does not mark nullable, responses do not contain null for that field.
  • Booleans never appear as null in responses.
  • Arrays never appear as null; empty arrays are used instead.

On this page