API testing
Verify that an API honors its contract, enforces its authorization rules, and fails correctly, without depending on a browser or a full end-to-end environment.
When to invoke
- "Write integration tests for these endpoints."
- "Validate responses against our OpenAPI spec."
- "Test authentication and authorization on this API."
- "Our API returns 200 with an error body; how do we test that?"
- "Add API tests to CI."
Test the contract, then the behavior
| Layer |
Question |
Typical assertion |
| Transport |
Did the call succeed at HTTP level? |
Status code, headers, content type |
| Schema |
Does the payload match the declared shape? |
Validate against OpenAPI or GraphQL schema |
| Semantics |
Is the value correct for this input? |
Field values, computed results |
| Authorization |
Can the wrong caller reach it? |
401 without token, 403 with wrong role |
| Side effects |
Did state change as promised? |
Re-read, or verify emitted event |
Testing only the happy path with a 200 assertion is the most common and least useful API suite.
Validate against the schema, not hand-written shapes
Hand-written expected payloads drift from the spec. Assert against the source of truth.
// Validate the response against the OpenAPI schema itself
const valid = ajv.validate(schema.components.schemas.User, response.body);
expect(valid, JSON.stringify(ajv.errors)).toBe(true);
For GraphQL, run introspection or load the SDL and validate both the query and the response. A query that requests a removed field must fail the build, not silently return null.
Authorization is a test matrix, not a single case
Every protected endpoint needs coverage for each meaningful caller:
- No credentials — expect 401.
- Valid credentials, wrong role — expect 403.
- Valid credentials, correct role, another tenant's resource — expect 403 or 404, and be deliberate about which.
- Expired or malformed token — expect 401, never 500.
The cross-tenant case is where real breaches occur and where suites are usually silent.
Error paths deserve explicit assertions
- Assert the status code and the error body shape, including a stable machine-readable code.
- Confirm the error does not leak stack traces, SQL, internal hostnames, or credentials.
- Verify validation errors identify the offending field.
- Check that an unexpected server error returns 5xx rather than a 200 with an error payload.
Pagination, ordering, and idempotency
- Pagination: assert page size, that cursors advance, and that the final page terminates. Verify no duplicates or gaps across pages under a stable sort.
- Ordering: if order is part of the contract, assert it; if not, do not assert it, or the test becomes flaky.
- Idempotency: replay PUT and DELETE and confirm the same terminal state. For POST with an idempotency key, replay must not create a second resource.
Test data and isolation
- Create the data each test needs and clean it up, or use a transaction rolled back per test.
- Never depend on records that another test created; ordering dependencies cause intermittent failures.
- Parameterize identifiers so parallel runs do not collide.
- Keep credentials in the environment or a secret store, never in the repository or in assertions.
Gotchas
- Asserting the whole response body breaks on every additive field. Assert the fields under test plus schema validity.
- Time and timezone leak into assertions. Freeze or inject clocks rather than comparing to
now.
- Retries can hide flakiness and non-idempotent bugs. If a retry makes it pass, investigate before adding the retry.
- Recorded fixtures go stale silently. Re-record on a schedule or verify against a live contract.
- Rate limits fail CI unpredictably. Use a dedicated test tenant or account with known limits.
Output template
## API test result
**Status:** pass | fail | blocked
**Summary:** <endpoints covered and outcome>
### Details
| Endpoint | Case | Expected | Result |
| --- | --- | --- | --- |
| <method path> | <happy, auth, error, pagination> | <status and shape> | <pass or fail> |
Schema source: <OpenAPI or GraphQL SDL used for validation>
Authorization matrix covered: <cases exercised>
### Validation
- Schema validation performed: <checked and result>
- Cross-tenant access case covered: <checked and result>
Quality gate
References
1---2name: api-testing3description: Test REST, GraphQL, and gRPC APIs at the contract and behavior level with schema validation, authentication and authorization coverage, error-path assertions, pagination and idempotency checks, and CI-ready suites. Use when the user asks to test an API, validate responses against OpenAPI or a GraphQL schema, cover auth and error cases, or build an integration suite for endpoints.4license: MIT5---67<!-- Generated from harness/github-copilot/skills/api-testing/SKILL.md by harness/claude-code/scripts/convert_from_copilot.py. Edit the source, not this file. -->89# API testing1011Verify that an API honors its contract, enforces its authorization rules, and fails correctly, without depending on a browser or a full end-to-end environment.1213## When to invoke1415- "Write integration tests for these endpoints."16- "Validate responses against our OpenAPI spec."17- "Test authentication and authorization on this API."18- "Our API returns 200 with an error body; how do we test that?"19- "Add API tests to CI."2021## Test the contract, then the behavior2223| Layer | Question | Typical assertion |24| --- | --- | --- |25| Transport | Did the call succeed at HTTP level? | Status code, headers, content type |26| Schema | Does the payload match the declared shape? | Validate against OpenAPI or GraphQL schema |27| Semantics | Is the value correct for this input? | Field values, computed results |28| Authorization | Can the wrong caller reach it? | 401 without token, 403 with wrong role |29| Side effects | Did state change as promised? | Re-read, or verify emitted event |3031Testing only the happy path with a 200 assertion is the most common and least useful API suite.3233## Validate against the schema, not hand-written shapes3435Hand-written expected payloads drift from the spec. Assert against the source of truth.3637```javascript38// Validate the response against the OpenAPI schema itself39const valid = ajv.validate(schema.components.schemas.User, response.body);40expect(valid, JSON.stringify(ajv.errors)).toBe(true);41```4243For GraphQL, run introspection or load the SDL and validate both the query and the response. A query that requests a removed field must fail the build, not silently return null.4445## Authorization is a test matrix, not a single case4647Every protected endpoint needs coverage for each meaningful caller:4849- **No credentials** — expect 401.50- **Valid credentials, wrong role** — expect 403.51- **Valid credentials, correct role, another tenant's resource** — expect 403 or 404, and be deliberate about which.52- **Expired or malformed token** — expect 401, never 500.5354The cross-tenant case is where real breaches occur and where suites are usually silent.5556## Error paths deserve explicit assertions5758- Assert the **status code and the error body shape**, including a stable machine-readable code.59- Confirm the error **does not leak** stack traces, SQL, internal hostnames, or credentials.60- Verify **validation errors identify the offending field**.61- Check that an unexpected server error returns 5xx rather than a 200 with an error payload.6263## Pagination, ordering, and idempotency6465- **Pagination:** assert page size, that cursors advance, and that the final page terminates. Verify no duplicates or gaps across pages under a stable sort.66- **Ordering:** if order is part of the contract, assert it; if not, do not assert it, or the test becomes flaky.67- **Idempotency:** replay PUT and DELETE and confirm the same terminal state. For POST with an idempotency key, replay must not create a second resource.6869## Test data and isolation7071- Create the data each test needs and clean it up, or use a transaction rolled back per test.72- Never depend on records that another test created; ordering dependencies cause intermittent failures.73- Parameterize identifiers so parallel runs do not collide.74- Keep credentials in the environment or a secret store, never in the repository or in assertions.7576## Gotchas7778- **Asserting the whole response body** breaks on every additive field. Assert the fields under test plus schema validity.79- **Time and timezone leak into assertions.** Freeze or inject clocks rather than comparing to `now`.80- **Retries can hide flakiness and non-idempotent bugs.** If a retry makes it pass, investigate before adding the retry.81- **Recorded fixtures go stale silently.** Re-record on a schedule or verify against a live contract.82- **Rate limits fail CI unpredictably.** Use a dedicated test tenant or account with known limits.8384## Output template8586```markdown87## API test result8889**Status:** pass | fail | blocked90**Summary:** <endpoints covered and outcome>9192### Details93| Endpoint | Case | Expected | Result |94| --- | --- | --- | --- |95| <method path> | <happy, auth, error, pagination> | <status and shape> | <pass or fail> |9697Schema source: <OpenAPI or GraphQL SDL used for validation>98Authorization matrix covered: <cases exercised>99100### Validation101- Schema validation performed: <checked and result>102- Cross-tenant access case covered: <checked and result>103```104105## Quality gate106107- [ ] Responses are validated against the declared schema, not hand-written shapes.108- [ ] Every protected endpoint covers unauthenticated, wrong-role, and cross-tenant cases.109- [ ] Error paths assert status, stable error code, and absence of sensitive leakage.110- [ ] Pagination assertions cover advance and termination without duplicates or gaps.111- [ ] Ordering is asserted only when it is part of the contract.112- [ ] Tests create and clean their own data and can run in parallel.113- [ ] No credentials appear in the repository, logs, or assertions.114- [ ] Retries are not used to mask non-deterministic behavior.115116## References117118- [OpenAPI Specification](https://spec.openapis.org/oas/latest.html)119- [GraphQL specification](https://spec.graphql.org/)120- [JSON Schema](https://json-schema.org/specification)121- [RFC 9110: HTTP Semantics](https://www.rfc-editor.org/rfc/rfc9110.html)