koban is a small, scriptable Rust CLI and client library for
Invoice Ninja, built for humans at a terminal
and AI agents that need stable JSON output, explicit errors, and shell
completions.
The name is a nod to the koban (小判), the Edo-period oval gold coin ninja were paid in — a short, currency-flavored name for a tool that drives an invoicing API.
📖 Documentation: https://jamesbrink.online/koban/
The project is a Cargo workspace with two crates:
koban— the reusable Invoice Ninja API client library (cargo add koban).koban-cli— the command-line tool, which installs akobanbinary (cargo install koban-cli).
The CLI exposes a broad Invoice Ninja API surface with guarded write commands, stable JSON for automation, human-friendly tables, shell completions, direct binary installs, and a reusable Rust client library.
For direct binary installs on macOS and Linux:
curl -fsSL https://raw.githubusercontent.com/jamesbrink/koban/main/install.sh | shInstaller options:
# A specific release tag, into a custom directory
curl -fsSL https://raw.githubusercontent.com/jamesbrink/koban/main/install.sh | \
KOBAN_VERSION="v0.2.0" KOBAN_INSTALL_DIR="$HOME/.local/bin" sh
# The rolling nightly build from main
curl -fsSL https://raw.githubusercontent.com/jamesbrink/koban/main/install.sh | \
KOBAN_VERSION="nightly" shKOBAN_VERSION accepts latest (default, newest stable release), nightly
(the rolling prerelease built from main), or a tag such as v0.2.0. Pass
--help to the script (sh install.sh --help) to print usage.
The installer downloads GitHub release tarballs, verifies SHA256SUMS when
available, installs koban into ~/.local/bin by default, and prints the
installed version. It uses the same macOS/Linux asset names as release CI and
koban update.
Other install paths:
cargo install koban-cli
nix run github:jamesbrink/koban -- --helpkoban is built to be driven by AI coding agents. One command teaches your agent the whole CLI:
koban skill install --target all # Claude Code, Codex, and an AGENTS.md block
koban skill install --target claude-code # or pick a single harness
koban skill generate # write to ./koban-skills to review firstOnce the skill is installed, an agent can track your work in Invoice Ninja
automatically — logging billable tasks and time, drafting and sending
invoices, recording expenses, and reporting on outstanding balances — while it
works, instead of you context-switching to the web UI. The skill teaches the
agent koban's stable JSON output and its --dry-run/--yes safety gates, so
every write is previewed before it happens.
Supported harnesses include Claude Code, OpenAI Codex CLI, pi, Cursor, OpenClaw,
Claude Desktop, and any tool that reads AGENTS.md (Windsurf, Gemini CLI, Aider,
Copilot, Zed, …). See the
agent skill docs.
koban --help
koban --version
koban update --check
koban completions zsh
koban completions bash
koban completions fish
koban completions nushell
# Authentication (stores the token in a 0600 config file or the OS keychain)
koban auth login --token <token>
koban auth login --keychain
echo "$TOKEN" | koban auth login --no-verify
koban auth status
koban auth logout
# Agent skill (teach Claude Code, Codex, pi, Cursor, ... how to drive koban)
koban skill generate # write to ./koban-skills for review
koban skill install --target claude-code # into ./.claude/skills/koban
koban skill install --global --target all # into ~/.claude, ~/.agents, AGENTS.md
koban skill install --target openclaw # into ./skills/koban (OpenClaw workspace)
# OpenClaw users can also install straight from Git:
# openclaw skills install git:jamesbrink/koban
koban skill install --target claude-desktop # build koban.zip to uploadCredentials resolve in this order, so agents and CI stay deterministic:
INVOICE_NINJA_API_TOKEN env → OS keychain → stored config file. Set
KOBAN_CONFIG_DIR to override where the config file lives.
The implemented API commands cover read workflows, guided/JSON writes, bulk/custom actions, and uploads across Invoice Ninja resource families. Invoice-specific PDF downloads remain first-class because their route shape is documented and invitation-key based:
koban statics --output json
koban clients list --page 1 --per-page 20
koban clients show <id> --output json
koban clients template --output json
koban clients edit-template <id> --output json
koban invoices list
koban invoices list --filter status_id=gt:1 --sort 'date|desc' --all --limit 50
koban invoices show <id>
koban invoices template --output json
koban invoices edit-template <id> --output json
koban invoices create --client-id <client_id> --line-item product_key=Consulting,quantity=1,cost=100 --dry-run
koban invoices create --data-file invoice.json --include client
koban invoices update <id> --data-file invoice.json --dry-run
koban invoices update <id> --public-notes "Thanks again" --mark-sent --yes
koban invoices delete <id> --dry-run
koban invoices delete <id> --yes
koban invoices bulk --action archive --id <id> --id <id> --dry-run
koban invoices action <id> --action mark_paid --dry-run
koban invoices upload <id> --file contract.pdf --dry-run
koban invoices download <invitation_key> --output-file invoice.pdf
koban invoices delivery-note <id> --output-file delivery-note.pdf
koban payments list
koban payments show <id>
koban payments template --output json
koban payments edit-template <id> --output json
koban quotes list
koban quotes show <id>
koban quotes template --output json
koban quotes edit-template <id> --output json
koban credits list
koban credits show <id>
koban credits template --output json
koban credits edit-template <id> --output json
koban vendors list
koban vendors show <id>
koban vendors template --output json
koban vendors edit-template <id> --output json
koban expenses list
koban expenses show <id>
koban expenses template --output json
koban expenses edit-template <id> --output json
koban projects list
koban projects show <id>
koban projects template --output json
koban projects edit-template <id> --output json
koban tasks list
koban tasks show <id>
koban tasks template --output json
koban tasks edit-template <id> --output json
koban products create --name Consulting --price 100 --dry-run
koban products update <id> --field notes="Hourly support" --dry-run
koban products delete <id> --dry-run
koban recurring-invoices action <id> --action start --dry-run
koban search run --field query=acme --dry-run
koban reports run --data-file report.json --dry-runGeneric resource single-record actions use Invoice Ninja's bulk endpoint when
that is the published upstream shape. Resources with official single-record
action routes, such as payments, quotes, purchase orders, recurring invoices,
and recurring quotes, use GET /api/v1/{resource}/{id}/{action}.
Endpoint runner payload flags are accepted only for POST and PUT; GET and
DELETE reject payloads so dry-runs cannot show a body that the live request
would ignore.
The koban crate is a standalone Invoice Ninja API client that other Rust
applications can depend on:
cargo add kobanuse koban::{ApiClient, Config};
#[tokio::main]
async fn main() -> koban::Result<()> {
// Reads INVOICE_NINJA_API_TOKEN and optional INVOICE_NINJA_BASE_URL.
let client = ApiClient::new(Config::from_env()?);
// Typed resource accessors return the built-in models.
for invoice in client.invoices().list().await? {
println!("{} -> {}", invoice.number, invoice.balance);
}
// Or work with any resource and your own type / serde_json::Value.
let client_record = client.clients().get("client_id").await?;
println!("{}", client_record.display_name);
Ok(())
}The typed models (Invoice, Client, Payment, ...) are forward-compatible:
unknown fields are preserved in an extra map, and the raw JSON methods
(client.get_json(...), post_json, ...) remain available as a low-level escape
hatch. The library depends only on thiserror for its error type by default;
enable the miette feature for diagnostic help text on KobanError.
Invoice Ninja v5 exposes an API under /api/v1. Hosted production is
https://invoicing.co, and self-hosted installs use the same namespace under
their own base URL. Invoice Ninja also provides a public demo API at
https://demo.invoiceninja.com with the demo token TOKEN; use that target for
live smoke tests whenever possible.
Authentication is token based. Requests require X-API-TOKEN, and the developer
guide also documents X-Requested-With: XMLHttpRequest as a required security
header. JSON write requests must send Content-Type: application/json.
The implemented koban API surface is intentionally boring and durable. The
core resource command shape is:
koban statics
koban <resource> list
koban <resource> show <id>
koban <resource> template
koban <resource> edit-template <id>
koban <resource> create
koban <resource> update <id>
koban <resource> delete <id>
koban <resource> bulk
koban <resource> action <id>
koban <resource> upload <id>
koban invoices download <invitation_key>
koban invoices delivery-note <id>
koban quotes download <invitation_key>
koban purchase-orders download <invitation_key>
koban search run
koban reports run --endpoint reports/invoices
koban charts run --endpoint charts/totals
koban utility run
Custom endpoint overrides are read-only and only send GET requests. Use
first-class resource commands for mutations.
<resource> includes clients, invoices, payments, quotes, credits,
vendors, expenses, projects, tasks, locations, products,
recurring-invoices, purchase-orders, recurring-expenses,
recurring-quotes, bank-transactions, bank-integrations,
bank-transaction-rules, group-settings, expense-categories, tax-rates,
payment-terms, task-schedulers, task-statuses, activities,
system-logs, documents, designs, templates, users, companies,
company-gateways, company-ledger, company-users, tokens, webhooks,
subscriptions, and client-gateway-tokens.
Inspect-only/audit groups activities, system-logs, and company-ledger
expose only safe reads. Import/preimport endpoints are not listable resource
families in the official OpenAPI spec, so they are intentionally left for a
dedicated guarded workflow.
Some official resources publish narrower route sets than the generic command
shape. Koban rejects unsupported commands locally before networking, such as
documents upload, tax-rates create, or templates list.
The template and edit-template commands use Invoice Ninja's read-only
GET /create and GET /{id}/edit routes. They return default/editable payloads
for schema discovery; they do not create or update records.
List commands accept raw Invoice Ninja query filters and sorting:
koban clients list --filter balance=gt:1000 --filter name=Bob --sort 'name|desc'
koban invoices list --all --limit 100 --output json--all stops after 100 pages to avoid accidental unbounded traversal. JSON
output includes meta.page_cap_reached when that guardrail is hit.
Invoice download commands also use read-only GET routes and write PDF bytes to
explicit file paths. Existing files are not overwritten unless --force is set.
Write commands accept either one raw JSON source or guided flags. Resource
writes expose broad guided fields such as --name, --number, --client-id,
--vendor-id, --project-id, --date, --due-date, --amount, --price,
--quantity, notes, repeatable --field key=value, and repeatable
--line-item key=value,... for document-like resources. Raw JSON cannot be
combined with guided fields or --line-item. Generic --field values parse
JSON-like scalars (true, false, null, and numbers); quote a field value to
force a JSON string, such as --field number='"1000"'.
koban invoices create --data '{"client_id":"...","line_items":[]}' --dry-run
koban invoices create --data-file invoice.json --include client
printf '%s' '{"public_notes":"Updated"}' | koban invoices update <id> --stdin
koban invoices create --client-id <client_id> \
--line-item product_key=Consulting,quantity=1,cost=100 \
--dry-run
koban products create --name Consulting --price 100 --dry-run
koban clients create --field name=Acme --field contacts.email=ap@example.test --dry-runGeneric resource create/update/delete/bulk/upload/action commands require
--yes unless --dry-run is used. Generic endpoint runner defaults may use
non-GET methods with --yes, but custom --endpoint overrides are GET-only.
Invoice create/update keep
their lighter workflow for ordinary draft edits, but require --yes when they
mark sent, send email, mark paid, cancel, save default footer/terms, retry
e-send, or otherwise cause externally visible state changes.
The intended configuration model is environment-first:
export INVOICE_NINJA_BASE_URL="https://invoicing.co"
export INVOICE_NINJA_API_TOKEN="..."INVOICE_NINJA_BASE_URL is optional and defaults to https://invoicing.co.
Tokens must never be printed by default, and human-facing output should have a
matching JSON mode before it ships.
For demo smoke tests:
export INVOICE_NINJA_BASE_URL="https://demo.invoiceninja.com"
export INVOICE_NINJA_API_TOKEN="TOKEN"Read-only live smoke tests should use the public demo endpoint above by default.
Write support is implemented with --dry-run and --yes guardrails, but live
write smoke tests must be explicit and should target the public demo API.
Purges, refunds, merges, imports, scheduler, and admin utility endpoints should
only be exercised when a dedicated smoke helper creates and cleans up its own
demo data. Production or personal accounts should only be used for intentional
checks.
This repo pins Rust in rust-toolchain.toml and exposes the same toolchain
through the Nix flake.
cargo fmt --all -- --check
cargo check
cargo clippy -- -D warnings
cargo test
cargo build --release
nix flake checkWith Nix:
nix develop
nix build
nix run . -- --helpInside nix develop, the devshell menu exposes helper commands:
build cargo build (debug)
build-release cargo build --release
check cargo check
clippy cargo clippy -- -D warnings
fmt cargo fmt
fmt-check cargo fmt --all -- --check
run-tests cargo test
ci-local run the Rust-side CI sequence
code-health check Rust source files against module size budgets
coverage cargo llvm-cov summary, or --html for a report
koban cargo run -p koban-cli -- ...
koban-help show koban help
smoke-statics safe live GET /api/v1/statics smoke test
smoke-invoice-write-demo explicit demo-only invoice create/update/delete smoke test
smoke-all-demo explicit demo-only smoke test for every implemented command family
docs-dev start the VitePress dev server for the docs site
docs-build build the docs site (static output in website/.vitepress/dist)
docs-preview preview the built documentation site
docs-fmt format documentation with prettier
docs-fmt-check check documentation formatting (matches CI)
The documentation website lives in website/ (VitePress) and is published to
GitHub Pages at https://jamesbrink.online/koban/ by
.github/workflows/pages.yml on pushes to main that touch website/.
To keep this repo safe for AI agents, the devshell forces koban to the public
demo endpoint by default: it exports the demo INVOICE_NINJA_BASE_URL /
INVOICE_NINJA_API_TOKEN (overriding any .env or inherited values) and points
KOBAN_CONFIG_DIR at a gitignored repo-local .koban/, so a stored
koban auth login credential is never resolved here. An agent loading the koban
skill can only reach the demo account.
To use live credentials for an intentional check, enter the shell with
KOBAN_ALLOW_LIVE=1:
KOBAN_ALLOW_LIVE=1 nix developUnder KOBAN_ALLOW_LIVE=1, INVOICE_NINJA_API_TOKEN and
INVOICE_NINJA_BASE_URL are loaded from a local gitignored .env file when not
already set in the shell, and the stored credential is reachable:
INVOICE_NINJA_API_TOKEN=TOKEN
INVOICE_NINJA_BASE_URL=https://demo.invoiceninja.comThe demo write smoke helpers are intentionally opt-in and hard-code the public demo API internally so they cannot inherit a production or personal endpoint:
KOBAN_LIVE_WRITE_SMOKE=1 smoke-invoice-write-demo
KOBAN_LIVE_WRITE_SMOKE=1 smoke-all-demosmoke-all-demo live-reads every resource that the public demo API exposes,
allows documented demo-only 404s for unsupported reference pages, dry-runs every
expanded resource write family, and performs a create/update/upload/action/
download/bulk/delete invoice lifecycle with cleanup.
The flake exports packages.default, packages.koban, apps.default,
apps.koban, checks.koban, and a development shell for Linux and Darwin on
both x86_64 and aarch64.
Releases are managed by release-plz across the
workspace. The koban library and koban-cli binary crates version
independently — each bumps only when its own code changes. release-plz opens the
release PR, tags the release, and publishes both crates to crates.io (library
first, then the CLI) using CARGO_REGISTRY_TOKEN. When a CLI release is tagged,
CI builds unsigned CLI tarballs for macOS and Linux and uploads them with
SHA256SUMS. The CLI release keeps the prefix-free vX.Y.Z tag (the library
uses koban-v*) that install.sh and koban update rely on.
The nightly workflow builds the current main branch into a rolling
nightly prerelease. It uses a nightly-staging release while compiling so
the previous nightly stays available to updater clients until the new assets
are ready.
The install.sh script is the supported curl | sh path for direct installs:
curl -fsSL https://raw.githubusercontent.com/jamesbrink/koban/main/install.sh | sh
curl -fsSL https://raw.githubusercontent.com/jamesbrink/koban/main/install.sh | KOBAN_VERSION=v0.2.0 sh
curl -fsSL https://raw.githubusercontent.com/jamesbrink/koban/main/install.sh | KOBAN_VERSION=nightly sh
curl -fsSL https://raw.githubusercontent.com/jamesbrink/koban/main/install.sh | KOBAN_INSTALL_DIR=/usr/local/bin shkoban update downloads those GitHub release tarballs and verifies checksums
for direct installs. Package-manager installs are left alone and get an upgrade
recipe instead:
koban update --check
koban update --nightly --check
koban update
koban update --tag v0.2.0
koban update --nightlyThe working notes in docs/invoice-ninja-api.md are grounded in the current Invoice Ninja documentation and should be refreshed before adding new networked command groups.