Development
Everything that builds, lints, tests or generates code runs in a dev container built from Dockerfile.dev. The host needs Docker and Make, nothing else. make help lists every target.
Layout
| Path | Contents |
|---|---|
api/v1.yaml | OpenAPI 3.1.0 spec. Source of truth for routes, models and both clients. |
api/_templates/ | Templates that make fastapi-code-generator emit async def handlers that call impl. |
ibkrapi/server.py | App setup, auth and logging middleware, /v1 and /mcp mounts. |
ibkrapi/api/impl.py | All endpoint logic. One function per operationId. |
ibkrapi/api/_generated/ | Generated routers and Pydantic models. Never edited by hand. |
ibkrapi/ibclient.py | The shared ib_async connection and its reconnect loop. |
ibkrapi/contracts.py, marketdata.py, serialize.py | Contract builders, IBKR data calls and the wickworks POST, SDK object to dict converters. |
ibkrapi/pacing.py, cache_*.py, historian.py, exec_history.py | The pacing gate and the disk stores (Pacing and history). |
ibkrapi/mcp_server.py | The MCP tools. |
ibkrapi/config.py, logger.py | Config loading, JSON logging. |
tests/ | pytest suite with a mocked IB connection. |
pkg/clients/ | Generated Go and Python clients. |
scripts/ | Age gate bump, Go module age check, compose audit, a one-time v0.3.0 spec patch. |
docs/ | This site. |
.agents/ | Agent rules, the client skill, the OpenClaw plugin, marketplace manifests. |
Contributor and agent rules live in AGENTS.md and .agents/rules/.
Lint and tests
make lint # ruff check ibkrapi tests, pyright ibkrapi, bandit -r ibkrapi
make test # pytest -ra
make format # ruff format ibkrapi tests
make lint-fix # ruff check --fix ibkrapi tests
make shell # interactive shell in the dev container
make lint and make test build the dev image first and then run with --network none, so they need no credentials and no network once the image exists. They are exactly what CI runs.
The tests never talk to a gateway. tests/conftest.py replaces get_ib() with a mock IB object, turns every disk store off and sets pacing limits high. HTTP tests use FastAPI’s TestClient against /v1/.... Warnings are errors (filterwarnings = ["error"]).
Ruff skips ibkrapi/api/_generated, and pyright ignores diagnostics in it; the generated routers use FastAPI’s param: str = ... idiom, which pyright flags.
Code generation
The spec is the API. To add or change an endpoint:
- Edit
api/v1.yaml. - Run
make generate. It runsgenerate-api(routers and models intoibkrapi/api/_generated/),generate-client-go(pkg/clients/go/client.gen.go) andgenerate-client-python(pkg/clients/python/ibkr-httpapi-client/). Each one deletes and rewrites its output. - Write or update the function in
ibkrapi/api/impl.pynamed after the snake-casedoperationId. - Run
make generate-api-check, which regenerates and fails ifibkrapi/api/_generateddiffers from what is committed.
Generated handlers pass arguments to impl by position, in spec order: query parameters, then refresh where it exists, then the request body last. The impl function must take them in exactly that order. Pyright does not report a mismatch because it ignores the generated files, so cover each new route with a request in tests/test_server.py.
A new IBKR call in impl.py goes inside async with pacing.guard(<tier>, <key>).
Dependencies
- Runtime pins are in
requirements.in, locked with hashes intorequirements.txt. Both Dockerfiles install with--require-hashes. - Change them only through Make, which first moves the age gate (
[tool.uv] exclude-newerinpyproject.toml) to seven days ago:
make pkg-add PKG=name==1.2.3
make pkg-upgrade PKG=name NEW=1.2.4
make pkg-remove PKG=name
make pkg-update
- The Go client has its own targets (
pkg-add-go,pkg-upgrade-go,pkg-remove-go,pkg-lock-go);scripts/check_go_age.shrefuses module versions younger than seven days. - Scans:
make audit(pip-audit),make audit-go(govulncheck),make audit-compose(compose hardening). - Dev tools are in
requirements-dev.txt, only in the dev image.
CI
.github/workflows/pipeline.yml runs on every push:
| Trigger | Jobs |
|---|---|
| Any branch | code: make lint and make test on a plain runner, through the shared code workflow. |
main | code, then badges (version and license badges on the badges branch). |
v* tag | wait for that commit’s main run to pass, release (GitHub release from the annotated tag), publish-to-clawhub (the ibkr-httpapi skill and plugin), badges. |
No image is built in CI. The api image builds on the host through compose, and the gateway image cannot be redistributed.
Two more workflows run beside it: mirror-and-archive.yml mirrors the repository to GitLab and Codeberg and archives it, and issue-pull.yml copies issues from the mirrors back to GitHub.
Docs
The site is docs/, built by GitHub Pages from main with the just-the-docs theme. Build it locally with the same image GitHub uses:
make docs
The output goes to a gitignored directory named by DOCS_SITE in the Makefile. Pages link to each other with relative .md paths and to repository files with full GitHub URLs.
Releases
Every release gets a section in CHANGELOG.md and an annotated v* tag on a commit that is already on main. Pushing the tag starts the release jobs above.