Guide

Testing docs examples that need API keys

Split each credential-gated example into layers: syntax, an offline run against a stub or fixture, and a live call against an authorized sandbox in your own CI.

Published
Updated
November 10, 2026
Reading time
13 min
Facts checked against
GitHub Actions secrets documentation, the Twelve-Factor App config chapter, Python os and pytest skipping documentation (November 10, 2026)
Outlined documentation pages with a lime pulse line running out of the stack
On this pageThe short answerWhy credentials complicate documentation testsMake placeholders honest and recognizableSeparate the parts that need a keyRun an offline Python exampleSkip a live test cleanly when the key is absentConfigure CI around the trust boundaryTriage credential-related findingsWhere DocsAlive fitsChecklistThe sources we checkedFAQ

The short answer

Split credential-dependent examples into testable layers: check syntax, run request construction and response parsing offline, and run the live call separately against an authorized test-mode or sandbox account. Use an environment variable with a clear name, document how readers obtain and provide their own value, and report a skipped live call as a coverage gap.

Credential-gated examples are among the hardest documentation blocks to test because a valid run depends on access that a clean scanner should not assume. They are also a common reason a scan cannot run a block. A missing key does not by itself mean the documentation is broken; a reader who cannot work out where to put their key has a documentation problem.

DocsAlive detects certain secret reads and placeholders, classifies credential-dependent blocks, and does not run them with customer-provided test credentials. Python and JavaScript blocks in that category still receive syntax checks; other affected blocks are skipped. Run the live leg in your own authorized CI. See documentation code testing and SDK documentation testing for the broader workflow.

Why credentials complicate documentation tests

An API example often combines several promises in a few lines: the package can be installed, the client can be configured, the request has the right shape, the key is accepted, the account has access, and the response can be parsed. A failed live call does not immediately identify which promise failed.

A clean documentation check may intentionally have no access to a key. A forked pull request may also run without repository secrets. A sandbox account can be unavailable or lack a permission needed by the example. Those conditions should be visible in the result, but they do not automatically establish that the code shown to readers is wrong.

At the same time, treating every authentication failure as harmless can hide a real documentation defect. A page may use a placeholder without explaining which environment variable receives the real value. It may point readers to the wrong kind of credential, use an obsolete header, or omit a required configuration step. Triage must distinguish an unavailable test credential from instructions that a reader cannot follow.

The best coverage comes from separating the promises. Syntax and request construction can often be checked without a live service. A recorded response can exercise parsing. A live test can then focus on authentication and current service behavior under a controlled account. Each result says what was tested and what remains unknown.

Make placeholders honest and recognizable

Use a named environment variable for a value that varies between environments. The following complete Python program reads a fictional Acme SDK credential without embedding a key:

import os

api_key = os.environ.get("ACME_API_KEY")
if api_key:
    print("A key is configured")
else:
    print("Set ACME_API_KEY to run the live example")

ACME_API_KEY and the Acme SDK are fictional names in this guide. The program reports only whether a value is present; it never prints the value. Python's environment documentation describes os.environ and os.getenv. The Twelve-Factor configuration guidance explains the broader practice of keeping deployment-specific configuration in the environment.

If a page must show a placeholder, use an unmistakable marker such as YOUR_API_KEY or <API_KEY>. Explain exactly which value replaces it and where the reader supplies that value. Do not paste a fake key designed to look real: it can be mistaken for an actual credential and is harder for a reader or scanner to distinguish from one.

Presentation Reader can identify the action? Testing implication
Code reads ACME_API_KEY and the page explains how to set it Yes The secret-dependent part can be classified and tested separately
YOUR_API_KEY with a clear replacement instruction Yes A scanner can recognize a placeholder; a live test needs a real authorized value
<API_KEY> without any explanation Uncertain Review whether the reader can map it to the correct credential and location
A realistic-looking pasted fake key Misleading Replace it with an honest placeholder or environment read
A real credential in source No safe public example Remove it and follow the credential owner's rotation process

A placeholder is not a substitute for a configuration guide. State whether the value is a test-mode key, which environment variable the example reads, and which account or permission the live call requires. Avoid promising that a particular credential will work in every environment.

Separate the parts that need a key

A useful example has a clear boundary between local work and the external call. Depending on the SDK, client construction can be a local operation even when the client eventually needs credentials. Request building and parsing a recorded response can usually be tested without contacting the service. If a particular SDK validates or contacts the service during construction, treat that operation as part of the live boundary instead.

