Skip to main content

README.md

# PolarExpress

Comprehensive Elixir SDK for the [Polar API](https://docs.polar.sh/api),
with verified 1:1 feature parity to the official [JavaScript SDK](https://github.com/polarsource/polar-js).

> **Note:** This is an community Elixir SDK for Polar. Polar's official SDKs are
> in TypeScript/JavaScript and Python. This project is generated from the same
> [OpenAPI spec](https://api.polar.sh/openapi.json) that Polar uses to
> build their official SDKs, follows the same service architecture, and is
> structured to match the official
> [JavaScript SDK](https://github.com/polarsource/polar-js). The goal is an
> idiomatic Elixir experience with the same API coverage.

### What's Included

The **SDK layer** provides typed resource structs, typed request params, and
auto-paging pagination — all generated from the spec with full documentation.
The **client layer** handles HTTP execution via Finch with connection pooling,
automatic retries, request encoding, response deserialization, and telemetry.

Together, the complete Polar API surface is covered: 31 service modules,
30 typed resource structs, 85 typed params modules, webhook signature
verification, and automatic pagination.

## Installation

### With Igniter (recommended for Phoenix)

> **Beta:** The Igniter installer is new and under active testing.
> [Report issues here.](https://github.com/jeffhuen/polar_express/issues)

If your project uses [Igniter](https://hex.pm/packages/igniter), one command
sets up the webhook plug, controller, route, and setup guidance:

```bash
mix igniter.install polar_express
```

See the [Igniter Installer](guides/igniter-installer.md) guide for a detailed
walkthrough of what the installer does.

### Manual

Add `polar_express` to your dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:polar_express, "~> 0.1.5"}
  ]
end
```

Requires Elixir 1.19+ and OTP 27+.

## Configuration

PolarExpress does not read `config :polar_express`. Read secrets at your
application boundary and pass them explicitly.

Start the default Finch pool in your supervision tree:

```elixir
children = [
  PolarExpress
]
```

Create clients with explicit credentials:

```elixir
client =
  PolarExpress.client(System.fetch_env!("POLAR_ACCESS_TOKEN"),
    server: :sandbox,
    max_retries: 3,
    timeout_ms: 30_000
  )
```

See the [Getting Started](guides/getting-started.md) guide for all config
options.

## Quick Start

```elixir
client = PolarExpress.client(System.fetch_env!("POLAR_ACCESS_TOKEN"))

# Create a customer
{:ok, customer} = PolarExpress.Services.CustomersService.create_customer(client, %{
  email: "jane@example.com"
})

# List products
{:ok, products} = PolarExpress.Services.ProductsService.list_products(client, %{"limit" => 10})

# Create a checkout
{:ok, checkout} = PolarExpress.Services.CheckoutsService.create_checkout_session(client, %{
  "product_price_id" => "price_123",
  "success_url" => "https://example.com/success"
})
```

Responses are automatically deserialized into typed structs:

```elixir
customer.id        #=> "cus_abc123"
customer.email     #=> "jane@example.com"
customer.__struct__ #=> PolarExpress.Resources.Customers
```

Pass options per-client for multi-key or multi-server scenarios:

```elixir
client = PolarExpress.client("pk_test_key", server: :sandbox)
client = PolarExpress.client("pk_test_other_key", max_retries: 5)
```

## Features

### SDK

- **Full API coverage** — every endpoint from the Polar OpenAPI spec, with
  dedicated service modules matching the JavaScript SDK layout
- **Typed resources** — API responses are deserialized into typed Elixir structs
  with `@type t` definitions, full nesting support, and inner types
- **Typed params** — request parameters have dedicated struct modules with
  `@typedoc` annotations sourced from the OpenAPI spec
- **Per-event typed modules** — dedicated modules per webhook event type
  with typed data structs
- **Auto-paging pagination** — lazy `Stream`-based iteration for list endpoints
- **Webhook verification** — standardwebhooks signature verification with
  constant-time comparison and timestamp tolerance
- **OAuth**`authorize_url`, `token`, and `deauthorize` for Polar OAuth flows
- **Documentation**`@moduledoc`, `@doc`, `@typedoc`, `@spec`, and
  `@deprecated` on all generated modules, sourced from the OpenAPI spec

### Client

- **Finch HTTP client** — modern HTTP/2-capable client with connection pooling
  via NimblePool (replaces legacy Hackney)
- **Automatic retries** — exponential backoff with jitter for transient failures
- **Request encoding** — JSON request encoding with automatic multipart for
  file uploads
- **Response deserialization** — JSON to typed structs via object type registry
- **Streaming** — chunked response streaming for large payloads and SSE
- **Telemetry**`:telemetry` events for request lifecycle observability
- **Per-client configuration** — explicit struct with no global app config, safe for
  concurrent use with multiple API keys
- **Test stubs** — process-scoped HTTP stubs via NimbleOwnership for
  `async: true` tests

## Guides

- [Getting Started](guides/getting-started.md) — installation, configuration, first API call, pagination, error handling
- [Igniter Installer](guides/igniter-installer.md) — one-command Phoenix setup (beta)
- [Webhooks](guides/webhooks.md) — signature verification, WebhookPlug setup, typed event modules
- [Testing](guides/testing.md) — process-scoped HTTP stubs with `async: true` support
- [Telemetry](guides/telemetry.md) — request lifecycle events, logging, metrics

## Development

```bash
# Sync the OpenAPI spec
bash scripts/sync_openapi.sh

# Generate the SDK
mix polar.generate --clean --stats

# Verify
mix compile --warnings-as-errors
mix test
mix docs --warnings-as-errors
bash scripts/diff_js.sh
```

### Code Generation

The SDK is auto-generated from Polar's [OpenAPI spec](https://api.polar.sh/openapi.json)
via `mix polar.generate`. The generator produces:

- **31 service modules** matching the JavaScript SDK layout
- **30 resource structs** with `@type t` definitions and inner types
- **85 params modules** with `@typedoc` field annotations
- **2 registries** (object types and event types)
- **36 event modules** for typed webhook event handling

A small set of [overrides](lib/polar_express/generator/overrides.ex) handle
cases where the spec's metadata doesn't match the JavaScript SDK's service layout.
Each override is documented with a reason and enforced by tests.

### Parity Testing

JavaScript SDK parity is a hard invariant. CI runs `scripts/diff_js.sh` to verify
1:1 service file and endpoint coverage. The test suite includes dedicated
parity assertions comparing the generated endpoint set against both the OpenAPI
spec and the JavaScript SDK fixture tree.

## License

MIT License. See `LICENSE` for details.