The official Python client for the ChangeCrab API. Manage your changelogs and release notes programmatically.
For complete API documentation, see the ChangeCrab API Overview.
pip install changecrabOr install from source:
git clone https://github.com/changecrab/python_sdk.git
cd python_sdk
pip install .from changecrab import ChangeCrab
# Initialize the client with your API key
client = ChangeCrab(api_key="your_api_key_here")
# List all your changelogs
changelogs = client.list_changelogs()
for changelog in changelogs:
print(f"{changelog.name} ({changelog.access_id})")
# Create a new post
post = client.create_post(
changelog_id="abc123def4",
summary="🚀 New Feature: Dark Mode",
markdown="We've added dark mode support! Toggle it in your settings.",
team=1,
public=True
)
print(f"Created post #{post.id}")Get your API key from your ChangeCrab account settings. All API requests require authentication.
For detailed authentication information, see the ChangeCrab API Overview.
from changecrab import ChangeCrab
client = ChangeCrab(api_key="your_api_key_here")You can also configure the client with custom settings:
client = ChangeCrab(
api_key="your_api_key_here",
base_url="https://changecrab.com/api", # Custom API URL (optional)
timeout=60, # Request timeout in seconds (default: 30)
max_retries=3, # Max retry attempts for transient errors (default: 3)
retry_delay=1.0, # Initial retry delay in seconds (default: 1.0, exponential backoff)
)The SDK automatically retries requests on transient errors (timeouts, connection errors) with exponential backoff:
- Default: 3 retries with delays of 1s, 2s, 4s
- Only retries on
TimeoutandConnectionErrorexceptions - Non-retryable errors (authentication, validation, etc.) are raised immediately
Retrieve all changelogs accessible to your account:
# Get all changelogs
changelogs = client.list_changelogs()
# Filter by team ID
team_changelogs = client.list_changelogs(team_id=123)Retrieve a specific changelog by its access ID:
changelog = client.get_changelog("abc123def4")
print(f"Name: {changelog.name}")
print(f"Subdomain: {changelog.subdomain}")
print(f"Created: {changelog.created_at}")changelog = client.create_changelog(
name="My Product Changelog",
team=1,
subdomain="myproduct",
accent="#007bff",
auto_notify=True,
show_social=True
)
print(f"Created: {changelog.access_id}")All available options:
| Parameter | Type | Description |
|---|---|---|
name |
str | Required. Name of the changelog |
team |
int | Required. Team ID that owns the changelog |
subdomain |
str | Subdomain (e.g., "myapp" for myapp.changecrab.com) |
domain |
str | Custom domain |
accent |
str | Accent color in hex format (e.g., "#E33597") |
private |
bool | Whether the changelog is private |
auto_notify |
bool | Auto-notify subscribers of new posts |
hide_subscriber |
bool | Hide subscriber count |
show_brand |
bool | Show ChangeCrab branding |
subscribe_active |
bool | Enable subscriptions |
return_url |
str | Return URL for your site |
slack_url |
str | Slack webhook URL |
teams_url |
str | Microsoft Teams webhook URL |
discord_webhook |
str | Discord webhook URL |
site_url |
str | Your main website URL |
twitter |
str | Twitter handle |
ga_tracking |
str | Google Analytics tracking ID |
logo |
str | Logo URL |
favicon |
str | Favicon URL |
extra_css |
str | Custom CSS |
show_creator |
bool | Show post creator names |
show_filters |
bool | Show category filters |
suggestion |
bool | Enable suggestions |
guest_voting |
bool | Allow guest voting |
guest_commenting |
bool | Allow guest comments |
guest_creation |
bool | Allow guest suggestions |
downvotes |
bool | Enable downvoting |
changelog = client.update_changelog(
"abc123def4",
name="Updated Changelog Name",
accent="#ff5722",
auto_notify=False
)client.delete_changelog("abc123def4")Categories help organize posts within a changelog. Retrieve them to get category IDs for tagging posts.
categories = client.list_categories("abc123def4")
for cat in categories:
print(f"{cat.title} (ID: {cat.id}, Color: {cat.colour})")posts = client.list_posts("abc123def4")
for post in posts:
status = "Draft" if post.draft else "Published"
print(f"[{status}] {post.summary}")post = client.create_post(
changelog_id="abc123def4",
summary="Bug Fix: Login Issue Resolved",
markdown="""
## What's Fixed
We've resolved an issue where some users couldn't log in using social authentication.
### Details
- Fixed OAuth callback handling
- Improved error messages for failed logins
- Added retry logic for transient failures
Thanks to everyone who reported this issue!
""",
team=1,
public=True,
announced=True, # Notify subscribers
categories=[1, 2] # Category IDs from list_categories()
)All available options:
| Parameter | Type | Description |
|---|---|---|
changelog_id |
str | Required. Changelog access ID |
summary |
str | Required. Title/summary (max 2555 chars) |
markdown |
str | Required. Content in Markdown format |
team |
int | Required. Team ID |
public |
bool | Public visibility (default: True) |
announced |
bool | Notify subscribers (default: False) |
draft |
bool | Save as draft (default: False) |
link |
str | Related URL |
record |
str | Record identifier (single char) |
categories |
list[int] | Category IDs to tag the post |
post = client.update_post(
changelog_id="abc123def4",
post_id=123,
markdown="Updated content here with more details.",
team=1,
summary="Updated Title",
announced=True, # Announce to subscribers
publish_date="2024-01-15",
categories=[1, 3]
)client.delete_post("abc123def4", post_id=123)The SDK provides specific exception classes for different error scenarios. All exceptions include status codes, request IDs (if available), and truncated response bodies for debugging.
from changecrab import (
ChangeCrab,
ChangeCrabError,
ApiError, # Alias for ChangeCrabError
AuthenticationError,
NotFoundError,
ValidationError,
RateLimitError,
ServerError,
)
client = ChangeCrab(api_key="your_api_key")
try:
post = client.create_post(
changelog_id="abc123def4",
summary="", # Empty summary will fail validation
markdown="Content here",
team=1
)
except ValidationError as e:
# Handle validation errors with field-level details
print(f"Validation failed: {e.message}")
print(f"Status: {e.status_code}")
if e.request_id:
print(f"Request ID: {e.request_id}")
# Access field-level validation errors
for field, messages in e.errors.items():
print(f" {field}: {', '.join(messages)}")
# Response body is automatically truncated and sanitized
# (no API keys or sensitive data leaked)
if e.response_data:
print(f"Response: {e.response_data}")
except AuthenticationError as e:
# Invalid or missing API key
print(f"Auth failed: {e.message} (status: {e.status_code})")
if e.request_id:
print(f"Request ID: {e.request_id}")
except NotFoundError as e:
# Resource doesn't exist
print(f"Not found: {e.message}")
except RateLimitError as e:
# Rate limit exceeded - SDK automatically retries with backoff
# This exception is only raised if all retries are exhausted
print(f"Rate limited: {e.message}")
print("The SDK automatically retried with exponential backoff")
except ServerError as e:
# Server error - SDK automatically retries transient 5xx errors
print(f"Server error: {e.message} (status: {e.status_code})")
except (ChangeCrabError, ApiError) as e:
# Catch-all for any SDK error (ApiError is an alias)
print(f"Error: {e.message}")
print(f"Status: {e.status_code}")
if e.request_id:
print(f"Request ID: {e.request_id}")All exceptions provide the following properties:
message: Human-readable error messagestatus_code: HTTP status code (if available)request_id: Request ID from API response headers (if available)response_data: Sanitized and truncated response body (max 500 chars, API keys redacted)errors: Field-level validation errors (ValidationError only)
The SDK automatically retries on:
- Network errors: Timeouts and connection errors (exponential backoff)
- HTTP 429 (Rate Limit): Respects
Retry-Afterheader if present - HTTP 5xx (Server Errors): 500, 502, 503, 504 (exponential backoff)
Retries are capped at max_retries (default: 3). Exceptions are only raised after all retries are exhausted.
ChangeCrabError (base) / ApiError (alias)
├── AuthenticationError # 401, 403
├── NotFoundError # 404
├── ValidationError # 422 (includes field errors)
├── RateLimitError # 429 (after retries exhausted)
└── ServerError # 5xx (after retries exhausted)
The SDK returns typed dataclass objects for easy access to resource properties:
changelog = client.get_changelog("abc123def4")
# Access properties directly
print(changelog.id) # Numeric ID
print(changelog.access_id) # String ID used in API URLs
print(changelog.name) # Display name
print(changelog.team) # Team ID
print(changelog.subdomain) # Subdomain
print(changelog.accent) # Accent color
print(changelog.created_at) # datetime object
# Convert to dictionary
data = changelog.to_dict()post = client.list_posts("abc123def4")[0]
print(post.id) # Numeric ID
print(post.summary) # Title
print(post.markdown) # Content
print(post.public) # bool
print(post.announced) # bool
print(post.draft) # bool
print(post.categories) # List of PostCategory objects
print(post.created_at) # datetime objectcategory = client.list_categories("abc123def4")[0]
print(category.id) # Numeric ID
print(category.title) # Display name
print(category.colour) # Hex color codeYou can set your API key as an environment variable:
export CHANGECRAB_API_KEY="your_api_key_here"Then initialize the client:
import os
from changecrab import ChangeCrab
client = ChangeCrab(api_key=os.environ["CHANGECRAB_API_KEY"])This SDK includes full type annotations and a py.typed marker for static type checkers like mypy:
from typing import Optional
from changecrab import ChangeCrab, Changelog, Post
def get_latest_post(client: ChangeCrab, changelog_id: str) -> Optional[Post]:
posts = client.list_posts(changelog_id)
return posts[0] if posts else NoneThe SDK includes a comprehensive test suite. To run tests:
# Install dev dependencies
pip install -e ".[dev]"
# Run all tests
pytest
# Run with coverage
pytest --cov=changecrab --cov-report=term
# Run specific test file
pytest tests/test_client.pyThe test suite includes:
- Unit tests for all client methods (with mocked API responses)
- Model tests for data parsing and conversion
- Exception handling tests
- Error scenario tests
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/your-feature) - Install dev dependencies (
pip install -e ".[dev]") - Make your changes
- Run tests (
pytest) - Run linting (
ruff check .) - Run type checking (
mypy changecrab) - Commit your changes
- Push to the branch
- Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- API Documentation: ChangeCrab API Overview
- Issues: GitHub Issues
- Email: support@changecrab.com