Turn your Rails integration tests into API documentation.
Reqcord runs your test suite, watches the HTTP requests and responses the tests make, and writes the documentation from what it saw — Markdown, runnable cURL, a Postman collection and an OpenAPI document. No DSL, no annotations, no second copy of every request: the tests are the source of truth.
flowchart LR
subgraph tests["Your integration tests"]
direction TB
T1["creates customer<br/>POST /api/v2/customers → 201"]
T2["rejects unknown status<br/>POST /api/v2/customers → 422"]
T3["requires authentication<br/>GET /api/v2/customers → 401"]
end
R["Rails route table"]
subgraph reqcord["Reqcord"]
direction TB
C["capture · sanitize · infer"]
D[("dataset.json")]
C --> D
end
subgraph out["Generated"]
direction TB
MD["Markdown pages"]
CU["cURL scripts"]
PM["Postman collection<br/>(Hoppscotch)"]
OA["OpenAPI 3.1"]
end
SC["Scalar<br/>mount Reqcord::Web"]
tests --> C
R --> C
D --> MD
D --> CU
D --> PM
D --> OA
OA --> SC
# Gemfile
group :development, :test do
gem "reqcord"
endbundle install
bin/rails reqcord:init # writes reqcord.yml — point test.paths at your API tests
bin/rails reqcord:generate # runs them with capture on, writes docs/api/[reqcord] captured 87 request(s), 85 matched a documented route
[reqcord] captured a successful 2xx request for 15 of 16 endpoint(s)
[reqcord] routes: 18 = 15 documented + 1 uncovered + 2 skipped
Every route ends in exactly one bucket, so nothing goes missing quietly. In
CI, bin/rails reqcord:check fails when the committed docs are behind the
tests.
Optionally, browse it inside the app with Scalar:
# config/routes.rb
mount Reqcord::Web => "/api-docs" if Rails.env.development?From an ordinary test —
test "creates customer" do
post "/api/v2/customers",
params: { customer: { name: "John Doe", email: "john@example.com", status: "active" } },
headers: { "Authorization" => "Bearer test-token" },
as: :json
assert_response :created
end— a page like this, plus a create.sh, a Postman request and an OpenAPI
operation built from the same captured request:
# Create Customer
`POST /api/v2/customers`
## Body Parameters
| Field | Type | Required | Values |
| --- | --- | --- | --- |
| `customer.name` | string | yes | `"John Doe"` |
| `customer.email` | string | yes | `"john@example.com"` |
| `customer.status` | string | yes | `"active"` \| `"passive"` |
## cURL
```bash
curl --request POST \
--url "http://localhost:3000/api/v2/customers" \
--header "Authorization: Bearer {{token}}" \
--header "Content-Type: application/json" \
--data '{"customer":{"name":"John Doe","email":"john@example.com","status":"active"}}'
```
## Responses
### 201 Created
### 401 Unauthorized
### 422 Unprocessable ContentThe parameter table is inferred from the requests the application
accepted: two tests sent "active" and "passive", a third sent
"inactive" and got a 422, so the page lists the two values that work and
keeps the rejection as a response example. Credentials never reach a file —
Bearer test-token became Bearer {{token}} before anything was stored.
| Getting started | install, configure, generate, read the output |
| Configuration | every key in reqcord.yml, defaults and environment overrides |
| Exporters | Markdown, cURL, Postman / Hoppscotch, OpenAPI — what each contains |
| Reqcord::Web | serve the docs from the app with Scalar |
| Route coverage | how the route table becomes endpoints; resource, match via:, engines, filters |
| Capture and inference | what is captured, sanitization, how parameter tables and response fields are derived, the dataset |
| Troubleshooting | when the output looks thin |
| Architecture | pipeline, modules, design principles, working on Reqcord |
| Changelog |
Four runnable applications under examples/, each with the
documentation it generates committed next to it:
| Example | Tests | Shows |
|---|---|---|
test-app |
Minitest | three small resources: auth, closed value sets, PATCH/PUT folding, a member action |
spec-app |
RSpec | the same API from request specs |
complex-test-app |
Minitest | a store API: products, cart, orders, nested notes, array bodies, filter[category], a form login, X-Api-Key admin namespace, two API versions, 400/403/404/409 |
complex-spec-app |
RSpec | the store API from request specs |
examples/reqcord.yml is an annotated configuration
file.
| Ruby | 3.2, 3.3, 3.4 |
| Rails | 7.1, 7.2, 8.0, 8.1 |
| Tests | Minitest integration tests, RSpec request specs |
Every Ruby × Rails pair Rails itself supports runs in CI.
- Tests are the source of truth. Reqcord never reads models, serializers or contracts; what the application accepted and answered is the spec.
- Capture once, export anywhere. One dataset, any number of formats.
- Nothing is lost silently.
routes = documented + uncovered + skipped, reconciled on every run. - Generated docs are safe. Sanitization runs before anything is stored.
MIT.