Python client for the Fizzy API.
pip install fizzy-api-clientfrom fizzy import FizzyClient
client = FizzyClient(
token="your-api-token",
account_slug="your-account-slug"
)
# List boards
boards = client.boards.list()
# Create a card
card = client.cards.create(
board_id="board-123",
title="My new card"
)
# List cards with filtering
cards = client.cards.list(
board_id="board-123",
tag_ids=["tag-1", "tag-2"],
status="open"
)The recommended authentication method for scripts and integrations:
from fizzy import FizzyClient
client = FizzyClient(
token="your-personal-access-token",
account_slug="your-account-slug"
)For native applications that need user authentication:
from fizzy import FizzyClient
from fizzy.auth import request_magic_link, submit_magic_code
# Request a magic link
request_magic_link("user@example.com")
# User receives email with 6-character code
session_token = submit_magic_code("ABC123")
# Use the session token
client = FizzyClient(
session_token=session_token,
account_slug="your-account-slug"
)# Get authenticated user info (list of accounts)
identity = client.identity.get()
for account in identity.accounts:
print(f"Account: {account.name} ({account.id})")
print(f" User: {account.user.name} - Role: {account.user.role}")# List all boards
boards = client.boards.list()
# Get a specific board
board = client.boards.get("board-id")
# Create a board
board = client.boards.create(
name="My Board",
public_description="<p>Optional rich text description</p>"
)
# Update a board
board = client.boards.update("board-id", name="New Name")
# Delete a board
client.boards.delete("board-id")# List cards with optional filters
cards = client.cards.list(
board_id="board-123", # Optional: filter by board
column_id="col-456", # Optional: filter by column
tag_ids=["tag-1"], # Optional: filter by tags
assignee_ids=["user-1"], # Optional: filter by assignees
status="open" # Optional: "open", "closed", "deferred"
)
# Get a specific card by number
card = client.cards.get(42) # Cards are accessed by number, not ID
# Create a card
card = client.cards.create(
board_id="board-123",
title="New Card",
description="<p>Rich text description</p>" # HTML supported
)
# Create a card with header image
card = client.cards.create(
board_id="board-123",
title="Card with Image",
image="/path/to/image.png" # File path or tuple (filename, file_obj, content_type)
)
# Update a card
card = client.cards.update(42, title="Updated Title")
# Update card with new header image
card = client.cards.update(42, image="/path/to/new-image.png")
# Delete card header image
client.cards.delete_image(42)
# Delete a card
client.cards.delete(42)
# Card operations
client.cards.close(42) # Close the card
client.cards.reopen(42) # Reopen a closed card
client.cards.postpone(42) # Move to "not now"
client.cards.triage(42, column_id="col-123") # Move to a column
client.cards.untriage(42) # Remove from triage
client.cards.toggle_tag(42, tag_title="Bug") # Toggle a tag on/off
client.cards.toggle_assignment(42, assignee_id="user-123") # Toggle assignment
client.cards.watch(42) # Start watching
client.cards.unwatch(42) # Stop watching
client.cards.gild(42) # Make a "golden ticket"
client.cards.ungild(42) # Remove golden status# List comments on a card
comments = client.comments.list(42) # card_number
# Get a specific comment
comment = client.comments.get(42, "comment-id")
# Create a comment
comment = client.comments.create(
42, # card_number
body="<p>Hello, world!</p>" # HTML supported
)
# Update a comment
comment = client.comments.update(42, "comment-id", body="<p>Updated</p>")
# Delete a comment
client.comments.delete(42, "comment-id")# List reactions on a comment
reactions = client.reactions.list(42, "comment-id")
# Add a reaction
reaction = client.reactions.create(42, "comment-id", content="thumbs_up")
# Remove a reaction
client.reactions.delete(42, "comment-id", "reaction-id")# List steps (retrieved from the card)
steps = client.steps.list(42) # card_number
# Get a specific step
step = client.steps.get(42, "step-id")
# Create a step
step = client.steps.create(42, content="Do this thing")
# Update a step (mark complete)
client.steps.update(42, "step-id", completed=True)
# Delete a step
client.steps.delete(42, "step-id")# List columns on a board
columns = client.columns.list("board-id")
# Get a specific column
column = client.columns.get("board-id", "column-id")
# Create a column
column = client.columns.create("board-id", name="In Progress")
# Update a column
column = client.columns.update("board-id", "column-id", name="Done")
# Delete a column
client.columns.delete("board-id", "column-id")# List users in account
users = client.users.list()
# Get a specific user
user = client.users.get("user-id")
# Update a user (name or avatar)
user = client.users.update("user-id", name="New Name")
user = client.users.update("user-id", avatar="/path/to/avatar.png")
# Deactivate a user
client.users.delete("user-id")# List all tags in account
tags = client.tags.list()# List notifications
notifications = client.notifications.list()
# Filter by read status
unread = client.notifications.list(read=False)
# Mark as read
client.notifications.mark_read("notification-id")
# Mark as unread
client.notifications.mark_unread("notification-id")
# Bulk mark as read
client.notifications.bulk_mark_read(["notif-1", "notif-2", "notif-3"])For embedding images in card descriptions or comments:
import hashlib
import base64
# Calculate MD5 checksum
with open("image.png", "rb") as f:
content = f.read()
checksum = base64.b64encode(hashlib.md5(content).digest()).decode()
# Create a direct upload
upload = client.uploads.create_direct_upload(
filename="image.png",
content_type="image/png",
byte_size=len(content),
checksum=checksum
)
# Upload to storage using the provided URL and headers
import httpx
httpx.put(
upload.upload_url,
content=content,
headers=upload.upload_headers
)
# Build an ActionText attachment tag
from fizzy import DirectUpload
tag = DirectUpload.build_attachment_tag(upload.signed_id)
# Use in card description or comment
client.cards.update(42, description=f"<p>Check this out: {tag}</p>")
client.comments.create(42, body=f"<p>Here's an image: {tag}</p>")# Auto-pagination iterator
for card in client.cards.list_all(board_id="board-123"):
print(card.title)
# Manual pagination
page = client.cards.list_paginated(board_id="board-123")
while page.has_next:
for card in page.items:
print(card.title)
page = page.next_page()from fizzy import AsyncFizzyClient
import asyncio
async def main():
async with AsyncFizzyClient(
token="your-token",
account_slug="your-account-slug"
) as client:
# All methods are async
boards = await client.boards.list()
# Async iteration
async for card in client.cards.list_all():
print(card.title)
asyncio.run(main())from fizzy.exceptions import (
FizzyError, # Base exception
AuthenticationError, # 401
ForbiddenError, # 403
NotFoundError, # 404
BadRequestError, # 400
RateLimitError, # 429
ServerError, # 5xx
)
try:
card = client.cards.get(99999)
except NotFoundError as e:
print(f"Card not found: {e.message}")
except BadRequestError as e:
print(f"Bad request: {e.message}")
except FizzyError as e:
print(f"API error: {e.status_code} - {e.message}")ETag caching is enabled by default for GET requests:
# First request stores the ETag
card = client.cards.get(123)
# Subsequent requests use If-None-Match header
# Returns cached response if server returns 304
card = client.cards.get(123)
# Disable caching
client = FizzyClient(
token="...",
account_slug="...",
cache=False
)client = FizzyClient(
token="your-token",
account_slug="your-account-slug",
base_url="https://app.fizzy.do", # Default
cache=True, # Enable ETag caching (default)
timeout=30.0, # Request timeout in seconds
)# Clone the repository
git clone https://github.com/robzolkos/fizzy-client-python.git
cd fizzy-client-python
# Install development dependencies
pip install -e ".[dev]"
# Run tests
pytest
# Run tests with coverage
pytest --cov=src/fizzy --cov-report=html
# Run linting
ruff check src tests
ruff format --check src tests
# Run type checking
mypy src- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
MIT License - see the LICENSE file for details.
Fizzy is a product and trademark of 37signals.