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 --assignee me --include-past           # current + previously assigned
openproject-cli wp list --project my-project --status "In progress"
openproject-cli wp query 532                                    # run a saved query by id
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

--assignee (and time --user) accept me, a numeric id, or a name. A name is matched case-insensitively: an exact full-name match wins, otherwise every whitespace-separated token must occur in the name (so "Ann Lee" matches "Ann Marie Lee"). Multiple matches list the candidates (id: name) so you can retry with a more specific name or the id.

wp query <id> runs a saved OpenProject query (the one identified by query_id in the query's URL) and lists its work packages like wp list; --offset/--limit page the results. Use api GET queries/<id> for the full query definition.

wp list --include-past (requires --assignee) also returns tasks the user was assigned to earlier, not just currently. OpenProject has no "was assigned" filter, so the ids ever seen assigned are accumulated in a local state file (~/.local/state/openproject-cli/assignee-history.json, keyed by host + user; override with OPENPROJECT_STATE). History therefore only grows from first use.

Notifications

openproject-cli notification list          # in-app notifications, newest first
openproject-cli notification read 5        # mark one as read
openproject-cli notification unread 5      # mark one as unread

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
OPENPROJECT_DEBUG re-raise an unexpected error as a full traceback (otherwise a one-line summary is printed)

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 (each sleep is capped so a large --retries or a far-future Retry-After cannot hang the CLI). Each retry prints a one-line warning to stderr. 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), warns on stderr when the base URL uses plaintext http://, and does not follow HTTP redirects (a redirect would otherwise replay the token to its target). A scheme-less base URL is treated as https://.

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.3.0.tar.gz (51.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.3.0-py3-none-any.whl (47.1 kB view details)

Uploaded Python 3

File details

Details for the file openproject_cli-0.3.0.tar.gz.

File metadata

  • Download URL: openproject_cli-0.3.0.tar.gz
  • Upload date:
  • Size: 51.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.3.0.tar.gz
Algorithm Hash digest
SHA256 a0e82ce16618d11783d9e3e743008b79330befac28cd90b73babf16448a43295
MD5 effe1f1c94a932a5f97b6ffbb8daa1df
BLAKE2b-256 462b27fd2dde75bff80efd68eede51fa1948aa54e3091965019c01a8c113e013

See more details on using hashes here.

Provenance

The following attestation bundles were made for openproject_cli-0.3.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.3.0-py3-none-any.whl.

File metadata

File hashes

Hashes for openproject_cli-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 af1148350e1ce5abdc3ae5d983f289ec66768d96e30f8a5ada401437a7fa8445
MD5 22796652373126abf783689c449c5c0e
BLAKE2b-256 ca9bc8b46aaadad0ec181abbe2ff618ab102267d434b349805e38679784f629a

See more details on using hashes here.

Provenance

The following attestation bundles were made for openproject_cli-0.3.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