Troubleshooting

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.

This manual is generated from the repository. Every command and flag in it is checked against the binary; the marked transcripts are executed.