Layer What it establishes What it does not establish
Syntax only The selected source parses Imports, behavior, authentication, or service access work
Offline stub or recorded fixture Local request and response handling work for the tested case The real service accepts the key or currently returns that response
Live sandbox call The tested account and service path work at that time Every account, permission, or production condition works

Design the example so the offline layer is meaningful. Build the same request shape used by the live client, return a representative response, and assert the fields the documentation promises. Keep recorded fixtures free of real credentials and private customer data. When a service response changes, review whether the fixture and documentation should change together.

A live test remains valuable for behavior a stub cannot establish: authentication, permissions, current endpoint behavior, and the interaction with the actual SDK or service. Use a test-mode or sandbox account that the owner has authorized for CI. Give it only the access the test needs and make any cleanup or service-side effects part of the test design.

Run an offline Python example

The following single file uses only the Python 3.12 standard library. It starts a local HTTP stub in a background thread, sends a request, parses a canned JSON response, checks the result, and shuts the server down. The host is local; no external API is contacted.

Save it as offline_example.py:

import json
import os
import threading
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.request import Request, urlopen


class StubHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path != "/v1/greeting":
            self.send_error(404)
            return

        body = json.dumps({"message": "Hello from the stub"}).encode("utf-8")
        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def log_message(self, format, *args):
        pass


def fetch_greeting(base_url):
    api_key = os.environ.get("ACME_API_KEY", "offline-placeholder")
    request = Request(
        base_url + "/v1/greeting",
        headers={"Authorization": "Bearer " + api_key},
    )
    with urlopen(request, timeout=5) as response:
        return json.load(response)


def main():
    server = HTTPServer(("127.0.0.1", 0), StubHandler)
    thread = threading.Thread(target=server.serve_forever, daemon=True)
    thread.start()
    try:
        host, port = server.server_address
        result = fetch_greeting(f"http://{host}:{port}")
        assert result["message"] == "Hello from the stub"
        print(result["message"])
    finally:
        server.shutdown()
        thread.join()
        server.server_close()


if __name__ == "__main__":
    main()

Run it with:

python3 offline_example.py

It prints:

Hello from the stub

The default value is explicitly an offline placeholder. The stub does not validate it, and the example does not prove that an actual service accepts any credential. The client function reads ACME_API_KEY so the same configuration boundary is visible, while the local server makes the request and parsing path runnable without a key.

For a real SDK, avoid sending an actual credential to a stub unless the test is deliberately designed and authorized to do so. The stub can verify a harmless test marker or inspect request structure without logging sensitive headers. Keep the live endpoint and live credential in a separate test configuration.

A recorded fixture is another offline option. Store a representative, sanitized response in the repository, load it in a test, and assert the parsing result. A fixture is easier to maintain than a server for pure parsing, while a local stub can also exercise HTTP request construction and status handling. Neither replaces a live authentication check.

Skip a live test cleanly when the key is absent

The following shell script checks for the fictional environment variable and exits successfully with a clear skip message when it is absent. It does not make a network request or print the key.

if [ -z "${ACME_API_KEY:-}" ]; then
  printf '%s\n' 'SKIP: ACME_API_KEY is not set'
  exit 0
fi

printf '%s\n' 'Credential is present; run the authorized live test here'

Save it as a script and run it in the environment where the live test would execute. Replace the final message with your project's actual live-test command after configuring an authorized sandbox. Keep the skip visible in CI output and reports: a successful job that skipped the live call is not evidence that the call passed.

For a pytest suite, the following complete test file uses pytest as its only non-standard-library import. It demonstrates the skip condition without making a real API call. Save it as test_live_example.py:

import os

import pytest


@pytest.mark.skipif(
    not os.environ.get("ACME_API_KEY"),
    reason="ACME_API_KEY is required for the authorized live test",
)
def test_live_configuration_is_present():
    assert os.environ.get("ACME_API_KEY")

Run it with pytest test_live_example.py in an environment where pytest is installed. When the variable is absent, pytest reports a skip. When it is present, this demonstration checks only that the value exists; replace the test body with your authorized sandbox call and assertions to verify live behavior. The pytest skip documentation describes skipif and its reason.

Do not print the value in a failure message, command trace, HTTP log, or test artifact. A test can report that a credential is missing or rejected without recording the credential itself.

Configure CI around the trust boundary

Store the sandbox credential in the CI platform's secret store and map it to a job-level environment variable for the live job. In GitHub Actions, secrets cannot be referenced directly in an if: conditional. Set the secret as a job-level environment variable, then check the environment variable in the step condition, as described in GitHub's secrets documentation.

