A non-interactive command-line client for the OpenProject API
Project description
openproject-cli
A non-interactive command-line client for the OpenProject
API v3, in the spirit of gh. It is designed to be
driven by scripts and AI agents: output is JSON by default, credentials come
from a config file, and any endpoint is reachable through a raw api
passthrough.
Contents
Features
- CRUDL for work packages, comments, attachments, relations and time entries.
- JSON output by default;
--humanfor a flat, dependency-free text rendering. - Server-side filtering (the API does the filtering, not the client), including
filtering work packages and time entries by assignee/user resolved from
me, a numeric id, or an exact name. - Streaming file transfers: downloads and uploads are never buffered whole in
memory. Downloads are atomic (temp file + rename), report a
sha256, and accept--max-bytes. - Custom field values with names resolved from the schema, and comment/activity author names resolved by id.
- Raw
apipassthrough to any endpoint, modelled ongh api. - Detailed
--helpat every level (global, per resource, per action). - Safe for scripts and agents: every command is non-interactive except
auth login, which prompts for the token only when one is not supplied.
Install
Requires Python 3.11+. Install with uv so the
openproject-cli command lands on your PATH:
uv tool install openproject-cli
# or from a local checkout:
uv tool install --from . openproject-cli
With pip:
pip install openproject-cli
Authentication
Like gh. The simplest path is interactive — auth login opens the OpenProject
API token page (My account → Access tokens → API) in a browser and prompts for
the token to paste:
openproject-cli auth login --url https://openproject.example.com
# opens .../my/access_token in a browser, then: "Paste your API token:" (hidden)
The base URL is saved to the config file, so --url only has to be given once;
afterwards auth login (and every other command) reuses it from the config or
$OPENPROJECT_URL. Use --no-browser to skip opening the browser.
For scripts, pass the token non-interactively instead:
openproject-cli auth login --url https://openproject.example.com --with-token < token.txt
# or pass it directly (less safe; ends up in shell history):
openproject-cli auth login --url https://openproject.example.com --token YOUR_TOKEN
openproject-cli auth status # show config and token source, verify against server
openproject-cli auth status --offline # same, but skip the server round-trip
openproject-cli auth token # print the resolved token
openproject-cli auth logout # remove the stored token
By default the token is stored in the system keyring (keyed by host URL);
the config file then holds only non-secret settings (including the base URL).
Pass --insecure-storage to write the token into the config file instead — the
tool also falls back to the file automatically when no keyring backend is
available (e.g. headless servers).
Credentials can also be supplied per command via --url/--token or the
OPENPROJECT_URL/OPENPROJECT_TOKEN environment variables. The token is
resolved in this order: --token flag, environment, keyring, config file.
OpenProject has no browser OAuth device flow comparable to GitHub's, so (unlike
gh) login does not perform an OAuth handshake — it opens the API token page and
takes the token you generate there.
Usage
Global options may be given before the resource or after the subcommand — a value repeated after the subcommand overrides the one before it:
openproject-cli [GLOBAL OPTIONS] <resource> <action> [options] [GLOBAL OPTIONS]
where GLOBAL OPTIONS are --url URL, --token TOKEN, --config PATH,
--timeout S, --retries N, --insecure and --human. For example, both
openproject-cli --human wp list and openproject-cli wp list --human work.
Work packages
openproject-cli wp list --assignee me --open
openproject-cli wp list --project my-project --status "In progress"
openproject-cli wp get 1234
openproject-cli wp create --project my-project --type Task --subject "Fix X"
openproject-cli wp update 1234 --status "In progress" --done-ratio 50
openproject-cli wp delete 1234
Comments
openproject-cli comment list --work-package 1234 --comments-only
openproject-cli comment create --work-package 1234 "Ready for review"
openproject-cli comment update 9876 "Edited comment"
The OpenProject API has no comment-deletion endpoint, so there is no
comment delete.
Attachments
openproject-cli attachment list --work-package 1234
openproject-cli attachment upload --work-package 1234 ./report.pdf
openproject-cli attachment download 9876 --output report.pdf # '-' for stdout
openproject-cli attachment delete 9876
When downloading to a file, the content is written via a temporary file and
atomically renamed on success, the sha256 is reported, and --max-bytes caps
the transfer. These apply to file output only; --output - streams straight to
stdout without the temp file, hash or size cap.
Relations
openproject-cli relation list --work-package 1234
openproject-cli relation create --work-package 1234 --to 5678 --type follows
openproject-cli relation delete 42
Time entries
openproject-cli time list --user me --since 2026-01-01
openproject-cli time create --work-package 1234 --hours 1.5 --comment "review"
openproject-cli time update 4242 --hours 2
openproject-cli time delete 4242
Raw API
openproject-cli api GET work_packages/1234
openproject-cli api GET work_packages -f pageSize=5
openproject-cli api POST time_entries --input body.json
Output format
JSON is printed to stdout by default; errors go to stderr and the process exits
non-zero. --human switches to a flat text rendering (mappings become
key: value lines, lists of objects become tab-separated rows). --raw on read
commands returns the unmodified API payload instead of the normalized subset.
Configuration
The config file lives at ~/.config/openproject-cli/config.yaml (override with
--config or OPENPROJECT_CONFIG; XDG_CONFIG_HOME is honoured). Values are
resolved in order: command-line flag, environment variable, config file.
url: https://openproject.example.com
timeout: 30
retries: 3
verify_ssl: true
# token: only present with --insecure-storage; otherwise it lives in the keyring
The file is written with 0600 permissions, and with keyring storage it holds
no secret.
Environment variables
| Variable | Equivalent flag | Meaning |
|---|---|---|
OPENPROJECT_URL |
--url |
base URL of the OpenProject instance |
OPENPROJECT_TOKEN |
--token |
API token |
OPENPROJECT_TIMEOUT |
--timeout |
request timeout in seconds (default 30) |
OPENPROJECT_RETRIES |
--retries |
retries for idempotent requests (default 3) |
OPENPROJECT_INSECURE |
--insecure |
disable TLS verification when truthy |
OPENPROJECT_CONFIG |
--config |
path to the config file |
Retries
Idempotent requests (GET/HEAD/OPTIONS/PUT/DELETE) are retried on a
transport error or a transient status (429 and 5xx), honouring a
Retry-After header and otherwise backing off exponentially. POST is never
retried, so a failed "create" cannot be duplicated. Set --retries 0 to disable.
Transport safety
The client refuses to send the API token to a host other than the configured
one (an absolute URL passed to api pointing elsewhere is rejected), and warns
on stderr when the base URL uses plaintext http://.
Development
uv venv && . .venv/bin/activate
uv pip install -e ".[dev]"
ruff check . && ruff format --check . && pyright && pytest
License
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file openproject_cli-0.1.0.tar.gz.
File metadata
- Download URL: openproject_cli-0.1.0.tar.gz
- Upload date:
- Size: 43.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b7e338c1c9a1abf4ca17ad34556a9182123964ab40dd49a386e27b93c7e4a8d8
|
|
| MD5 |
1cb63b970202b397555af1f302b860cb
|
|
| BLAKE2b-256 |
7e90543d99d4bfc0f18a29b29bce5af9bc58d75be2930b6952824faea28b6db3
|
Provenance
The following attestation bundles were made for openproject_cli-0.1.0.tar.gz:
Publisher:
publish.yml on VitalyOstanin/openproject-cli
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
openproject_cli-0.1.0.tar.gz -
Subject digest:
b7e338c1c9a1abf4ca17ad34556a9182123964ab40dd49a386e27b93c7e4a8d8 - Sigstore transparency entry: 2027665445
- Sigstore integration time:
-
Permalink:
VitalyOstanin/openproject-cli@540e1334af0f1c398c50221514073bbf6af4109b -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/VitalyOstanin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@540e1334af0f1c398c50221514073bbf6af4109b -
Trigger Event:
push
-
Statement type:
File details
Details for the file openproject_cli-0.1.0-py3-none-any.whl.
File metadata
- Download URL: openproject_cli-0.1.0-py3-none-any.whl
- Upload date:
- Size: 41.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c051abb1923fe5e44d319736a17b9d2ee9bd478f4db6a5c94205588143d91319
|
|
| MD5 |
a3928a4525d59eb085df1acc55cce08f
|
|
| BLAKE2b-256 |
bb7f714567f4c695e42cbed425f00eb40781c10eecba045ed1d109ee854f28c0
|
Provenance
The following attestation bundles were made for openproject_cli-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on VitalyOstanin/openproject-cli
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
openproject_cli-0.1.0-py3-none-any.whl -
Subject digest:
c051abb1923fe5e44d319736a17b9d2ee9bd478f4db6a5c94205588143d91319 - Sigstore transparency entry: 2027665734
- Sigstore integration time:
-
Permalink:
VitalyOstanin/openproject-cli@540e1334af0f1c398c50221514073bbf6af4109b -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/VitalyOstanin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@540e1334af0f1c398c50221514073bbf6af4109b -
Trigger Event:
push
-
Statement type: