polar-sh
Version, currently main branch1 version
- main branchlatestNov 3, 2025
github.com/Qard/polar-sh
Crystal language client for the Polar.sh API. Auto-generated from the official OpenAPI specification with custom Crystal enhancements.
Installation
# Add this to your shard.yml
dependencies:
polar-sh:
github: Qard/polar-sh
branch: mainmain is a branch, not a release, so this tracks it rather than pinning a version.
Then run:
shards installshard.yml
- Crystal
>= 1.17.1- License
- MIT
- Author
- Stephen Belanger
Dependencies
Development Dependencies
- ameba*github: crystal-ameba/amebadev
README
Polar.sh Crystal SDK
A Crystal language client for the Polar.sh API, providing a type-safe interface to manage your SaaS business operations.
Features
- ✨ Auto-generated from OpenAPI spec - Always up-to-date with the latest Polar.sh API
- 🔒 Type-safe - Leverage Crystal's strong type system
- 🎯 Complete API coverage - All 31 resource endpoints with 100+ methods
- 🔐 Webhook validation - Built-in HMAC signature verification
- 📦 520+ models - Full type definitions for all API objects
- ⚡ HTTP client included - No external HTTP library needed (uses stdlib)
- 🛡️ Error handling - Proper exception hierarchy for API errors
Installation
Add this to your application's shard.yml:
dependencies:
polar-sh:
github: qard/polar-sh
version: ~> 0.1.0
Then run:
shards install
Quick Start
require "polar-sh"
# Initialize the client (uses POLAR_ACCESS_TOKEN env var)
client = Polar::Client.new
# Or explicitly provide credentials
client = Polar::Client.new(
access_token: "your_access_token",
server_url: "https://api.polar.sh" # optional, defaults to production
)
# List organizations
response = client.organizations.list(limit: 10)
# Get a specific organization
org = client.organizations.get(id: "org_123")
# Create a product
product = client.products.create(body: product_data)
Authentication
The SDK uses Bearer token authentication. You can create an access token in your Polar.sh dashboard.
# Production environment (default)
client = Polar::Client.new(access_token: "your_access_token")
# Or explicitly specify production URL
client = Polar::Client.new(
access_token: "your_access_token",
server_url: "https://api.polar.sh"
)
# Sandbox environment (for testing)
client = Polar::Client.new(
access_token: "your_sandbox_token",
server_url: "https://sandbox-api.polar.sh"
)
# Or use environment variables
# Set POLAR_ACCESS_TOKEN and optionally POLAR_SERVER_URL
client = Polar::Client.new
Usage Examples
Fluent Nested API
The SDK uses a fluent, nested API structure that clearly separates different contexts:
require "polar-sh"
client = Polar::Client.new(access_token: ENV["POLAR_ACCESS_TOKEN"])
# Organization/Admin operations (root level)
client.organizations.list(limit: 10)
client.products.create(body: product_data)
client.subscriptions.list(limit: 20)
# Customer Portal operations (customer-facing)
client.customer_portal.orders.list()
client.customer_portal.subscriptions.get(id: "sub_123")
client.customer_portal.benefit_grants.list()
client.customer_portal.customers.get # Get authenticated customer
client.customer_portal.customers.list(page: 1, limit: 10) # List payment methods
# OAuth2 operations
client.oauth2.clients.create(body: oauth_client_data)
client.oauth2.clients.get(client_id: "client_123")
# Webhook operations
client.webhooks.endpoints.list()
client.webhooks.endpoints.create(body: webhook_data)
Creating a Product
require "polar-sh"
client = Polar::Client.new(
access_token: ENV["POLAR_ACCESS_TOKEN"]
)
# Note: Body parameters are JSON::Serializable objects
# You'll need to create a struct matching the API schema
response = client.products.create(body: product_data)
Listing with Pagination
# Get first page
page1 = client.organizations.list(page: 1, limit: 20)
# Get next page
page2 = client.organizations.list(page: 2, limit: 20)
Error Handling
begin
org = client.organizations.get(id: "invalid_id")
rescue ex : Polar::ResourceNotFoundError
puts "Organization not found (404)"
rescue ex : Polar::HTTPValidationError
puts "Validation error (422): #{ex.message}"
rescue ex : Polar::NotPermittedError
puts "Permission denied (403)"
rescue ex : Polar::PolarError
puts "API error: #{ex.status_code} - #{ex.message}"
end
Webhook Validation
Polar.sh uses the Standard Webhooks specification for secure webhook delivery.
Basic Usage
require "polar-sh"
# In your web framework (Kemal, Lucky, etc.)
post "/webhooks/polar" do |env|
# Get raw body and headers
body = env.request.body.try(&.gets_to_end) || ""
headers = env.request.headers
begin
# Validate and parse the webhook
event = Polar::Webhooks.validate_event(
body: body,
headers: headers,
secret: ENV["POLAR_WEBHOOK_SECRET"] # Base64-encoded secret from Polar
)
# Process the event
case event["type"].as_s
when "subscription.created"
handle_subscription_created(event)
when "order.completed"
handle_order_completed(event)
end
# Return 200 to acknowledge receipt
env.response.status_code = 200
"OK"
rescue ex : Polar::WebhookVerificationError
# Invalid signature - reject the webhook
env.response.status_code = 403
"Forbidden"
end
end
Webhook Security Features
- HMAC-SHA256 signatures - Cryptographic verification
- Timestamp validation - Prevents replay attacks (5-minute tolerance)
- Constant-time comparison - Prevents timing attacks
- Base64 secret decoding - Handles Polar's secret format
Required Headers
The webhook must include these headers:
webhook-id- Unique webhook identifierwebhook-timestamp- Unix timestampwebhook-signature- HMAC signature(s)
Configuration
Server URLs
# Production (default)
Polar::Client.new(access_token: token)
# Uses: https://api.polar.sh
# Sandbox
Polar::Client.new(
access_token: token,
server_url: "https://sandbox-api.polar.sh"
)
# Custom server (for local development)
Polar::Client.new(
access_token: token,
server_url: "http://localhost:8000"
)
# Using environment variable (recommended)
# Set POLAR_SERVER_URL=https://sandbox-api.polar.sh
Polar::Client.new(access_token: token)
Timeouts
Currently uses Crystal's standard HTTP client defaults. Custom timeout configuration coming soon.
Development
Prerequisites
- Crystal >= 1.17.1
- OpenSSL (for webhook HMAC)
Setup
# Clone the repository
git clone https://github.com/qard/polar-sh.git
cd polar-sh
# Install dependencies
shards install
# Run tests
crystal spec
# Format code
crystal tool format
# Build
crystal build src/polar-sh.cr
Regenerating the SDK
The SDK is generated from Polar's OpenAPI specification using a custom generator.
Automatic (via GitHub Actions):
The SDK automatically regenerates daily when the Polar API changes:
- 🤖 Runs daily at 2 AM UTC
- 📥 Downloads latest OpenAPI spec from Polar API
- 🔄 Regenerates SDK
- ✅ Verifies build succeeds
- 📬 Creates PR if changes detected
Manual regeneration:
# Regenerate from production API (default)
crystal run src/generate.cr
# Or regenerate from sandbox API
crystal run src/generate.cr https://sandbox-api.polar.sh/openapi.json
# Format generated code
crystal tool format
# Verify build
crystal build src/polar-sh.cr
Architecture
This SDK uses a hybrid approach:
- Generated Code - API models and HTTP methods auto-generated from OpenAPI spec
- Custom Enhancements - Webhook validation, error handling, and Crystal idioms
Directory Structure
src/polar/
client.cr # Main client class
errors.cr # Exception hierarchy
webhooks.cr # Webhook validation
models/ # 520+ generated models
api/ # 31 generated API clients
src/
generator.cr # Custom OpenAPI → Crystal generator
.github/workflows/
ci.yml # Run tests on every PR
regenerate-sdk.yml # Auto-regenerate on API changes
release.yml # Create releases on version tags
Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes
- Run
crystal tool formatandcrystal spec - Submit a pull request
Resources
- Polar.sh Website: https://polar.sh
- API Documentation: https://docs.polar.sh/api
- OpenAPI Spec: https://api.polar.sh/openapi.json
- Standard Webhooks: https://www.standardwebhooks.com/
- Crystal Language: https://crystal-lang.org/
License
MIT License - see LICENSE for details.
Support
- Issues: GitHub Issues
- Polar.sh Support: https://polar.sh/support
- Crystal Forum: https://forum.crystal-lang.org/
Built with ❤️ using Crystal
Documentation
Built from the current release. The first visit to a release nobody has asked for starts its build.
Links
This branch
- Branch
main- Seen
- Nov 3, 2025
- Crystal
>= 1.17.1- Indexed
- yes
Dependents
No indexed shard depends on this one yet.
Repository
github.com/Qard/polar-sh
Metadata
- Created
- Aug 12, 2026
- Updated
- Aug 16, 2026
- Synced
- Aug 15, 2026
- Versions
- 1