---
title: Integration Basics
description: Learn the core concepts and requirements for building a Churnkey integration.
navigation: false
---

## Project Structure

We recommend organizing your integration code in the following structure:

```bash
churnkey/
├── controllers/      # Data access endpoints
│   ├── Customers.ts
│   ├── Prices.ts
│   ├── Subscriptions.ts
│   ├── Coupons.ts   # optional
│   ├── Families.ts  # optional
|   └── Products.ts  # optional
├── models/          # Data models
│   ├── Customer.ts
│   ├── Price.ts
│   ├── Subscription.ts
│   ├── Coupon.ts    # optional
│   ├── Family.ts    # optional
│   └── Product.ts   # optional
├── actions/         # Subscription modifications
│   ├── Cancel.ts    # optional
│   ├── ApplyCoupon.ts  # optional
│   ├── ExtendTrial.ts  # optional
│   ├── ChangePrice.ts  # optional
│   └── Pause.ts     # optional
└── index.ts        # Integration entry point
```

## Pagination

All list endpoints should support pagination to handle large datasets efficiently. Each `list()` method accepts pagination parameters and returns a paginated response. You can skip implementation of pagination if you are sure that the number of items will be small. However, it is strongly recommended to implement pagination just in case.

### Request Parameters

Pagination is controlled through query parameters:

```bash
GET /churnkey/customers?limit=10&cursor=abc123
```

::collapsible{name="Query Parameters"}
:field-schema{schema="/types/pagination-query.type"}
::

### Response Format

List endpoints return paginated responses in this format:

::collapsible{name="Response Structure"}
:field-schema{schema="/types/pagination-response.type"}  
::

## Error Handling

When an error occurs, return an appropriate HTTP status code along with a structured error response:

```json
{
  "code": 500, // HTTP status code
  "message": "A human-readable error message"
}
```

Common status codes:

- `400` - Bad Request (invalid parameters)
- `401` - Unauthorized (invalid or missing token)
- `404` - Not Found (resource doesn't exist)
- `500` - Internal Server Error

## Test Mode

Every request includes an `X-Churnkey-Mode` header indicating whether it's a test or live request:

```bash
X-Churnkey-Mode: test   # Test mode
X-Churnkey-Mode: live   # Live mode (default)
```

Use this header to determine which environment (test or production) to use for your billing system operations. Test mode must be explicitly enabled in the Churnkey dashboard.

## Authentication

Every request to your integration endpoints will include an `Authorization` header with your integration token:

```bash
Authorization: Bearer your_integration_token
```

Verify this token matches the one from your Churnkey dashboard before processing any requests.

## Features Manifest

Your integration must expose a `/churnkey/features` endpoint that describes which functionality is supported. This helps Churnkey understand which Cancel Flow options to enable.

::collapsible{name="Features Schema"}
:field-schema{schema="/types/features.type.json"}
::

The manifest includes:

- Supported controllers (required and optional)
- Available actions and their configurations
- Supported features for each action (e.g., immediate vs end-of-period cancellation)

## Implementing Context
Your custom context should extend the SDK `Integrator.Context` class. You can add any properties/methods you need to the context object.

```typescript
import { Integrator } from '@churnkey/sdk'
export class Context extends Integrator.Context {
    db: DbConnection
    constructor(mode: Integrator.Mode, db: DbConnection) {
        super(mode)
        this.db = db
    }
}
```

## Instantiating Context
When you expose your integration to the internet, you should provide a function that returns a new instance of your context. This function will be called for every request.

```typescript
import { Integrator } from './churnkey/Integration'

const app = express()
Integration.expose({
    app: app, // express app instance
    token: process.env.CK_INTEGRATION_TOKEN, // your integration token
    ctx(req, req) {
        return new Context(
            req.headers['X-Churnkey-Mode'],
            req.db
        )
    } 
})
```
