Contract sections¶
The fields that name a service, say who owns it and list what it exposes:
pactoVersion, service, interfaces, capabilities, metadata and
extensions. Configuration and policy are in Configuration and policy;
dependencies and state are in
Dependencies and state; readiness is in
Readiness.
pactoVersion¶
The contract specification version: "2.0", "2.1", "2.2" or "2.3". 2.1 adds
publish, checks, external dependencies and the mcp interface type.
2.2 adds the interface lifecycle, an interface's
compatibilityEpoch, its guarantees and
dependencies[].requires.
2.3 adds environments.
Every 2.0 contract is also valid under 2.1, every 2.1 contract under 2.2 and
every 2.2 contract under 2.3.
A 2.1 contract needs Pacto core 4.1.0 or later and operator 6.1.0 or later.
A 2.2 contract needs Pacto core 4.3.0 or later and operator 6.3.0 or later.
A 2.3 contract needs Pacto core 4.4.0 or later and operator 6.4.0 or later;
see Versions.
The version picks the JSON Schema the contract is validated against:
pacto-v2.0.schema.json,
pacto-v2.1.schema.json,
pacto-v2.2.schema.json
or
pacto-v2.3.schema.json.
Any other value is a hard error: the contract fails to load before validation
runs and reports PARSE_ERROR (unsupported pactoVersion "3.0"; supported: 2.0,
2.1, 2.2, 2.3). See Validation layers.
service¶
Identifies the service.
| Field | Type | Required | Constraints |
|---|---|---|---|
name |
string | Yes | Pattern: ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$ |
version |
string | Yes | Parses as semver (for example 2.1.0). See the note below |
owner |
OwnerInfo | No | Object only (string form removed) |
service carries identity only. The contract has no image or chart field:
how you build and deploy a service is a delivery concern that lives outside the
contract.
version is parsed leniently, so write it strictly
Pacto accepts any version that
Masterminds/semver can parse. That
parser fills in the parts you leave out and accepts a leading v, so 1, 0.1 and v0.1
all validate and all mean 0.1.0-style coerced versions. 1.2.3.4, abc, a
capital V1.0.0 and anything with surrounding whitespace are rejected with
INVALID_SEMVER.
Later steps keep the coerced string as written, so that string is what gets
published. pacto push tags the artifact with the literal string, and two
contracts written 1 and 1.0.0 become two different tags of the same
version. Write the full MAJOR.MINOR.PATCH and skip the v.
OwnerInfo¶
Structured ownership metadata. All fields are optional but at least one must be present. The field you set decides the canonical owner key that the graph groups by. See Ownership and the canonical owner key.
| Field | Type | Required | Constraints |
|---|---|---|---|
team |
string | No | The team's bare name, such as billing: usually the team label on its workloads. pacto init and pacto_check flag a team/ prefix |
dri |
string | No | Directly Responsible Individual |
contacts |
OwnerContact[] | No | Contact points |
OwnerContact¶
| Field | Type | Required | Constraints |
|---|---|---|---|
type |
string | Yes | One of: email, url, group, oncall, chat, other |
value |
string | Yes | Non-empty |
purpose |
string | No | One of: ownership, support, escalation, oncall, notifications |
Examples:
# Structured form (the owner is always an object)
service:
owner:
team: foundations
dri: eduardo.diaz
contacts:
- type: email
value: foundations@acme.com
purpose: ownership
- type: chat
value: "#foundations"
# Minimal object form (at least one field required)
service:
owner:
team: payments
interfaces¶
Declares the service's communication boundaries. Optional: a service with no network interfaces, such as a batch job or shared library, can omit this section. The ref field points at the spec you already publish: an OpenAPI document, an AsyncAPI document, a gRPC service descriptor or the MCP tools a service exposes. Pacto reads that spec in place.
| Field | Type | Required | Constraints |
|---|---|---|---|
name |
string | Yes | Non-empty. Must be unique across interfaces |
type |
string | Yes | Enum: openapi, asyncapi, grpc, mcp (2.1) |
ref |
string | Yes | Non-empty. Path to the spec file within the bundle |
visibility |
string | No | Enum: public, internal. Default: internal |
lifecycle |
object | No | 2.2. See interface lifecycle |
guarantees |
object | No | 2.2. Behavior consumers may rely on. See Guarantees |
compatibilityEpoch |
integer | No | 2.2. Minimum 0. Default: 0. See Compatibility epoch |
Interface types¶
| Value | Spec kind |
|---|---|
openapi |
OpenAPI document for HTTP/REST traffic |
asyncapi |
AsyncAPI document for event-driven communication |
grpc |
gRPC service descriptor |
mcp |
The tools a service such as an AI agent exposes, as an MCP tools/list result. See an agent is a service |
Note
Interface names must be unique within a contract. Every interface requires a non-empty ref (SCHEMA_VIOLATION otherwise), and the referenced file must exist in the bundle (FILE_NOT_FOUND otherwise). A .json, .yaml or .yml ref must also parse (INVALID_INTERFACE_SPEC otherwise). For any other extension, such as a gRPC .proto, Pacto checks only that the file exists. Ports are a deployment concern, so interfaces have no port field. Declare health and metrics endpoints as capabilities.
Interface lifecycle¶
A provider retires an interface on a schedule its consumers can see. A
lifecycle marks the interface deprecated:
interfaces:
- name: orders-v1
type: openapi
ref: interfaces/orders-v1.yaml
lifecycle:
deprecated: "2026-10-01"
sunset: "2027-04-01"
successor: orders-v2
- name: orders-v2
type: openapi
ref: interfaces/orders-v2.yaml
| Field | Type | Required | Constraints |
|---|---|---|---|
deprecated |
string | Yes | YYYY-MM-DD. The date the interface was deprecated |
sunset |
string | No | YYYY-MM-DD, not before deprecated. The date from which the interface may be removed |
successor |
string | No | Another interface of this service, or an absolute URL such as an oci:// reference to another service |
pacto validate --closure warns a consumer about each deprecated interface of
the services it depends on; see Dependency closure.
A dependency names a service and not an interface, so the consumer is warned about
every deprecated interface of that service. pacto diff reports the schedule;
see change classification.
Guarantees¶
guarantees declares the behavior a consumer may rely on, in a closed
vocabulary: an unknown key fails to load.
| Guarantee | Value | For | Stronger value |
|---|---|---|---|
delivery |
at-most-once or at-least-once |
events | Neither: they trade loss for duplicates |
ordering |
per-key or global |
events | global, which also orders every key |
dedupeWindow |
duration | events | Longer |
retention |
duration | events | Longer |
idempotent |
boolean | calls | true |
timeout |
duration | calls | Shorter |
profile |
"1" |
both | The vocabulary version. Omitted means 1 |
A duration is written like 500ms, 5m or 168h.
interfaces:
- name: order-events
type: asyncapi
ref: interfaces/events.yaml
guarantees:
delivery: at-least-once
ordering: per-key
dedupeWindow: 10m
retention: 168h
A consumer lists what it relies on in
dependencies[].requires.
pacto diff classifies a removed or weakened guarantee as
breaking.
Compatibility epoch¶
Raise compatibilityEpoch when an interface's behavior changes incompatibly
while its spec stays the same, for example when an amount field switches from
cents to euros. pacto diff reports the change as BREAKING with the
interface's name in the reason, so pacto impact lists the consumers to review.
An epoch only goes up. Lowering it is also BREAKING, because consumers that
adapted to the higher epoch break in turn. A "2.0" or "2.1" contract that
sets it reports NEEDS_PACTO_2_2.
Interfaces at runtime¶
The Kubernetes operator checks two fields of a running interface: type and
visibility.
type. The operator sends a probe to the port and looks at the protocol that answers.openapiaccepts HTTP/1 or HTTP/2 andgrpcaccepts HTTP/2. Anything else isINTERFACE_PROTOCOL_MISMATCH. A TLS port, no answer andasyncapistay unverified.visibility(internalwhen omitted). It is contradicted only when the operator sees an address outside private networks: a publicexternalIPsentry, or aLoadBalancerService (without an internal load balancer annotation) or an Ingress whose status carries a public IP or hostname. That isINTERFACE_VISIBILITY_MISMATCH. ANodePortService, an internal or address-less load balancer and an HTTPRoute leave exposure unknown, so the field stays unverified. Apublicinterface seen only in-cluster also stays unverified, because a gateway the operator cannot see may expose it.
A field the operator cannot check leaves the contract status unchanged. See Runtime conformance.
capabilities¶
Optional. Declares standard observability capabilities (health, metrics) or custom extension capabilities. Use this section to tell the platform that a service exposes health and metrics endpoints.
| Field | Type | Required | Constraints |
|---|---|---|---|
type |
string | Yes | Enum: health, metrics, extension |
ref |
string | Conditional | Required for extension only. A namespaced identifier (e.g. example.com/custom). Not allowed for health/metrics |
binding |
Binding | No | Standard types only (health/metrics). Binds the endpoint to a declared interface. Not allowed for extension |
Capability binding¶
Binds a standard capability endpoint (health/metrics) to a declared interface so a collector can probe it. binding.type is a closed set and http is the only supported transport in this release. Any other transport fails structural validation.
A binding resolves as follows:
binding.type: httpmeans the capability is probed over HTTP.binding.interfacenames the declared interface that provides the address and port for the probe. It is the platform-neutral counterpart of the Kubernetes CR'sspec.target.interfaceBindings[].interface, which resolves to a concrete Kubernetes Service port. The capability path is independent of that interface's OpenAPI, AsyncAPI or gRPC specification.binding.pathis relative to the resolved endpoint.
| Field | Type | Required | Constraints |
|---|---|---|---|
type |
string | Yes | Enum: http (only supported transport; any other value fails validation) |
interface |
string | Yes | Must match a declared interfaces[].name: the interface whose platform binding provides the address and port (CAPABILITY_INTERFACE_UNKNOWN otherwise) |
path |
string | No | Application path relative to the resolved endpoint. Must start with a single / and carry no scheme/host/fragment (CAPABILITY_PATH_INVALID otherwise) |
capabilities:
- type: health
binding:
type: http
interface: rest-api
path: /health
- type: metrics
binding:
type: http
interface: rest-api
path: /metrics
- type: extension
ref: example.com/tracing
You can declare a health or metrics capability with no binding. Another collector can verify that declaration. The Kubernetes integration reports an unbound capability as Unsupported/Unknown because it has no endpoint to probe. An extension capability requires a namespaced ref and cannot declare a binding. Pacto has no evaluator for extensions, so it lists each one in coverage.unverified as capabilities[ref=<ref>] and excludes it from required assertions. A contract that declares one can still reach Compliant, and that verdict covers none of its extensions.
metadata¶
Optional. Free-form key-value pairs for organizational use. Pacto validates only the type.
The schema sets additionalProperties: false at every level except inside metadata, so an unknown field fails validation.
Tip
metadata is a deliberate extension point for tooling. Platform teams use it to attach signals that their CI or deployment systems read from a contract. For example, infrastructure contracts can carry metadata.labels like platform/provisioner: crossplane to drive provisioning generically. See Infrastructure contracts.
extensions¶
Optional. A free-form object for forward-compatible, namespaced extension data outside the core contract model. Use metadata for organizational key-value pairs and extensions for structured data that future Pacto features or third-party tools interpret. Namespace each key, for example with a domain, to avoid collisions. In this release Pacto validates only the type.