Fizzy API Client
Python client for the Fizzy API.
Installation
pip install fizzy-api-client
Quick Start
from 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"
)
Authentication
Personal Access Token
The recommended authentication method for scripts and integrations:
from fizzy import FizzyClient
client = FizzyClient(
token="your-personal-access-token",
account_slug="your-account-slug"
)
Magic Link Session
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"
)
Resources
Identity
# 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}")
Boards
# 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")
Cards
# 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
Comments
# 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")
Reactions
# 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")
Steps (Checklist Items)
# 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")
Columns
# 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")
Users
# 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")
Tags
# List all tags in account
tags = client.tags.list()
Notifications
# 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"])
File Uploads (Direct Uploads for Rich Text)
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>")
Pagination
# 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()
Async Usage
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())
Error Handling
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}")
Caching
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
)
Configuration
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
)
Development
# 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
Contributing
- 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
License
MIT License - see the LICENSE file for details.
Fizzy is a product and trademark of 37signals.
Metadata
Release files for fizzy-api-client 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| fizzy_api_client-1.0.0.tar.gz | 22.7 kB | With provenance | Details for fizzy_api_client-1.0.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| fizzy_api_client-1.0.0-py3-none-any.whl | Python 3 | none | any | Details for fizzy_api_client-1.0.0-py3-none-any.whl |
Total release size: 58.1 kB
Release files / fizzy_api_client-1.0.0.tar.gz
| Download URL | fizzy_api_client-1.0.0.tar.gz |
|---|---|
| Size | 22.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
cb4f33de4684a51679a5dbabd603b1242af98bdd216bb5f861d2c4581d93550b
|
|
BLAKE2b-256 checksum How to use checksums |
48e78ab4aee55a60d2089598c2406754ce60219688832ecb87d228432c6ffa6f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jan 2, 2026.
Transparency logRelease files / fizzy_api_client-1.0.0-py3-none-any.whl
| Download URL | fizzy_api_client-1.0.0-py3-none-any.whl |
|---|---|
| Size | 35.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
347a3c356b160e561406fc06d46d97a347dfd359b77ba993e76511396a019f35
|
|
BLAKE2b-256 checksum How to use checksums |
456d945a1e56aff364e1db9dbd79df752690e852d690ac2e1fff6deb714fca7d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jan 2, 2026.
Transparency logSecurity for fizzy-api-client 1.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for fizzy_api_client-1.0.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jan 2, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.