Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.markapidown.net/llms.txt

Use this file to discover all available pages before exploring further.

MarkApiDown is a single static binary with no runtime dependencies, which makes it straightforward to drop into any CI pipeline. You get spec validation, contract test execution, and structured output in one tool — gated by stable exit codes that never change between releases.
Run mad doctor as a pre-flight check before any CI step. It verifies your collection exists, all specs are valid, and common misconfigurations (such as a missing .env.local entry in .gitignore) are caught before they cause a cryptic failure downstream.

Validate specs on every push

Use mad validate to check that every spec file in api-docs/ is structurally correct. It exits with code 2 if any spec has a frontmatter error, a missing required section, or an unresolved variable. It exits with code 5 if a secret pattern is detected in a versioned file.
mad validate api-docs/
Run this before any test step so that malformed specs are caught early, independently of whether the API is reachable.

Run contract tests in CI

Use mad exec to run a single endpoint against a live environment and compare the response to the ## Expected response block in the spec. Use mad flow for multi-step pipelines that chain captures across endpoints.
# Single endpoint
mad exec api-docs/apis/users/get-user-by-id.md --env=staging --var userId=usr_123

# Multi-step pipeline
mad flow api-docs/flows/user-onboarding.md --env=staging
Both commands accept --output=junit to produce JUnit XML and --output=json to produce machine-readable JSON.
mad exec api-docs/apis/users/get-user-by-id.md \
  --env=staging \
  --output=junit > results.xml

mad flow api-docs/flows/user-onboarding.md \
  --env=staging \
  --output=json > flow-results.json

Exit codes

MarkApiDown returns one of six stable exit codes. Use them in CI to distinguish between different failure modes without parsing text output.
CodeNameWhen it occurs
0PassedAll checks passed; response matched the expected response.
1Test failedResponse received but did not match the ## Expected response block.
2Invalid specFrontmatter error, missing required section, or unresolved variable.
3Engine errorRequest build failed or unsupported protocol in frontmatter.
4Network errorDNS failure, connection refused, or timeout exhausted after all retries.
5Secret detectedA secret pattern was found in a versioned spec file — exits immediately, no request sent.
Handle codes individually in shell scripts to give different feedback for different failure types:
mad exec api-docs/apis/users/get-user.md --env=staging
CODE=$?
case $CODE in
  0) echo "Passed" ;;
  1) echo "Response mismatch — check the spec or API behavior" ; exit 1 ;;
  2) echo "Invalid spec — run mad validate to see errors" ; exit 1 ;;
  3) echo "Engine error — check frontmatter protocol and request syntax" ; exit 1 ;;
  4) echo "Network error — check VPN, service health, or MAD_BASE_URL" ; exit 1 ;;
  5) echo "Secret committed to spec file — fix before merging" ; exit 1 ;;
  *) echo "Unexpected error (code $CODE)" ; exit 1 ;;
esac

GitHub Actions

1

Install MarkApiDown

Add a step that downloads the binary using the shell installer. The installer detects the platform automatically.
- name: Install MarkApiDown
  run: curl -fsSL https://markapidown.net/install.sh | sh
2

Validate specs

Run mad validate against the entire api-docs/ directory. Any structural error or committed secret exits non-zero and fails the job.
- name: Validate specs
  run: mad validate api-docs/
3

Run contract tests

Pass secrets via MAD_* environment variables. The MAD_ prefix is stripped and the remainder is converted to lower camelCase before variable resolution — MAD_AUTH_TOKEN becomes {{authToken}} in your specs.
- name: Run endpoint tests
  env:
    MAD_AUTH_TOKEN: ${{ secrets.STAGING_AUTH_TOKEN }}
    MAD_BASE_URL: https://staging.example.com
  run: |
    mad exec api-docs/apis/health/get-health.md --env=staging
    mad exec api-docs/apis/users/get-users.md --env=staging
4

Run pipelines

