APIZ guide
Writing Policy Fixtures
A Policy Fixture describes one synthetic request and the Policy result you expect. It is credential-free, does not contact an upstream service, and is published atomically with the Policy definition.
Every normal Fixture has three primary sections:
metadata which Fixture this is and which Adapter normalizes it
request the synthetic request presented to that Adapter
expectation the Policy result that must be observed
You do not write a normalized action or normalized resource. APIZ derives them through the selected Adapter before running the Policy.
Discover complete credential-free starters and the selected Adapter vocabulary from the installed CLI:
apiz policy example list
apiz policy example show <example-id> --output json
apiz adapter explain <adapter-id>
apiz policy draft save --help
To test a saved server candidate rather than local source or the current Published Revision, compile it and name its exact Draft revision:
apiz policy draft compile <policy-id> --expected-draft-revision <draft-revision>
apiz policy draft test <policy-id> \
--expected-draft-revision <draft-revision> \
--fixture fixtures/deny-delete.yaml --explain
The result identifies target=working_draft, the Draft revision, content hash,
compiled artifact, and test run. It does not contact upstream services.
Minimal YAML Fixture
metadata:
key: deny-delete
name: Deny delete requests
enabled: true
adapter_id: general
request:
method: DELETE
path: /items/1
expectation:
decision: deny
reason: delete_not_allowed
Store it under the project, for example
fixtures/deny-delete.yaml, and reference it from apiz-policy.json:
{
"fixtures": [
{"file": "fixtures/deny-delete.yaml"}
]
}
The project manifest still contains the other required Policy Project fields; the abbreviated example above shows only the Fixture reference.
Equivalent JSON Fixture
{
"metadata": {
"key": "allow-read",
"name": "Allow a read request",
"enabled": true,
"adapter_id": "general"
},
"request": {
"method": "GET",
"path": "/items/1",
"query": {"include": ["summary"]},
"headers": {"accept": ["application/json"]}
},
"expectation": {
"decision": "allow",
"reason": "read_allowed"
}
}
JSON, YAML, and Studio Form mode are representations of the same Fixture contract. Studio may normalize formatting when a source Fixture is edited in Form mode. Resolve syntax errors before switching from source to Form.
Metadata
| Field | Required | Meaning |
|---|---|---|
key | yes | Stable lowercase id used by CLI, Studio, results, and persisted readiness evidence. |
name | yes | Human-readable case name. |
enabled | yes | Whether Run all includes this Fixture. A disabled Fixture can still be opened and run explicitly. |
adapter_id | conditional | Adapter that normalizes the request. It may be omitted when inherited from a non-empty Policy adapter_scope. |
If both Fixture adapter_id and Policy adapter_scope are present, they must
match. An adapter-neutral Policy must select an Adapter per Fixture so APIZ
knows how to normalize the request.
Request
request.method and request.path are required. The bounded request may also
contain:
query: a key to string-list map;headers: a sanitized key to string-list map;content_type;body.encoding:json,text,raw,base64,file,form_urlencoded, ormultipart; andbody.value: the bounded synthetic value.
Policy Studio exposes these fields as a complete credential-free HTTP request builder. Headers and query parameters accept arbitrary keys and repeated values. Body modes cover JSON, text, raw bytes, one embedded file, URL-encoded form fields, and multipart fields/files. Selecting a local file embeds its bounded base64 content in the Fixture; running a Fixture never reads from the original workstation path.
Example JSON-RPC request:
metadata:
key: deny-unsupported-rpc
name: Deny an unsupported JSON-RPC method
enabled: true
adapter_id: ethereum
request:
method: POST
path: /
content_type: application/json
body:
encoding: json
value:
jsonrpc: "2.0"
id: 1
method: eth_chainId
params: []
expectation:
decision: deny
reason: unsupported_ethereum_rpc_method
Never put Authorization headers, cookies, Client Access Credentials, upstream API keys, ciphertext, or copied production requests in a Fixture. Use synthetic identifiers and payloads.
URL-encoded form example:
request:
method: POST
path: /search
content_type: application/x-www-form-urlencoded
body:
encoding: form_urlencoded
value:
tag: [one, two]
name: [demo]
Multipart form example:
request:
method: POST
path: /documents
body:
encoding: multipart
value:
fields:
purpose: [policy-test]
files:
- field: document
name: example.txt
content_type: text/plain
data_base64: aGVsbG8=
Adapter Normalization
On live traffic, an Adapter converts protocol details into stable Policy context. Fixture execution uses the same boundary:
DELETE /items/1
-> General Adapter
-> action: http:delete
-> resource: {type: general_path, path: /items/1}
eth_sendRawTransaction request body
-> Ethereum Adapter
-> action: ethereum:rpc:call
-> resource: {type: ethereum_rpc}
The derived action is available to Scripted Policies as ctx.action and to
Structured Policies through descriptor-driven action matching. It is shown in
sanitized test trace output for explanation, but it is not an author input.
This prevents contradictory Fixtures such as method: GET combined with a
manually entered action: http:delete.
Expectations
expectation.decision is required. Supported decisions are allow, deny,
reply, patch, and rate_limit according to the Policy contract. Add only
the assertions relevant to the behavior being tested.
Common assertions include:
expectation:
decision: deny
reason: delete_not_allowed
error_category: policy_denied
Patch behavior can assert sanitized request changes:
expectation:
decision: patch
reason: route_rewritten
request:
path: /v2/items/1
headers:
x-policy-mode: [rewritten]
body:
encoding: json
value:
accepted: true
Reply behavior can assert the concrete APIZ-owned response:
expectation:
decision: reply
reason: synthetic_response
reply:
status: 202
headers:
content-type: [application/json]
body:
encoding: json
value:
accepted: true
For expected patched requests and replies, only configured fields are checked. Header names are case-insensitive; each configured header must have exactly the listed values. JSON bodies are compared structurally, while text and base64 bodies are compared byte-for-byte.
Studio preserves the last request and reply expectation objects when you
switch the Expected Decision so comparison work is not discarded. APIZ only
evaluates expectation.request when the selected decision is patch, and only
evaluates expectation.reply when it is reply; the inactive object is an
editable draft, not an additional assertion.
Runtime-failure tests can assert stable errors without depending on raw stack text:
expectation:
decision: deny
reason: policy_definition_error
error_category: script_runtime_error
error_reason: policy_script_exception
Test results show expected versus actual values, trace, request patch, source location when available, exact content/build identity, and a redaction summary.
Advanced Context Overrides
Most Fixtures do not need context overrides. Use them only when a Policy explicitly reads allowlisted context that cannot be derived from the request:
context_overrides:
client_instance:
tags:
environment: production
api_instance:
tags:
owner: payments
Context overrides cannot set Adapter identity, normalized action, normalized resource, credentials, authenticated identity, persisted Policy evidence, or other server-owned security fields. Tests that need malformed internal post-normalization context belong in adapter/runtime conformance tests rather than a user Policy Project.
Running Fixtures
CLI
For a Scripted project, when the separate apiz-policy TypeScript builder is
installed and on PATH:
apiz policy test --project .
apiz policy test --project . --case deny-delete --explain
The current CLI does not implement policy validate and does not run a
Structured project locally with policy test --project. For either Policy
type, publish through the authoritative server and run the saved published
suite:
apiz -o json policy publish --project . > policy-review.json
POLICY_ID=$(jq -r '.candidate.policy_id' policy-review.json)
DRAFT_REVISION=$(jq -r '.candidate.draft_revision' policy-review.json)
POLICY_REVISION=$(jq -r '.candidate.current_published_revision' policy-review.json)
apiz policy publish "$POLICY_ID" --project . \
--expected-draft-revision "$DRAFT_REVISION" \
--expected-policy-revision "$POLICY_REVISION" --confirm
apiz policy test-suite "$POLICY_ID"
Web Console
In Policy Studio:
- select a Fixture in the project Explorer and choose
Run Fixture; or - select
policy.ts, openRun, and choose one Fixture orRun all.
APIZ saves and compiles the exact Draft when needed, runs without Secret Broker or upstream access, and opens the sanitized result in the docked test panel.
Recommended Suite
For a Policy that affects authorization or request mutation, include applicable cases for:
- expected allow behavior;
- expected deny behavior;
- malformed request input;
- resource or action outside the intended boundary;
- runtime error or timeout behavior for Scripted Policies;
- request patch or APIZ-owned reply behavior; and
- any advanced Client/API tag branch used by the Policy.
Use small Fixtures with one clear purpose. A Fixture should make it obvious which request is being exercised and why the expectation should pass.