Skip to main content

Unofficial Python client for the OpenCollective GraphQL API

Project description

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

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

opencollective-0.2.1.tar.gz (98.1 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.1-py3-none-any.whl (17.1 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: opencollective-0.2.1.tar.gz
  • Upload date:
  • Size: 98.1 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.1.tar.gz
Algorithm Hash digest
SHA256 703e651b8b7979e15b30ea40767d19825d3e85e42d3eb3af19a968a973d477dc
MD5 9e47569a2c982eb16c2977cdab28dc08
BLAKE2b-256 ab20b1a32686eb08dee8b830dd5bef33a1822ad1ff2723de36ed18c11eb2f404

See more details on using hashes here.

File details

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

File metadata

  • Download URL: opencollective-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 17.1 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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 e0a38942d6746b46e1745f5375d2e7a2e13ba0228a509a5de932e9fb8a0a9f56
MD5 bfc747e0ba3d302995583007a5b4aa2d
BLAKE2b-256 8c4b0abcd0816af7d30cdee4fa934d9ba3b94d41663ffb1dafc49abbf88693ff

See more details on using hashes here.

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