A practical workflow has an offline job that runs for ordinary changes and a live job or step that runs only when the authorized secret is available. The live step should report a skip when it cannot run. Map the secret in the workflow using GitHub's documented expression syntax; the exact mapping belongs in your repository's workflow configuration.

With the exception documented by GitHub for GITHUB_TOKEN, secrets are not passed to runners for workflows triggered from forks. Design the live job so it is not a required check on fork pull requests. Keep syntax, offline tests, and other safe checks available there, and run the live leg later in an authorized context. Do not expose a secret to untrusted pull request code merely to make a check green.

Mask sensitive values in logs, including non-secret sensitive values where appropriate, and never deliberately print keys. GitHub documents the ::add-mask::VALUE mechanism for masking a value; arrange masking without echoing the underlying value into an unprotected log. Review shell tracing, HTTP debugging, exception messages, and uploaded artifacts as well as the normal test output.

A useful result should make the coverage distinction explicit:

  • Offline checks passed and the live test passed.
  • Offline checks passed and the live test was skipped because no credential was available.
  • Offline checks passed and the live test failed with an authentication or service response.
  • Syntax failed before either behavioral layer could run.

Those outcomes call for different reviews. A skipped live test is an honest result, but a release process that requires live verification must arrange an authorized run before treating that requirement as met.

Record the test layer, endpoint type, account type, and sanitized error. Do not include the key in the finding. Use the evidence to decide whether the issue is in the page, the test environment, or an external service.

Finding Initial interpretation Next action
Key absent in a clean scan The live path needs credentials Mark it as needing credentials and record the skipped coverage
Placeholder cannot be mapped to a reader action The configuration instructions are unclear Explain where to obtain and supply the value
Python or JavaScript does not parse Syntax is invalid regardless of credential availability Fix the source and rerun the syntax check
Offline response parser fails The code and fixture disagree Review the documented response shape and parsing logic
Sandbox returns 401 or 403 Authentication or authorization needs investigation Check the authorized account, permissions, endpoint, and documented setup
Live service times out The service or network may be unavailable Retry or investigate without automatically calling it a docs bug
Live test is skipped on a fork The secret is unavailable to that workflow Run the live leg in an authorized context
Example prints a credential Sensitive data may be exposed Remove the logging and follow the credential owner's response process

needs-credentials is not itself a docs-bug classification. It says the scan cannot establish the live result without credentials. A separate human review can still find a documentation problem, such as a placeholder that readers cannot map to the configuration they are asked to supply.

A 401 or 403 also needs context. It can result from a missing or invalid key, insufficient account permissions, or a setup mismatch. Preserve enough sanitized evidence to reproduce the problem with an authorized account, then fix the instructions if they led the reader to the wrong configuration.

Where DocsAlive fits

Before running a block, DocsAlive looks for secret environment reads in supported Python and JavaScript forms. It identifies variable names containing KEY, TOKEN, SECRET, PASSWORD, or CREDENTIAL, including Python environment reads and JavaScript process.env reads. It also recognizes placeholder patterns such as YOUR_API_KEY, <API_KEY>, and relevant shell variables.

When a block has a secret or placeholder requirement, Python and JavaScript are syntax-checked only; other blocks, including shell and curl examples, are skipped. The finding is classified as needs-credentials, which is not counted as a docs bug. For example, a curl block using $ACME_API_KEY is skipped rather than sent. A Python syntax error can still be caught, but a successful syntax check does not test the API call.

Triage also recognizes authentication-related output, including 401 Unauthorized, 403 Forbidden, and invalid or missing API key messages, as needs-credentials. These classifications help distinguish access-dependent results from likely documentation bugs, but they do not replace a review of whether the instructions are understandable.

The sandbox has no access to customer secrets, and running examples with customer-provided test credentials is not currently supported. Run the live leg in your own authorized CI with a test-mode or sandbox credential. Use the product's report to see what was syntax-checked or skipped, and combine it with the offline and live results from your own tests. See findings for the reporting model, and testing AI and LLM SDK documentation examples for provider-specific credential cases.

Checklist

Copy this list into a pull request or release checklist:

  • [ ] Name each required credential and explain where the reader supplies it.
  • [ ] Use an environment variable or an unmistakable placeholder, never a real-looking pasted key.
  • [ ] Separate local construction, request building, parsing, and the live call where practical.
  • [ ] Run syntax checks even when no credential is available.
  • [ ] Test request and response handling with a local stub or sanitized fixture.
  • [ ] Use an authorized test-mode or sandbox account for live verification.
  • [ ] Store the credential in the CI secret store and map it to a job-level environment variable.
  • [ ] Skip the live step clearly when the secret is absent.
  • [ ] Keep the live job from being required on fork pull requests.
  • [ ] Mask sensitive values and check logs and artifacts for accidental disclosure.
  • [ ] Record which layer passed, failed, or was skipped.
  • [ ] Review unclear placeholders as documentation issues.

