> This page is for version v1.
> For other versions, use one of these documentation indexes:
> - v2 (default): https://docs.paid.ai/v-2/llms.txt
> - v1: https://docs.paid.ai/v-1/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.paid.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.paid.ai/_mcp/server.

# Developer Guide: Build, Price, and Bill with Paid

> Complete guide to integrating billing and revenue management into your applications using Paid SDKs for Python, Node.js, Go, and Ruby.

## At-a-Glance

| Step                                         | Interface | What you'll do                                                                                                                                                                                                                                                                                                                              | Outcome                                                                                                                  |
| -------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| **1: Sign up**                               | Web App   | Go to [**app.paid.ai**](http://app.paid.ai), and register with your work email. During sign-up you'll set the organization name                                                                                                                                                                                                             | A dedicated, secure workspace for your company, isolated from other tenants and ready for configuration.                 |
| **2: Create an API key**                     | Web App   | Generate and copy your secret token.                                                                                                                                                                                                                                                                                                        | Credentials to call the Paid API.                                                                                        |
| **3: Create a Product**                      | Web App   | Configure pricing in **Products > Create**.                                                                                                                                                                                                                                                                                                 | A `product_id` used in every request.                                                                                    |
| **4: Integrate Accounts / Contacts/ Orders** | API       | For every new or updated record in your platform, immediately `POST` a JSON payload to: • `/customers` (for companies or buying groups) • `/contacts` (for billing contact or other people working at the account) • `/orders` (for subscriptions) This near-real-time push keeps Paid perfectly mirrored without batching or nightly jobs. | A continuously live, up-to-the-second copy of your account, contact, and order data inside Paid.                         |
| **5: Stream Signals**                        | API       | For every runtime event, send a Signal payload to Paid                                                                                                                                                                                                                                                                                      | Paid ingests each Signal in real time, applies your pricing and costing, and shows them against your customer and order. |

## Step 1: Sign up

Go to [**app.paid.ai**](http://app.paid.ai), and register with your work email. During sign-up you'll set the organization name and details.

## Step 2: Creating an API Key

API keys are your credentials to authenticate with the Paid API.

1. Sign in to your Paid dashboard at [app.paid.ai](https://app.paid.ai)
2. Navigate to your organization settings
3. Go to the API Keys section
4. Click "Create New API Key"
5. Give your key a descriptive name
6. Copy the generated secret key immediately - you won't be able to see it again!

> ### Security Best Practices
>
> * Never commit API keys to your repository
> * Store keys in environment variables or a secure secret manager
> * Rotate keys regularly
> * Use different keys for different environments (development, staging, production)

## Step 3: Product Creation

### Create Your First Product

For the purpose of this example, we are going to use an AI SDR product as a reference.

1. Navigate to [**Configure > Products**](https://app.paid.ai/products) in the sidebar
2. Click **Create**
3. Fill in the product details:
   * **Product name** (required): A recognizable name (e.g., "AI SDR"). This name will appear on invoices.
   * **Description**: What this Product does (e.g., "Automates SDR activities").
   * **External ID**: Your internal identifier for API integration (e.g., `ai-sdr-001`).
   * **Product code**: Identifier for accounting purposes (e.g., `PROD-001`).

> Tip: If you have multiple AI agents (sales chatbot, service chatbot, etc.), create a separate Product for each so they can be tracked and billed separately.

### Configure Pricing

After entering product details, add pricing using the **Add pricing** dropdown. You can combine multiple pricing types:

* **One-time fee**

  A single charge when a customer signs up or onboards.

* **Platform fee**

  A recurring flat fee (monthly/annual) for ongoing platform access.

* **Per seat**

  A recurring charge per user or seat.

* **Usage pricing**

  Variable charges based on **activity Signals**. The event name you specify here (e.g., `email_sent`) will be matched against Signals you send via API.

* **Outcome pricing**

  Variable charges based on **outcome Signals**, such as successful results (e.g., `meeting_booked`, `lead_qualified`).

### Example Configuration

For an AI SDR agent, you might add:

1. **Platform fee**: \$500/month base access
2. **Usage pricing**: \$0.10 per `email_sent` signal
3. **Outcome pricing**: \$50 per `meeting_booked` signal

Click **Finish** to create the product, or **Save as draft** to continue later.

> Copy the Product ID from the URL or product details page. You'll need this for API integration.

## **Step 4: Integrate Accounts / Contacts / Orders**

Your platform already lets new customers sign up, choose a plan, and start using your products. Paid needs to see the *same* objects (**Accounts**, **Contacts**, and **Orders**) so it can price usage, generate invoices, and collect payments.

This section walks through a complete, end-to-end example. Copy, paste, and adapt these calls to wire Paid into your onboarding flow.

### Scenario

> **John Doe**, VP of Sales at **Acme Ltd**, signs up for your *AI SDR - Pro* plan on 1 June 2025.
>
> In your database you will create:
>
> 1. **Account** → *Acme Ltd*
> 2. **Contact** → *John Doe*
> 3. **Order** → their paid subscription to *AI SDR (Pro)*

We will mirror those three objects inside Paid with the API.

### Prerequisites

#### Python

```python
pip install paid-python
```

#### Node.js

```typescript
npm i -s @paid-ai/paid-node
```

#### Go

```bash
go get -u github.com/paid-ai/paid-go
```

#### Ruby

```ruby
gem install paid_ruby
```

### 1: Create the Account

#### Python

```python
from paid import Paid, TaxExemptStatus, CreationSource, Address

# Initialize the client
client = Paid(token="<YOUR_API_KEY>")

# Create the account
customer = client.customers.create(
    name="Acme Ltd",
    phone="+1-555-0123",
    employee_count=100,
    annual_revenue=1000000,
    tax_exempt_status="none",
    creation_source="api",
    website="https://acme.com",
    external_id="acme_account_123", # this would normally be this account's ID in your DB
    billing_address=Address(
        line_1="123 Business Ave",
        line_2="Suite 100",
        city="San Francisco",
        state="CA",
        zip_code="94105",
        country="USA"
    )
)
```

#### Node.js

```typescript
import { PaidClient, Paid } from '@paid-ai/paid-node';

// Initialize the client
const client = new PaidClient({
    token: '<YOUR_API_KEY>'
});

// Create account data
const customerData: Paid.CustomerCreate = {
    name: 'Acme Ltd',
    phone: '+1-555-0123',
    employeeCount: 100,
    annualRevenue: 1000000,
    taxExemptStatus: Paid.TaxExemptStatus.None,
    creationSource: Paid.CreationSource.Api,
    website: 'https://acme.com',
    externalId: 'acme_account_123', // this would normally be this account's ID in your DB
    billingAddress: {
        line1: '123 Business Ave',
        line2: 'Suite 100',
        city: 'San Francisco',
        state: 'CA',
        zipCode: '94105',
        country: 'USA'
    }
};

// Create the account
const customer = await client.customers.create(customerData);
```

#### Go

```go
func createCustomer(apiKey string) *api.Customer {
  c := client.NewClient(
    option.WithToken(apiKey),
  )

  customer, err := c.Customers.Create(ctx, &api.CustomerCreate{
    Name:            "Acme Ltd",
    Phone:           api.String("+1-555-0123"),
    EmployeeCount:   api.Float64(100),
    AnnualRevenue:   api.Float64(1000000),
    TaxExemptStatus: api.TaxExemptStatusNone.Ptr(),
    CreationSource:  api.CreationSourceApi.Ptr(),
    Website:         api.String("https://acme.com"),
    ExternalId:      api.String("acme_account_123"), // this would normally be this account's ID in your DB
    BillingAddress: &api.Address{
      Line1:   "123 Business Ave",
      Line2:   api.String("Suite 100"),
      City:    "San Francisco",
      State:   "CA",
      ZipCode: "94105",
      Country: "USA",
    },
  })

  if err != nil {
    log.Fatalf("Error creating customer: %v", err)
  }

  return customer
}
```

#### Ruby

```ruby
require 'paid_ruby'

# Initialize the client
api = Paid::Client.new(
  token: "<YOUR_API_KEY>"
)

# Create the account
customer = api.customers.create(
  name: "Acme Ltd",
  phone: "+1-555-0123",
  employee_count: 100.0,
  annual_revenue: 1000000.0,
  tax_exempt_status: Paid::TaxExemptStatus::NONE,
  creation_source: Paid::CreationSource::API,
  website: "https://acme.com",
  external_id: "acme_account_123", # this would normally be this account's ID in your DB
  billing_address: {
    line_1: "123 Business Ave",
    line_2: "Suite 100",
    city: "San Francisco",
    state: "CA",
    zip_code: "94105",
    country: "USA"
  }
)
```

**Returns**

Returns the `Customer` object upon successful creation. Save the returned `id`; we'll need it in the next two calls.

```yaml
{
  id: 'uuid',
  organizationId: 'uuid',
  name: 'Acme Ltd',
  phone: '+1-555-0123',
  website: 'https://acme.com',
  employeeCount: 100,
  annualRevenue: 1000000,
  taxExemptStatus: 'none',
  creationSource: 'api',
  externalId: 'acme_123',
  creationState: 'active',
  billingAddress: {
    city: 'San Francisco',
    line1: '123 Business Ave',
    line2: 'Suite 100',
    state: 'CA',
    country: 'USA',
    zipCode: '94105'
  }
}
```

### 2: Create the Contact

#### Python

```python
from paid import Paid, Salutation

# Initialize the client
client = Paid(token="<YOUR_API_KEY>")

# Create the contact (using either an internal or an external account id)
contact = client.contacts.create(
    customer_external_id="acme_account_123", # use either customer_id or customer_external_id for contact creation
    salutation="Mr.",
    first_name="John",
    last_name="Doe",
    email="john.doe@acme.com",
    phone="+1-555-0123",
    billing_street="123 Main St",
    billing_city="San Francisco",
    billing_state_province="CA",
    billing_country="USA",
    billing_postal_code="94105",
    external_id="acme_contact_123"
)
```

#### Node.js

```typescript
import { PaidClient, Paid } from '@paid-ai/paid-node';

// Initialize the client
const client = new PaidClient({
    token: '<YOUR_API_KEY>'
});

// Create contact data (using either an internal or an external account id)
const contactData: Paid.ContactCreate = {
    customerExternalId: 'acme_account_123', // use either customerId or customerExternalId for contact creation
    salutation: Paid.Salutation.Mr,
    firstName: 'John',
    lastName: 'Doe',
    email: 'john.doe@acme.com',
    phone: '+1-555-0123',
    billingStreet: '123 Main St',
    billingCity: 'San Francisco',
    billingStateProvince: 'CA',
    billingCountry: 'USA',
    billingPostalCode: '94105',
    externalId: 'acme_contact_123'
};

// Create the contact
const contact = await client.contacts.create(contactData);
```

#### Go

```go
func createContact(apiKey string) *api.Contact {
  c := client.NewClient(
    option.WithToken(apiKey),
  )

  contact, err := c.Contacts.Create(ctx, &api.ContactCreate{
    CustomerExternalId:   api.String("acme_account_123"), // use either CustomerId or CustomerExternalId for contact creation
    Salutation:           api.SalutationMr,
    FirstName:            "John",
    LastName:             "Doe",
    Email:                "john.doe@acme.com",
    Phone:                api.String("+1-555-0123"),
    BillingStreet:        "123 Main St",
    BillingCity:          "San Francisco",
    BillingStateProvince: api.String("CA"),
    BillingCountry:       "USA",
    BillingPostalCode:    "94105",
    ExternalId:           api.String("acme_contact_123"),
  })

  if err != nil {
    log.Fatalf("Error creating contact: %v", err)
  }

  return contact
}
```

#### Ruby

```ruby
require 'paid_ruby'

# Initialize the client
api = Paid::Client.new(
  token: "<YOUR_API_KEY>"
)

# Create the contact (using either an internal or an external account id)
contact = api.contacts.create(
  customer_external_id: "acme_account_123", # use either customer_id or customer_external_id for contact creation
  salutation: Paid::Salutation::MR,
  first_name: "John",
  last_name: "Doe",
  email: "john.doe@acme.com",
  phone: "+1-555-0123",
  billing_street: "123 Main St",
  billing_city: "San Francisco",
  billing_state_province: "CA",
  billing_country: "USA",
  billing_postal_code: "94105",
  external_id: "acme_contact_123"
)
```

**Returns**

Returns the `Contact` object upon successful creation. It's recommended to store the returned contact ID in your own system for future reference and operations. The contact is now linked to the account inside Paid.

```yaml
{
  id: 'uuid',
  organizationId: 'uuid',
  salutation: 'Mr.',
  firstName: 'John',
  lastName: 'Doe',
  accountName: 'Acme Ltd',
  email: 'john.doe@acme.com',
  phone: '+1-555-0123',
  billingStreet: '123 Main St',
  billingCity: 'San Francisco',
  billingStateProvince: 'CA',
  billingCountry: 'USA',
  billingZipPostalCode: '94105',
  externalId: 'acme_contact_123',
  accountId: 'uuid'
}
```

### 3: Create an Order

#### Python

```python
from paid import Paid

# Initialize the client
client = Paid(token="<YOUR_API_KEY>")

# Create the order (using either an internal or an external customer id and billing contact id)
order = client.orders.create(
    customer_id=customer.id, # use either customer_id or customer_external_id for order creation
    billing_contact_id=contact.id, # use the contact ID from the previous step
    name="AI SDR - Pro plan",
    description="Annual subscription for AI SDR - Pro plan",
    start_date="2025-06-01",
    end_date="2026-05-31",
    currency="USD"
)
```

#### Node.js

```typescript
import { PaidClient, Paid } from '@paid-ai/paid-node';

// Initialize the client
const client = new PaidClient({
    token: '<YOUR_API_KEY>'
});

// Create order data (using either an internal or an external customer id and billing contact id)
const orderData: Paid.OrderCreate = {
    customerId: customer.id,
    billingContactId: contact.id!, // use the contact ID from the previous step
    name: 'AI SDR - Pro plan',
    description: 'Annual subscription for AI SDR - Pro plan',
    startDate: '2025-06-01',
    endDate: '2026-05-31',
    currency: 'USD'
};

// Create the order
const order = await client.orders.create(orderData);
```

#### Go

```go
func createOrder(apiKey) *api.Order {
  c := client.NewClient(
    option.WithToken(apiKey),
  )

  order, err := c.Orders.Create(ctx, &api.OrderCreate{
    CustomerId:       customerID, // use either CustomerId or CustomerExternalId for order creation
    BillingContactId: contactID,  // use the contact ID from the previous step
    Name:             "AI SDR - Pro plan",
    Description:      api.String("Annual subscription for AI SDR - Pro plan"),
    StartDate:        "2025-06-01",
    EndDate:          api.String("2026-05-31"),
    Currency:         "USD",
  })

  if err != nil {
    log.Fatalf("Error creating order: %v", err)
  }

  return order
}
```

#### Ruby

```ruby
require 'paid_ruby'

# Initialize the client
api = Paid::Client.new(
  token: "<YOUR_API_KEY>"
)

# Create the order (using either an internal or an external customer id and billing contact id)
order = api.orders.create(
  customer_id: customer.id, # use either customer_id or customer_external_id for order creation
  billing_contact_id: contact.id, # use the contact ID from the previous step
  name: "AI SDR - Pro plan",
  description: "Annual subscription for AI SDR - Pro plan",
  start_date: "2025-06-01",
  end_date: "2026-05-31",
  currency: "USD",
  order_lines: []
)
```

**Returns**

Returns the `Order` object upon successful creation. It's recommended to store the returned order ID in your own system for future reference and operations.

```json
{
  "id": "uuid",
  "organizationId": "uuid",
  "name": "Annual subscription for AI SDR - Pro plan",
  "startDate": "2024-01-01T00:00:00.000Z",
  "endDate": "2024-12-31T00:00:00.000Z",
  "orderAmount": 0,
  "estimatedTax": 0,
  "billedAmountNoTax": 0,
  "billedTax": 0,
  "pendingBillingAmount": 0,
  "totalBilledAmount": 0,
  "totalAmount": 0,
  "creationState": "draft",
  "usageSummary": [],
  "customer": {
    "id": "uuid",
    "organizationId": "uuid",
    "name": "Acme Ltd",
    "email": "contact@acme.com",
    "phone": "",
    "website": "https://acme.com",
    "employeeCount": 100,
    "annualRevenue": 1000000,
    "taxExemptStatus": "exempt",
    "creationSource": "api",
    "externalId": "customer-123",
    "creationState": "active",
    "billingAddress": {
      "city": "New York",
      "line1": "123 Main St",
      "line2": "Suite 100",
      "state": "NY",
      "country": "USA",
      "zipCode": "10001"
    }
  },
  "customerId": "uuid",
  "orderLines": [...]
}
```

## Step 5: Stream Signals (AI SDR Edition)

Paid measures how each **Product** is used through **Signals**, which are immutable events you emit in real time. Every Signal links back to the *Order* you created (for example, Acme Ltd's **Annual subscription for AI SDR - Pro plan**), so Paid can measure all your product activities against it, and can also price the event and invoice it if you charge your customers on outcome.

We'll use an AI SDR example and stream Signals for common SDR activities: sending outbound emails, making calls, logging replies, and booking meetings.

**When should I send a Signal?**

| Activity                                 | Send? | Signal Name      |
| :--------------------------------------- | :---- | :--------------- |
| AI SDR sends a prospecting email         | Yes   | `email_sent`     |
| Prospect replies (AI ingests reply)      | Yes   | `email_reply`    |
| AI SDR places an outbound call           | Yes   | `call_completed` |
| Call goes to voicemail (no conversation) | No    | None             |
| Meeting booked & pushed to CRM           | Yes   | `meeting_booked` |
| Internal nightly refresh job             | No    | None             |

Only instrument activities and outcomes that mean something to your customers.

#### Python

```python
from paid import Paid, SignalV2

# Initialize the client
client = Paid(token="<YOUR_API_KEY>")

product_id = "product_123"
customer_id = "customer_123"
signal_name = "email_sent"
additional_data = {
  "model": "gpt4",
  "subjectLength": 56,
  "bodyLength": 452
}


# Create signals for bulk usage recording
signal = SignalV2(
    event_name=signal_name,
    product_id=product_id,
    customer_id=customer_id,
    data=additional_data
)

# Record bulk usage
result = client.usage.record_bulk_v2(signals=[signal])
```

#### Go

```go
func recordUsage(apiKey) {
  c := client.NewClient(
    option.WithToken(apiKey),
  )

  productID = "product_123"
  customerID = "customer_123"
  signalName = "email_sent"
  additionalData := map[string]interface{}{
    "model":         "gpt4-go-test",
    "subjectLength": 56,
    "bodyLength":    452,
  }

  signals := []*api.SignalV2{
    {
      ProductId:    api.String(productID),
      CustomerId: api.String(customerID),
      EventName:  api.String(signalName),
      Data:       additionalData,
    },
  }

  _, err := c.Usage.RecordBulkV2(ctx, &api.UsageRecordBulkV2Request{
    Signals: signals,
  })

  if err != nil {
    log.Fatalf("Error recording bulk usage: %v", err)
  }
}
```

#### Ruby

```ruby
require 'paid_ruby'

# Initialize the client
client = Paid::Client.new(
  token: "<YOUR_API_KEY>"
)

product_id = "product_123"
customer_id = "customer_123"
signal_name = "email_sent"
additional_data = {
  "model" => "gpt4",
  "subjectLength" => 56,
  "bodyLength" => 452
}

# Create signals for bulk usage recording
signals = [
  {
    product_id: product_id,
    customer_id: customer_id,
    event_name: signal_name,
    data: additional_data
  }
]

# Record bulk usage
result = client.usage.record_bulk_v2(signals: signals)
```

#### Node.js

```javascript
import { PaidClient } from '@paid-ai/paid-node';

// Initialize the client
const client = new PaidClient({
  token: "<YOUR_API_KEY>"
});

const productId = "product_123";
const customerId = "customer_123";
const signalName = "email_sent";
const additionalData = {
  model: "gpt4",
  subjectLength: 56,
  bodyLength: 452
};

// Create signals for bulk usage recording
const signals = [
  {
    productId: productId,
    customerId: customerId,
    eventName: signalName,
    data: additionalData
  }
];

// Record bulk usage
const result = await client.usage.recordBulkV2({ signals });
```

### Sending Signals via OTEL Traces

A more reliable and user-friendly way to send Signals is to send them via OpenTelemetry tracing. This allows you to send Signals with less arguments and boilerplate as the information is available in the tracing context. The arguments take in signal name, optional data, and a flag that attaches costs from the same trace.

`Paid.signal()` has to be called within a tracing context. Tracing context is created with `Paid.trace()`.

#### Python

```python
from paid import Paid

# Initialize the client
client = Paid(token="<YOUR_API_KEY>")

# Initialize tracing
client.initialize_tracing()

def send_email():
    # Emit signal within a trace
    client.signal(
        event_name="email_sent",
        data={ } # Optional data (ex. Manual Cost Tracking)
    )
    # Simulate the rest of your logic
    print("Email sent!")

# Wrap it in a trace
_ = client.trace(
    external_customer_id="customer_123",
    external_product_id="product_123", # external_product_id is generally optional but is required for sending signals.
    fn=lambda: send_email()
)
```

#### Go

```go
// Placeholder for signals within traces in Go.
// This feature is not yet available in Go SDK.
```

#### Ruby

```ruby
# Placeholder for signals within traces in Ruby.
# This feature is not yet available in Ruby SDK.
```

#### Node.js

```javascript
  import { PaidClient } from "@paid-ai/paid-node";

  async function main() {
      const client = new PaidClient({ token: "<your_paid_api_key>" });

      // initialize cost tracking
      await client.initializeTracing()

      // trace the call
      await client.trace("<your_external_customer_id>", async () => {
          // ... your app logic, cost tracking LLM wrapper calls
          paid.signal("signal_name", { "optional": "data" });
      }, "<optional_external_product_id>");
  }
```

## Error Handling

The API uses conventional HTTP response codes to indicate the success or failure of requests:

* `2xx` range indicates success
* `4xx` range indicates an error that failed given the information provided
* `5xx` range indicates an error with Paid's servers

**Error Response Format**

```json
{
  "error": {
    "message": "Error message",
    "code": "ERROR_CODE",
    "details": "Additional error details"
  }
}
```

## Need Help?

* **Email**: [support@paid.ai](mailto:support@paid.ai)
* **Community**: Join `#paid-developers` on Slack

## Additional Resources

* [API Documentation](https://docs.paid.ai)
* [Support](mailto:support@paid.ai)