Python client for the Fizzy API.
pip install fizzy-api-clientfromfizzyimportFizzyClientclient=FizzyClient(
token="your-api-token",
account_slug="your-account-slug"
)
# List boardsboards=client.boards.list()
# Create a cardcard=client.cards.create(
board_id="board-123",
title="My new card"
)
# List cards with filteringcards=client.cards.list(
board_id="board-123",
tag_ids=["tag-1", "tag-2"],
status="open"
)The recommended authentication method for scripts and integrations:
fromfizzyimportFizzyClientclient=FizzyClient(
token="your-personal-access-token",
account_slug="your-account-slug"
)For native applications that need user authentication:
fromfizzyimportFizzyClientfromfizzy.authimportrequest_magic_link, submit_magic_code# Request a magic linkrequest_magic_link("user@example.com")
# User receives email with 6-character codesession_token=submit_magic_code("ABC123")
# Use the session tokenclient=FizzyClient(
session_token=session_token,
account_slug="your-account-slug"
)# Get authenticated user info (list of accounts)identity=client.identity.get()
foraccountinidentity.accounts:
print(f"Account: {account.name} ({account.id})")
print(f" User: {account.user.name} - Role: {account.user.role}")# List all boardsboards=client.boards.list()
# Get a specific boardboard=client.boards.get("board-id")
# Create a boardboard=client.boards.create(
name="My Board",
public_description="<p>Optional rich text description</p>"
)
# Update a boardboard=client.boards.update("board-id", name="New Name")
# Delete a boardclient.boards.delete("board-id")# List cards with optional filterscards=client.cards.list(
board_id="board-123", # Optional: filter by boardcolumn_id="col-456", # Optional: filter by columntag_ids=["tag-1"], # Optional: filter by tagsassignee_ids=["user-1"], # Optional: filter by assigneesstatus="open"# Optional: "open", "closed", "deferred"
)
# Get a specific card by numbercard=client.cards.get(42) # Cards are accessed by number, not ID# Create a cardcard=client.cards.create(
board_id="board-123",
title="New Card",
description="<p>Rich text description</p>"# HTML supported
)
# Create a card with header imagecard=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 cardcard=client.cards.update(42, title="Updated Title")
# Update card with new header imagecard=client.cards.update(42, image="/path/to/new-image.png")
# Delete card header imageclient.cards.delete_image(42)
# Delete a cardclient.cards.delete(42)
# Card operationsclient.cards.close(42) # Close the cardclient.cards.reopen(42) # Reopen a closed cardclient.cards.postpone(42) # Move to "not now"client.cards.triage(42, column_id="col-123") # Move to a columnclient.cards.untriage(42) # Remove from triageclient.cards.toggle_tag(42, tag_title="Bug") # Toggle a tag on/offclient.cards.toggle_assignment(42, assignee_id="user-123") # Toggle assignmentclient.cards.watch(42) # Start watchingclient.cards.unwatch(42) # Stop watchingclient.cards.gild(42) # Make a "golden ticket"client.cards.ungild(42) # Remove golden status# List comments on a cardcomments=client.comments.list(42) # card_number# Get a specific commentcomment=client.comments.get(42, "comment-id")
# Create a commentcomment=client.comments.create(
42, # card_numberbody="<p>Hello, world!</p>"# HTML supported
)
# Update a commentcomment=client.comments.update(42, "comment-id", body="<p>Updated</p>")
# Delete a commentclient.comments.delete(42, "comment-id")# List reactions on a commentreactions=client.reactions.list(42, "comment-id")
# Add a reactionreaction=client.reactions.create(42, "comment-id", content="thumbs_up")
# Remove a reactionclient.reactions.delete(42, "comment-id", "reaction-id")# List steps (retrieved from the card)steps=client.steps.list(42) # card_number# Get a specific stepstep=client.steps.get(42, "step-id")
# Create a stepstep=client.steps.create(42, content="Do this thing")
# Update a step (mark complete)client.steps.update(42, "step-id", completed=True)
# Delete a stepclient.steps.delete(42, "step-id")# List columns on a boardcolumns=client.columns.list("board-id")
# Get a specific columncolumn=client.columns.get("board-id", "column-id")
# Create a columncolumn=client.columns.create("board-id", name="In Progress")
# Update a columncolumn=client.columns.update("board-id", "column-id", name="Done")
# Delete a columnclient.columns.delete("board-id", "column-id")# List users in accountusers=client.users.list()
# Get a specific useruser=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 userclient.users.delete("user-id")# List all tags in accounttags=client.tags.list()# List notificationsnotifications=client.notifications.list()
# Filter by read statusunread=client.notifications.list(read=False)
# Mark as readclient.notifications.mark_read("notification-id")
# Mark as unreadclient.notifications.mark_unread("notification-id")
# Bulk mark as readclient.notifications.bulk_mark_read(["notif-1", "notif-2", "notif-3"])For embedding images in card descriptions or comments:
importhashlibimportbase64# Calculate MD5 checksumwithopen("image.png", "rb") asf:
content=f.read()
checksum=base64.b64encode(hashlib.md5(content).digest()).decode()
# Create a direct uploadupload=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 headersimporthttpxhttpx.put(
upload.upload_url,
content=content,
headers=upload.upload_headers
)
# Build an ActionText attachment tagfromfizzyimportDirectUploadtag=DirectUpload.build_attachment_tag(upload.signed_id)
# Use in card description or commentclient.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 iteratorforcardinclient.cards.list_all(board_id="board-123"):
print(card.title)
# Manual paginationpage=client.cards.list_paginated(board_id="board-123")
whilepage.has_next:
forcardinpage.items:
print(card.title)
page=page.next_page()fromfizzyimportAsyncFizzyClientimportasyncioasyncdefmain():
asyncwithAsyncFizzyClient(
token="your-token",
account_slug="your-account-slug"
) asclient:
# All methods are asyncboards=awaitclient.boards.list()
# Async iterationasyncforcardinclient.cards.list_all():
print(card.title)
asyncio.run(main())fromfizzy.exceptionsimport (
FizzyError, # Base exceptionAuthenticationError, # 401ForbiddenError, # 403NotFoundError, # 404BadRequestError, # 400RateLimitError, # 429ServerError, # 5xx
)
try:
card=client.cards.get(99999)
exceptNotFoundErrorase:
print(f"Card not found: {e.message}")
exceptBadRequestErrorase:
print(f"Bad request: {e.message}")
exceptFizzyErrorase:
print(f"API error: {e.status_code} - {e.message}")ETag caching is enabled by default for GET requests:
# First request stores the ETagcard=client.cards.get(123)
# Subsequent requests use If-None-Match header# Returns cached response if server returns 304card=client.cards.get(123)
# Disable cachingclient=FizzyClient(
token="...",
account_slug="...",
cache=False
)client=FizzyClient(
token="your-token",
account_slug="your-account-slug",
base_url="https://app.fizzy.do", # Defaultcache=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.