Troubleshooting
The errors you are most likely to hit, what they mean, and the fix.
Most of these are caught by validate before you ever
serve a request, which is why it is worth running before you commit and in CI.
uuid is a function — write {{uuid()}} to call it
- path: /a
method: GET
responses:
- status: 200
body:
id: "{{uuid}}"
$ apifae validate
…/mocks/a.yaml:1 - GET /a 200: body does not render: Mock error: Template render error: invalid operation: uuid is a function — write {{uuid()}} to call it (in <string>:1)
1 mocks checked, 0 valid, 1 errors, 1 warnings
Error: Validation failed: 1 error(s)
EXIT: 1
You wrote {{uuid}} where you meant {{uuid()}}. The helpers are functions and
have to be called; naming one without parentheses is not a value.
Applies to uuid(), now(), fake_name(), fake_email() and fake_url().
NONCE is not defined
- path: /a
method: GET
responses:
- status: 200
body:
id: "{{NONCE}}"
$ apifae validate
…/mocks/a.yaml:1 - GET /a body.id: NONCE is not defined. Templates can use path, query, header, body, the helpers (uuid(), now(), fake_name(), fake_email(), fake_url()), or {% raw %}…{% endraw %} to keep braces literal
EXIT: 1
You used a name the template has no way to resolve. A template may reach for:
| Root | What it holds |
|---|---|
path |
path parameters, e.g. {{path.id}} |
query |
query-string parameters |
header |
request headers |
body |
the request body |
| the helpers | uuid(), now(), fake_name(), fake_email(), fake_url() |
| minijinja's own | range, dict, debug, namespace |
The last row is the template engine's builtins, which is why
{% for i in range(3) %} in Mock an API is not an
error.
Any other root name is an error — deliberately, because a typo that renders blank is a bug you find much later, in a client, with no clue where it came from.
A missing key inside a known root is fine. {{query.limit}} on a request with
no limit renders blank rather than failing, because whether a query parameter is
present is a runtime fact, not a mistake in your mock:
- path: /a
method: GET
responses:
- status: 200
body:
limit: "{{query.limit}}"
$ apifae validate
1 mocks checked, 1 valid, 0 errors, 1 warnings
✓ All mocks valid
EXIT: 0
The rule is about roots, not keys: an unknown root is a typo, a missing key is data.
I want literal braces in a response
Recorded content, a template your own client is meant to render, a code sample —
anything that legitimately contains {{:
- path: /a
method: GET
responses:
- status: 200
body:
tpl: "{% raw %}{{ not_a_template }}{% endraw %}"
$ apifae validate
1 mocks checked, 1 valid, 0 errors, 1 warnings
✓ All mocks valid
EXIT: 0
record does this for you: captured values containing {{ or {% are wrapped
automatically, so a capture replays as itself.
My array comes back as a string
You wrote a YAML block scalar that emits YAML source. The rule and both forms are in Mock an API before it exists.
Short version: a template renders to text, and that text is the value unless it parses as JSON. Use the single-quoted JSON form.
The server returns 500 with an error in the body
A mock whose template fails to render is served as a 500. That is deliberate — a
test asserting status === 200 should fail on a broken mock rather than pass on
an error envelope.
Run apifae validate to see which mock, then fix the template. In CI, start the
server with --strict so a broken workspace refuses to serve at all instead of
answering 500 to a suite that then reports thirty confusing failures.
No apifae.yaml found
You are not in a workspace. record, up, status and diff all need one.
Run apifae init here, or cd to the workspace.
validate and patch do not — so if one of them is what surprised you, this
is not your error. validate falls back to the directory you are standing in
(mocks/ beside a spec is enough), and patch never looks for a workspace root
at all.
The error lists every directory it searched, so if it looked in the wrong place the path will tell you why.
No OpenAPI spec found (openapi.yaml/yml/json) — skipping spec validation
A warning, not an error. validate checks your mocks on their own terms and says
plainly that it could not compare them against a contract.
If you expected a spec to be found, check that it is at the workspace root as
openapi.yaml, openapi.yml or openapi.json, or is matched by a schemas:
glob in apifae.yaml. A file only counts as a spec if it has a
top-level openapi: or swagger: key — a fragment holding only components:
is passed over.
Under --strict the warning becomes a failure, which is what you want in CI.
apifae push / login exits 1
Working as intended. APIFae cloud does not exist yet, and these commands refuse rather than printing a success they cannot deliver. Nothing in the local workflow needs an account.
See Commands that refuse.
diff exits 2
The run could not complete — connection refused, a timeout, a 401 or a 403. This is deliberately not exit 1: a network problem is not a contract failure, and a CI gate that conflated them would go red for the wrong reason and teach you to ignore it.
Check the URL, and whether the endpoint needs auth. config set auth.headers.…
stores a header for diff and patch to send.
Something else
If a command's behaviour disagrees with this manual, the manual is wrong — every example here is executed against the binary in CI, so a mismatch is a defect worth reporting rather than something to work around.