The sources we checked

These sources were read on November 10, 2026.

Source What it covers
GitHub Actions secrets Secret availability, conditional steps, and masking
The Twelve-Factor App: Config Keeping configuration separate from code
Python os documentation Environment variables and their default values
pytest skipping Conditional skips and reasons
Supported stacks Product language coverage and credential limitations
Findings Result categories and triage

FAQ

Is a missing API key a documentation bug?

Not by itself. It means the live call was not verified in that environment. The page may still have a documentation problem if it fails to explain which credential a reader needs or where to provide it.

What can I test without a credential?

Check syntax and, where the client permits it, construction, request building, and response parsing. A local stub or sanitized fixture can exercise behavior without proving that a live service accepts a key.

Why does the live test skip on a fork pull request?

GitHub generally does not pass repository secrets to workflows triggered from forks. Run offline checks there and arrange the live check in an authorized context without making it a required fork check.

Can I put a fake key in a code example?

Use an unmistakable placeholder such as YOUR_API_KEY or read a clearly named environment variable. Explain how readers supply their own value, and avoid fake strings that look like real credentials.

Does DocsAlive run credential-dependent examples with my test key?

No. Customer-provided test credentials are not supported. Credential-dependent Python and JavaScript receive syntax checks, while other affected blocks are skipped; run the live leg in your own authorized CI.

Does a passing offline test prove the API works?

No. It establishes behavior against the tested stub or fixture. A live sandbox test separately checks authentication and current service behavior for the authorized account and path used.

Keep reading
Outlined documentation pages with a lime pulse line running out of the stackGuide

Docs Testing: How to Test the Code Samples in Your Docs

Why code examples in documentation break, the ways teams test them (doctest, docs-as-tests, CI runners) and how to run every sample on each release.

Two lenses held over the same documentation page, each bringing a different layer into focusComparison

Vale vs markdownlint: What Each Docs Linter Catches

Vale checks prose style; markdownlint checks Markdown structure. What each catches, sample configs, running both in CI and what neither one tests.

Blueprint of two documentation pages: one with a handwritten test script, one whose code blocks are framed and checkedComparison

Doc Detective vs DocsAlive: Testing the Code in Your Docs

Doc Detective is a free, open-source docs-as-tests framework; DocsAlive is a hosted docs CI for code examples. Features, price, setup and when to use each.

A machine blueprint above a ruled documentation page, with a caliper measuring between themComparison

Promptless Alternative: Promptless vs DocsAlive Compared

Promptless drafts docs updates from what you ship; DocsAlive runs the code examples you've published. Features, pricing, security and when to use each.

A long workbench of outlined testing instruments, each with a small lime light, beside a stack of documentation pagesGuide

Best Free Tools to Test Code Examples in Documentation

Free, open-source tools that run the code examples in your docs: rustdoc, Go examples, doctest, pytest plugins, Sphinx doctest, Doc Detective and byexample.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Testing SDK Docs Before Every Release: A Release-Gate Guide

Test SDK and client-library docs against the package you ship: build the wheel or npm tarball, install it clean, run every example, and gate the release in CI.

Outlined documentation pages with a lime pulse line running out of the stackGuide

How to Test Quickstarts and Tutorials in Your Docs

Test the onboarding path a new reader follows: run quickstarts in a clean environment, in page order, sort real bugs from credentials and fragments, and wire it into CI.

Outlined documentation pages with a lime pulse line running out of the stackGuide

How to Test CLI Documentation: Commands, Flags and Output

Test the commands a CLI page promises: install in a clean container, run examples in order, compare flags with --help, check exit codes and output, and run it in CI.

Outlined documentation pages with a lime pulse line running out of the stackGuide

How to Test API Reference Docs: Requests, Responses and SDKs

Test API reference pages against the contract they publish: methods, parameters, request and response examples, SDK snippets, status codes and OpenAPI samples.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Docs testing for API-first companies

Keep API quickstarts, SDK snippets and curl examples working across releases.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Docs testing for CLI and developer-tool vendors

Test developer-tool install paths, package managers and command examples across releases.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Testing README Code Examples: Install, Usage and CI

Keep README install commands and usage examples working with clean environments, output checks and CI.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Testing Notebooks and Executable Docs: Cells, Kernels and Output

