Skip to main content

A non-interactive command-line client for the OpenProject API

Project description

openproject-cli

CI PyPI Python versions License: MIT

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; --human for 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 api passthrough to any endpoint, modelled on gh api.
  • Detailed --help at 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

MIT

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

openproject_cli-0.1.0.tar.gz (43.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

openproject_cli-0.1.0-py3-none-any.whl (41.2 kB view details)

Uploaded Python 3

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

Hashes for openproject_cli-0.1.0.tar.gz
Algorithm Hash digest
SHA256 b7e338c1c9a1abf4ca17ad34556a9182123964ab40dd49a386e27b93c7e4a8d8
MD5 1cb63b970202b397555af1f302b860cb
BLAKE2b-256 7e90543d99d4bfc0f18a29b29bce5af9bc58d75be2930b6952824faea28b6db3

See more details on using hashes here.

Provenance

The following attestation bundles were made for openproject_cli-0.1.0.tar.gz:

Publisher: publish.yml on VitalyOstanin/openproject-cli

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for openproject_cli-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c051abb1923fe5e44d319736a17b9d2ee9bd478f4db6a5c94205588143d91319
MD5 a3928a4525d59eb085df1acc55cce08f
BLAKE2b-256 bb7f714567f4c695e42cbed425f00eb40781c10eecba045ed1d109ee854f28c0

See more details on using hashes here.

Provenance

The following attestation bundles were made for openproject_cli-0.1.0-py3-none-any.whl:

Publisher: publish.yml on VitalyOstanin/openproject-cli

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page