PACT Specification
PACT Specification
PACT
Protocol for Action-based Coordinated Transport
POST /api/:version/:scheme/:resource
Version + scheme + operation — everything the server needs in one URL.
PACT · 1 / 24
PACT — Protocol for Action-based Coordinated Transport v1.0
C
H
A
P
T
E What is PACT?
R
0
1
1 — The resource name describes the full operation. cancel-order-and-notify tells you everything. DELETE /orders/123 tells
→
you almost nothing.
2 — Version is a path segment your team controls freely. v1, v2, 2024-01, stable — any string. It sits right after the category
→
prefix, before the scheme, so routing resolves in one step.
REST vs PACT
PACT · 2 / 24
PACT — Protocol for Action-based Coordinated Transport v1.0
C
H
A
P
T
E The URL — Seven Prefixes, One Pattern
R
0
2
Version is any string your team agrees on: v1, v2, 2024-01, stable, beta. PACT does not enforce a format — it enforces the
→
position. Version always comes immediately after the category prefix.
/api examples
PACT · 3 / 24
PACT — Protocol for Action-based Coordinated Transport v1.0
POST /api/v1/public/fetch-product-catalogue
POST /api/v1/private/place-order-and-reserve-stock
POST /api/v2/private/place-order-and-reserve-stock ← new contract, runs in parallel
POST /api/v1/private/cancel-order-and-refund-and-notify
POST /api/stable/internal/reindex-search-and-report
Webhooks never use the PACT request envelope — the body is whatever the provider sends. Your runner normalises it
→
internally. HMAC signature verification happens in [Link] before your runner sees the payload.
/ws examples
WS /ws/v1/order-tracking-updates
WS /ws/v1/live-chat-session
WS /ws/v2/order-tracking-updates ← new message schema, runs in parallel
SSE uses HTTP GET and responds with Content-Type: text/event-stream. This is the only transport in PACT where the
→
response is not a JSON envelope — the stream is a sequence of named events, not a single response.
PACT · 4 / 24
PACT — Protocol for Action-based Coordinated Transport v1.0
# Stream complete
event: done
data: {}
/stream examples
GET /stream/v1/user-activity-feed
GET /stream/v1/order-export-progress
GET /stream/v2/user-activity-feed ← new event schema
PACT · 5 / 24
PACT — Protocol for Action-based Coordinated Transport v1.0
/events examples
POST /events/v1/order-placed
POST /events/v1/user-verified-email
POST /events/v2/order-placed ← new schema, v1 still running
POST /events/v1/payment-failed-needs-retry
/rpc examples
POST /rpc/v1/check-user-permissions
POST /rpc/v1/calculate-shipping-cost
POST /rpc/v2/calculate-shipping-cost ← caller and callee migrate together
PACT · 6 / 24
PACT — Protocol for Action-based Coordinated Transport v1.0
C
H
A
P
T
E Versioning — Rules & Migration
R
0
3
Whatever format you pick — use it everywhere. Mixing v1 on /api and 2024-01 on /events in the same system is worse
→
than picking either format consistently.
POST /api/v2/private/place-order-and-confirm
→ /routes/v2/place-order-and-confirm/[Link]
→ /routes/v2/place-order-and-confirm/[Link]
PACT · 7 / 24
PACT — Protocol for Action-based Coordinated Transport v1.0
Deprecation in practice
// [Link] — mark a version as deprecated
deprecated: '2026-06-01', // ISO date — triggers both meta field and Sunset header
After the sunset date, the route returns UNAVAILABLE with code VERSION_RETIRED. It does not silently vanish. Clients get a
→
clear, machine-readable signal.
PACT · 8 / 24
PACT — Protocol for Action-based Coordinated Transport v1.0
C
H
A
P
T
E Naming Operations
R
0
4
Simple operations
Simple
POST /api/v1/public/fetch-product-catalogue
POST /api/v1/public/search-articles-by-tag
POST /api/v1/private/update-billing-address
POST /api/v1/private/delete-draft-post
Compound operations
This is where PACT's naming pays off most. Operations that touch multiple systems get a name that is honest about all of it.
Compound
POST /api/v1/private/register-user-and-send-welcome
POST /api/v1/private/cancel-order-and-refund-and-notify
POST /api/v1/private/downgrade-plan-and-prorate-and-email
POST /api/v1/private/close-account-and-export-data
POST /api/v1/internal/expire-stale-sessions-and-audit-log
When you version a compound operation — POST /api/v2/private/cancel-order-and-refund-and-notify — the name stays
→
the same. The version says the contract changed, not the operation.
PACT · 9 / 24
PACT — Protocol for Action-based Coordinated Transport v1.0
C
H
A
P
T
E Tenants
R
0
5
Scenario Result
JWT token has tenant claim Token value used — always wins
No tenant in token, header present Header value used as fallback
Both token and header carry tenant Token wins — header ignored
Neither token nor header Single-tenant mode — no isolation applied
Header value malformed VALIDATION error returned
PACT · 10 / 24
PACT — Protocol for Action-based Coordinated Transport v1.0
C
H
A
P
T
E Requests & Responses
R
0
6
Request headers
Request body
One field: payload. Your data goes inside. Nothing else — no version, no action, no metadata. The URL already carries all of that.
Body
{
"payload": {
"orderId": "ord_789"
}
}
Unknown top-level body fields are rejected. Body size limit is 1MB by default. payload must always be present and must be
→
an object — never null.
Success Failure
HTTP 200 HTTP 200
{ {
"success": true, "success": false,
"requestId": "req_a3f9b2", "requestId": "req_a3f9b2",
"data": { ... }, "error": {
"meta": { "category": "NOT_FOUND",
"duration": 24, "code": "ORDER_NOT_FOUND",
"cached": false "retryable": false,
PACT · 11 / 24
PACT — Protocol for Action-based Coordinated Transport v1.0
} "source": "runner"
} },
"meta": { "duration": 8 }
}
Meta fields
PACT · 12 / 24
PACT — Protocol for Action-based Coordinated Transport v1.0
C
H
A
P
T
E Schemes — Who Can Call What
R
0
7
Internal operations require both a service token AND the caller IP to be on the allowlist. A valid token from an unknown IP
→
is rejected. This prevents internal routes from being reached from the internet even if a token leaks.
PACT · 13 / 24
PACT — Protocol for Action-based Coordinated Transport v1.0
C
H
A
P
T
E Errors
R
0
8
Error envelope
{
"success": false,
"requestId": "req_a3f9b2",
"error": {
"category": "VALIDATION",
"code": "MISSING_FIELD",
"message": "orderId is required",
"fields": { "orderId": "required" }, // VALIDATION only
"retryable": false,
"source": "pact"
},
"meta": { "duration": 3 }
}
VERSION_RETIRED is a special code under UNAVAILABLE. It fires after the sunset date when a client calls a retired version.
→
retryable: false — client must upgrade.
PACT · 14 / 24
PACT — Protocol for Action-based Coordinated Transport v1.0
C
H
A
P
T
E [Link] & [Link]
R
0
9
[Link]
export const contract = {
scheme: 'private',
schema: [Link]({
items: [Link]([Link]({ productId: [Link](), quantity:
[Link]().min(1) })).min(1),
addressId: [Link](),
}),
idempotency: { required: true },
cache: { enabled: false },
rateLimit: { rpm: 30 },
deprecated: null, // set to ISO date string to trigger deprecation
}
The runner is completely blind to the protocol. No HTTP, no tokens, no rate limits, no version checks. By the time your code
→
runs, all of that is handled. You receive clean, validated data and return plain data.
PACT · 15 / 24
PACT — Protocol for Action-based Coordinated Transport v1.0
[Link]
export default async function run(ctx: PactContext) {
// [Link] — validated, schema-matched
// [Link] — decoded JWT (null if public)
// [Link] — resolved tenant (null if single-tenant)
// [Link] — 'v1', 'v2', etc — read-only, for logging only
// [Link] — trace ID
PACT · 16 / 24
PACT — Protocol for Action-based Coordinated Transport v1.0
C
H
A
P
T
E The 20-Step Middleware Chain
R
1
0
PACT · 17 / 24
PACT — Protocol for Action-based Coordinated Transport v1.0
Step 5 (Version Check) is where retired versions return VERSION_RETIRED. Step 19 (Response Builder) is where
→
[Link] and the Sunset header are automatically added when [Link] is set.
PACT · 18 / 24
PACT — Protocol for Action-based Coordinated Transport v1.0
C
H
A
P
T
E Folder Structure
R
1
1
PACT · 19 / 24
PACT — Protocol for Action-based Coordinated Transport v1.0
/v1
/activity-feed
[Link] [Link] [Link]
PACT · 20 / 24
PACT — Protocol for Action-based Coordinated Transport v1.0
[Link] [Link] [Link] [Link]
/types ← shared TypeScript types
[Link] [Link] [Link]
/constants ← app-wide constants
[Link] [Link] [Link]
/validators ← shared Zod schemas used across operations
[Link] [Link] [Link] [Link]
/errors ← custom error classes
NotFoundError ConflictError ValidationError ...
/helpers ← domain-specific helpers (not pure — can use libs)
[Link] [Link] [Link] [Link]
/middleware ← reusable middleware pieces
[Link] [Link] [Link] [Link]
AI lives in two places: /adapters/openai and /adapters/anthropic wrap the outbound API calls. The /transports/api route
→
that exposes AI to your clients calls the adapter — same pattern as Stripe.
PACT · 21 / 24
PACT — Protocol for Action-based Coordinated Transport v1.0
R
E
F
E
R Quick Reference & Compliance Checklist
E
N
C
E
Deprecation contract
Requirement Detail
[Link] in response ISO date string — present on every response from deprecated
route
Sunset HTTP header Standard header — Sunset: <HTTP-date>
Minimum window 90 days between setting deprecated date and that date arriving
After sunset date Route returns UNAVAILABLE / VERSION_RETIRED — not silent
Compliance checklist
Versioning
PACT · 22 / 24
PACT — Protocol for Action-based Coordinated Transport v1.0
Tenant
☐ JWT token claim wins over X-Tenant-ID header
☐ Neither present = single-tenant mode — not an error
☐ Cache keys include tenant and version
SSE (/stream)
☐ Response is text/event-stream — not JSON envelope
☐ Errors sent as: event: error\ndata: {category, code, retryable}
☐ Heartbeat sent every 30s: event: ping\ndata: {}
☐ Stream end: event: done\ndata: {}
☐ Last-Event-ID honoured for client reconnection
Response envelope
☐ Always HTTP 200 — no exceptions in PACT routes
☐ success true/false always present
☐ requestId always echoed
☐ meta: duration + cached only — never echoes request fields
Errors
☐ All 8 categories implemented
☐ VERSION_RETIRED under UNAVAILABLE after sunset date
☐ INTERNAL never exposes stack trace in production
☐ retryable and source always present
[Link]
☐ Zero HTTP or protocol logic
☐ [Link] available read-only — for logging only, not routing logic
☐ Returns plain data — framework wraps it
PACT · 23 / 24
PACT — Protocol for Action-based Coordinated Transport v1.0
Events migration
☐ All consumers documented per version before retiring
☐ 90-day minimum migration window applied to event versions
☐ v1 and v2 run in parallel until all consumers migrated
PACT · 24 / 24