Skip to main content

opencollective-py

A Python client for the OpenCollective GraphQL API.

Installation

# Basic installation
pip install git+https://github.com/MaxGhenis/opencollective-py.git

# With PDF conversion support (for HTML receipts)
pip install "opencollective[pdf] @ git+https://github.com/MaxGhenis/opencollective-py.git"

Quick start

Authentication

First, create an OAuth2 application at https://opencollective.com/applications.

from opencollective import OAuth2Handler, OpenCollectiveClient

# Set up OAuth2 handler
auth = OAuth2Handler(
    client_id="your_client_id",
    client_secret="your_client_secret",
    token_file="~/.config/opencollective/token.json"  # Optional: persist token
)

# Get authorization URL (redirect user here)
auth_url = auth.get_authorization_url(scope="expenses")

# After user authorizes, exchange code for token
token_data = auth.exchange_code(authorization_code)

# Create client with access token
client = OpenCollectiveClient(access_token=token_data["access_token"])

Submitting expenses

Reimbursement vs invoice: when to use which

Type Use when... Receipt required?
Reimbursement You paid out-of-pocket and need to be paid back Yes
Invoice You're billing for services/work rendered No (optional)

Examples:

  • Membership dues you paid → Reimbursement (you have a receipt)
  • Conference registration → Reimbursement (you have a receipt)
  • Monthly consulting fee → Invoice (you're billing for work)
  • Software development work → Invoice (you're billing for work)

Submit a reimbursement (recommended)

Use this when you paid for something and need to be reimbursed:

expense = client.submit_reimbursement(
    collective_slug="policyengine",
    description="NASI Membership Dues 2026",
    amount_cents=32500,  # $325.00
    receipt_file="/path/to/receipt.pdf",  # or .png, .jpg, .html
    tags=["membership", "professional development"]
)
print(f"Created: https://opencollective.com/policyengine/expenses/{expense['legacyId']}")

Features:

  • Auto-detects your account from the OAuth token
  • Auto-selects your first payout method
  • Converts HTML receipts to PDF automatically (requires opencollective[pdf])
  • Uploads receipt and attaches it to the expense

Submit an invoice

Use this when billing for services (no receipt needed):

expense = client.submit_invoice(
    collective_slug="policyengine",
    description="January 2026 Consulting",
    amount_cents=500000,  # $5,000.00
    tags=["consulting"]
)

Low-level expense creation

For more control, use the lower-level create_expense() method:

# First upload receipt if needed
file_info = client.upload_file("/path/to/receipt.pdf")

# Create expense with full control
expense = client.create_expense(
    collective_slug="policyengine",
    payee_slug="max-ghenis",
    description="Cloud services - January 2026",
    amount_cents=10000,
    expense_type="RECEIPT",  # or "INVOICE"
    payout_method_id="your-payout-method-id",
    attachment_urls=[file_info["url"]],
    tags=["cloud", "infrastructure"],
)

Managing expenses

# Get your account info
me = client.get_me()
print(f"Logged in as: {me['name']} (@{me['slug']})")

# Get your payout methods
methods = client.get_payout_methods(me['slug'])
for m in methods:
    print(f"  {m['type']}: {m['id']}")

# Get recent expenses
expenses = client.get_expenses("policyengine", limit=50)
print(f"Found {expenses['totalCount']} expenses")

# Get pending expenses only
pending = client.get_pending_expenses("policyengine")

# Approve an expense (requires admin permissions)
client.approve_expense(expense_id="abc123")

# Reject an expense with a message
client.reject_expense(expense_id="xyz789", message="Missing receipt")

# Delete your own draft/pending expense
client.delete_expense(expense_id="abc123")

File uploads

Upload files for expense attachments:

# Upload from file path
file_info = client.upload_file(
    "/path/to/receipt.pdf",
    kind="EXPENSE_ITEM"  # or EXPENSE_INVOICE, EXPENSE_ATTACHED_FILE
)
print(f"Uploaded to: {file_info['url']}")

# Upload from file-like object
from io import BytesIO
file_obj = BytesIO(pdf_bytes)
file_info = client.upload_file(file_obj, filename="receipt.pdf")

Supported formats: PNG, JPEG, GIF, WebP, PDF, CSV

Get collective info

collective = client.get_collective("policyengine")
print(f"Name: {collective['name']}")
print(f"Currency: {collective['currency']}")

CLI

The package includes a command-line interface for common operations.

Setup

# Authenticate (get credentials at https://opencollective.com/applications)
oc auth

Commands

# Submit a reimbursement
oc reimbursement "NASI Dues 2026" 325.00 receipt.pdf -c policyengine -t membership

# Submit an invoice
oc invoice "January Consulting" 5000.00 -c policyengine

# List expenses
oc expenses -c policyengine --pending
oc expenses -c policyengine --mine

# Delete an expense
oc delete abc123-def456

# Show current user
oc me

MCP server (for Claude Code)

The package includes an MCP server so Claude Code can submit expenses directly.

Setup

Add to your Claude Code MCP config (~/.claude/mcp.json):

{
  "mcpServers": {
    "opencollective": {
      "command": "python",
      "args": ["-m", "opencollective.mcp_server"]
    }
  }
}

Make sure you've authenticated first with oc auth.

Available tools

  • submit_reimbursement - Submit a reimbursement with receipt
  • submit_invoice - Submit an invoice for services
  • list_expenses - List expenses for a collective
  • delete_expense - Delete a draft/pending expense
  • get_me - Get current user info
  • get_collective - Get collective info

Example usage in Claude Code

"Submit my NASI receipt at /tmp/nasi_receipt.html as a $325 reimbursement to policyengine with tags membership and professional-development"

Development

# Clone the repository
git clone https://github.com/MaxGhenis/opencollective-py.git
cd opencollective-py

# Install development dependencies
pip install -e ".[dev]"

# Run tests
pytest

# Format code
black .
ruff check --fix .

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

opencollective-0.2.0.tar.gz (98.0 kB view details)

Uploaded Source

Built Distribution

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

opencollective-0.2.0-py3-none-any.whl (17.0 kB view details)

Uploaded Python 3

File details

Details for the file opencollective-0.2.0.tar.gz.

File metadata

  • Download URL: opencollective-0.2.0.tar.gz
  • Upload date:
  • Size: 98.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.11

File hashes

Hashes for opencollective-0.2.0.tar.gz
Algorithm Hash digest
SHA256 c408432d46c8eae3bdf4969567f1be13470ab32231e92baadbb3104cef46c4fe
MD5 004e418e00a02281390502308ace831e
BLAKE2b-256 1e61967ab6e914ee257210263a186e3625b2ae88864a1d9f8874fcd58c84f492

See more details on using hashes here.

File details

Details for the file opencollective-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: opencollective-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 17.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.11

File hashes

Hashes for opencollective-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4d6e9e544c045df430715b90699fadbc1640023ab85a142931dc34c24e97a247
MD5 f1e14bd1466306adb7ac0d374ee81035
BLAKE2b-256 4b46f369ca20dd83aa8d0079fbae20a39584ff5137925373704c41a95311edf3

See more details on using hashes here.

Release history Release notifications | RSS feed

0.2.1

2 files

This release

0.2.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page