Test notebooks in fresh kernels, check meaningful output and manage dependencies, data and drift in CI.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Testing Docs in GitHub Actions: Pull Requests and Scheduled Checks

Run documentation example checks in GitHub Actions with practical guidance on schedules, path filters, caching, secrets and reports.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Testing Code Examples in MkDocs, Docusaurus and Sphinx Sites

Find and test documentation examples in Markdown, MDX and reStructuredText, alongside site builds and link checks.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Python Docs Testing: Doctest, Pytest and Markdown

Test Python documentation with doctest, pytest, Markdown example execution and isolated environments.

Outlined documentation pages with a lime pulse line running out of the stackGuide

JavaScript Docs Testing: Node, TypeScript and Modules

Test Node and TypeScript documentation examples across module formats, package versions and type checks.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Testing Go documentation examples

Test Go example functions and Markdown snippets against the intended module versions in clean environments.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Sphinx Docs Testing: doctest, code-block and MyST

Test Sphinx documentation examples: what the doctest and linkcheck builders prove, code-block and literalinclude, MyST fences, and what a Markdown scan covers.

Outlined documentation pages with a lime pulse line running out of the stackGuide

ReadMe Docs Testing: CLI, OpenAPI and Code Examples

Test ReadMe documentation examples: docs-as-code and bi-directional sync, what lint and oas:validate prove, Tabs variants and runnable code.

Outlined documentation pages with a lime pulse line running out of the stackGuide

TypeScript Docs Testing: tsc --noEmit and Runtime

Test TypeScript documentation examples: type-check snippets with tsc --noEmit in a consumer project, then run them, and what each check proves.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Docusaurus Docs Testing: Code Blocks, Tabs and MDX

Test Docusaurus documentation examples: builds vs runnable code, code block metadata, Tabs install variants, MDX pages and clean-container runs.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Mintlify Docs Testing: CodeGroup, Tabs and the CLI

Test Mintlify documentation examples: docs.json, MDX pages, CodeGroup and Tabs variants, what mint broken-links and mint validate prove, and clean runs.

Outlined documentation pages with a lime pulse line running out of the stackGuide

MkDocs Docs Testing: Code Examples, Tabs and Snippets

Test MkDocs documentation examples: strict builds, Material tabs, Snippets includes, mkdocstrings pages and clean-container runs.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Testing Installation Instructions: pip, npm and Containers

Test documented install steps in clean environments: pip and extras, venv, npm install vs npm ci, containers and the first import.

Outlined documentation pages with a lime pulse line running out of the stackGuide

AI SDK Docs Testing: LLM Examples, Keys and Drift

Test AI and LLM SDK documentation examples: requests, responses, streaming, tool calls, credentials, model retirements and CI.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Testing migration guides and changelog examples

Test before and after examples against the right releases and make migration documentation part of the release gate.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Docs Testing Checklist: Code Examples Before Release

A practical pre-release checklist for testing documentation examples, reviewing gaps and verifying published packages.

Outlined documentation pages with a lime pulse line running out of the stackGuide

The Cost of Broken Code Examples in Documentation

Understand how broken documentation examples create friction in developer adoption and support, and how to reduce it.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Testing Rust documentation examples

Test Rust rustdoc examples and cargo documentation tests, and keep native Rust checks because compiled Rust fences are recognized but not executed.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Testing VitePress documentation examples

Test VitePress Markdown examples separately from the site build, including code groups, includes and what a plain Markdown scan covers.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Testing docs in GitLab CI

Run documentation example checks in GitLab CI jobs, keep site builds separate from execution, and review what a scan covers and skips.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Testing JSON, TOML and YAML examples in docs

Test JSON, TOML and YAML config examples in documentation: parse, schema and tool checks, honest fence labels, fragments, CI jobs and what a scan covers.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Testing Java documentation examples

Test Java examples in READMEs, SDK docs and Javadoc: the source launcher, javac, JShell scripts, assertions, @snippet files, CI and what a scan covers.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Testing PHP documentation examples

Test PHP examples in docs: php -l lint, clean-directory runs, assertions with zend.assertions, Composer scratch projects, PHPUnit, CI and scan coverage.

A machine blueprint above a ruled documentation page, with a caliper measuring between themGuide

Documentation Drift: How to Detect It Before Users Do

What documentation drift is, why code examples drift first, and practical ways to detect it: ownership, release checks, link and execution tests.

See which of your code samples still run.

Send us a public repository. Our team runs DocsAlive on its docs and emails you the real report within 2 business days. Free, no signup.