Skip to main content

Schema Contracts

A generated API follows your database. That is the point — and also the risk: someone drops a column, renames a field, or widens a type, and every consumer of the API discovers it at runtime.

Schema Contracts let you lock the shape a SQL service exposes, then tell you when the live database drifts away from it. You can stop there and treat drift as information, or turn on runtime enforcement and have DreamFactory hold the API to the locked shape until you deliberately promote a new one.

Contracts are managed under /api/v2/system/schema_contract/... and in the admin UI under Schema Contracts.


The workflow

  1. Lock a table — DreamFactory captures a snapshot of its current shape as the active contract.
  2. Diff — drift is computed against the live database whenever you look, so it is always current rather than a cached verdict.
  3. Promote — when the drift is intended, promote to make the current shape the new contract.

Locking a table

curl -X POST \
"https://{instance}/api/v2/system/schema_contract/{service}/tables/{table}/lock" \
-H "X-DreamFactory-API-Key: {api_key}"

Each lock stores a versioned snapshot. Previous versions are retained as archives — see Retention below.


Reading contracts and drift

EndpointReturns
GET /system/schema_contract/{service}Service-level contract status
GET /system/schema_contract/{service}/tablesEvery table and its contract status
GET /system/schema_contract/{service}/tables/{table}/snapshotThe active locked contract
GET /system/schema_contract/{service}/tables/{table}/snapshotsSnapshot history (append /{version} for one)
GET /system/schema_contract/{service}/tables/{table}/diffDrift for one table
GET /system/schema_contract/{service}/diffDrift across the whole service
GET /system/schema_contract/{service}/tables/{table}/openapiOpenAPI schema generated from the contract
GET /system/schema_contract/{service}/openapiOpenAPI for the whole service

Drift severity

Every difference is classified, which is what makes drift actionable rather than just a list of changes:

SeverityMeaning
breakingConsumers will break — a column the contract promised is gone or incompatible
potentially_breakingMay break consumers depending on how they use the field
additiveNew surface; existing consumers are unaffected
cosmeticNo consumer-visible effect

Testing and promoting

Validate a table against its contract without changing anything:

curl -X POST \
"https://{instance}/api/v2/system/schema_contract/{service}/tables/{table}/test" \
-H "X-DreamFactory-API-Key: {api_key}"

Accept the current shape for the whole service:

curl -X POST \
"https://{instance}/api/v2/system/schema_contract/{service}/promote" \
-H "X-DreamFactory-API-Key: {api_key}"
Promotion requires a mode

A service whose mode is none cannot be promoted — the call fails with a message telling you to set mode to auto or strict first, via PATCH /system/schema_contract/{service}.

Remove a contract with DELETE .../tables/{table} for one table, or DELETE .../{service} for the whole service.


Service settings

PATCH /api/v2/system/schema_contract/{service} configures how a service treats its contracts:

SettingValuesMeaning
modenone, auto, strictWhether contracts apply to this service at all, and how snapshots are managed. none also blocks promotion.
runtime_enforcementoff, shape_response, strictWhether the contract is enforced on live traffic. See below.
archive_retention_countintegerHow many archived snapshots to keep per table.
enabledbooleanMaster switch for the service's contracts.

Runtime enforcement

By default (off) a contract is documentation and a drift signal — it never touches live traffic. The other two modes change that:

shape_response — on the way out, response fields that are not in the table's active locked contract are stripped. This applies to every verb's response body, including the rows returned by POST, PUT, PATCH and DELETE acknowledgements via ?fields=, so a column added to the database stays invisible to consumers until you re-lock or promote.

strict — everything shape_response does, plus writes are rejected before they run when the payload references fields outside the contract, or writes to fields the contract marks read-only. Payloads are inspected in any of the shapes DreamFactory accepts: wrapped in resource, a bare list, or a bare record.

shape_response hides new columns from consumers

This is the intended behaviour — a locked contract means the API's shape does not change under consumers' feet — but it does mean that adding a column to the database and expecting it in API responses will appear to do nothing until the contract is promoted. If a new field is "missing" from responses on a service with enforcement on, check the contract before checking the database.


Command line

# Emit the canonical JSON schema for a service (or one table)
php artisan schema-contracts:describe {service} [--table=orders] [--pretty]

# Delete surplus archived snapshots per each service's archive_retention_count
php artisan schema-contracts:prune [--service=name] [--dry-run]

--dry-run reports what would be deleted without applying it.


Retention

Every lock and promotion archives the previous snapshot. archive_retention_count caps how many are kept per table, and schema-contracts:prune applies that policy — run it on a schedule if you lock frequently.


Next steps