Use mad flow for multi-step contract scenarios that chain captures between endpoints.
- name: Run onboarding pipeline
  env:
    MAD_AUTH_TOKEN: ${{ secrets.STAGING_AUTH_TOKEN }}
  run: mad flow api-docs/flows/user-onboarding.md --env=staging

Full workflow example

name: API spec tests

on:
  push:
    branches: [main]
  pull_request:
    paths:
      - 'api-docs/**'

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install MarkApiDown
        run: curl -fsSL https://markapidown.net/install.sh | sh

      - name: Pre-flight check
        run: mad doctor

      - name: Validate specs
        run: mad validate api-docs/

      - name: Run endpoint tests
        env:
          MAD_AUTH_TOKEN: ${{ secrets.STAGING_AUTH_TOKEN }}
          MAD_BASE_URL: https://staging.example.com
        run: |
          mad exec api-docs/apis/health/get-health.md --env=staging
          mad exec api-docs/apis/users/get-users.md --env=staging

      - name: Run onboarding pipeline
        env:
          MAD_AUTH_TOKEN: ${{ secrets.STAGING_AUTH_TOKEN }}
        run: mad flow api-docs/flows/user-onboarding.md --env=staging

      - name: Run tests and capture JUnit output
        env:
          MAD_AUTH_TOKEN: ${{ secrets.STAGING_AUTH_TOKEN }}
        run: |
          mad exec api-docs/apis/users/get-user-by-id.md \
            --env=staging --output=junit > results.xml

      - name: Upload test results
        uses: actions/upload-artifact@v4
        with:
          name: mad-results
          path: results.xml

      - name: Publish test report
        uses: dorny/test-reporter@v1
        with:
          name: MarkApiDown API tests
          path: results.xml
          reporter: java-junit

GitLab CI

stages:
  - validate
  - test

validate-specs:
  stage: validate
  script:
    - curl -fsSL https://markapidown.net/install.sh | sh
    - mad doctor
    - mad validate api-docs/

api-tests:
  stage: test
  variables:
    MAD_AUTH_TOKEN: $STAGING_AUTH_TOKEN
    MAD_BASE_URL: https://staging.example.com
  script:
    - mad exec api-docs/apis/health/get-health.md --env=staging
    - mad exec api-docs/apis/users/get-users.md --env=staging
    - mad flow api-docs/flows/user-onboarding.md --env=staging
  only:
    - main
    - merge_requests

npm-based CI (Node.js projects)

For Node.js projects, use npx to run MarkApiDown without a global install step. The npm wrapper downloads the platform-appropriate binary on first use.
npx mark-api-down validate api-docs/
npx mark-api-down exec api-docs/apis/health/get-health.md --env=staging
Add it to package.json as a script so it runs in the same way locally and in CI:
{
  "scripts": {
    "api:validate": "mad validate api-docs/",
    "api:test": "mad exec api-docs/apis/health/get-health.md --env=staging"
  }
}
Then call it in CI the same way you run any other npm script:
- name: Validate API specs
  run: npm run api:validate

Parse results programmatically

Use --output=json to get machine-readable output from any mad exec or mad flow command. The JSON includes the pass/fail status, HTTP status code, duration, diff details, and any assertion results.
mad exec api-docs/apis/users/get-user-by-id.md \
  --env=staging \
  --output=json | jq '.diff.passed'
This is useful for custom dashboards, Slack notifications, or any downstream step that needs to act on structured results rather than parsing terminal output.

Passing secrets via MAD_* environment variables

Set any secret as a MAD_-prefixed environment variable. MarkApiDown strips the prefix and converts the remainder to lower camelCase before resolving it as a spec variable.
Environment variableResolved as
MAD_AUTH_TOKEN{{authToken}}
MAD_STRIPE_KEY{{stripeKey}}
MAD_BASE_URL{{baseUrl}}
MAD_API_SECRET_KEY{{apiSecretKey}}
Never put secrets directly in spec files or pass them with --var authToken=$SECRET on the command line. Shell argument lists can appear in CI logs and expose the secret value. Always use MAD_* environment variables — MarkApiDown reads them at runtime and masks them in all output.