> This page is for version v2 (default).
> 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.

# First signals

> Send first signals and cost traces

## Prerequisites

* A [Paid API key](https://app.paid.ai/settings/api-keys).
* The Paid SDK installed in the service that emits signals.
* Optional: [customers](https://app.paid.ai/customers) and [products](https://app.paid.ai/products) with stable external IDs for attribution. You can also create them through the [Products API](/api-reference/api-reference/products/create-product) and [Customers API](/api-reference/api-reference/customers/create-customer).

## Sending signals

Send a signal by calling the SDK with an event name and optional attribution. Signals represent actions your agent takes; use them to drive billing, track delivered value, or both. To attach a signal to a customer's product usage, provide both `customer` and `product` ids. Unattributed signals are still saved and visible in the dashboard.

#### Python

```python
from paid import Paid, Signal, CustomerByExternalId, ProductByExternalId

paid_client = Paid(token="YOUR_PAID_API_KEY")

# ... your business logic ...

# emit a signal for an action your agent takes
res = paid_client.signals.create_signals(
    signals=[
        Signal(
            event_name="email_sent",
            customer=CustomerByExternalId(
                external_customer_id="<your_external_customer_id>",
            ),
            attribution=ProductByExternalId(
                external_product_id="<your_external_product_id>",
            ),
            data={"some_data": "some_value"},
        )
    ],
)

print(res)
```

#### Node.js

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

const paidClient = new PaidClient({ token: "YOUR_PAID_API_KEY" });

// ... your business logic ...

// emit a signal for an action your agent takes
const res = await paidClient.signals.createSignals({
  signals: [
    {
      eventName: "email_sent",
      customer: {
        externalCustomerId: "<your_external_customer_id>",
      },
      attribution: {
        externalProductId: "<your_external_product_id>",
      },
      data: { someData: "some_value" },
    },
  ],
});

console.log(res);
```

#### Go

```go
import (
    "context"
    "fmt"

    paid "github.com/paid-ai/paid-go"
    paidclient "github.com/paid-ai/paid-go/client"
    "github.com/paid-ai/paid-go/option"
)

paidClient := paidclient.NewClient(option.WithToken("YOUR_PAID_API_KEY"))

// ... your business logic ...

// emit a signal for an action your agent takes
res, err := paidClient.Signals.CreateSignals(
    context.TODO(),
    &paid.BulkSignalsRequest{
        Signals: []*paid.Signal{
            {
                EventName: "email_sent",
                Customer: &paid.CustomerAttribution{
                    CustomerByExternalID: &paid.CustomerByExternalID{
                        ExternalCustomerID: "<your_external_customer_id>",
                    },
                },
                Attribution: &paid.Attribution{
                    ProductByExternalID: &paid.ProductByExternalID{
                        ExternalProductID: "<your_external_product_id>",
                    },
                },
                Data: map[string]interface{}{"some_data": "some_value"},
            },
        },
    },
)
if err != nil {
    return err
}

fmt.Println(res)
```

## Consuming multiple credits per signal

By default, each signal consumes the fixed credit cost defined in your product's pricing configuration (typically 1 credit). To consume a variable number of credits per signal, you can configure your pricing rule to read a `quantity` value from signal data.

### Pricing configuration

For quantity-based credit consumption to work, your product's prepaid credits pricing line must have a **quantity mapping** configured. This tells Paid which field in the signal's `data` object contains the quantity value. Configure this in the product's pricing settings by adding a quantity property that maps to the signal data path (e.g., `$.quantity`).

Without this mapping, every signal consumes the fixed credit cost regardless of what you send in `data`.

### Sending quantity in signals

Once the pricing rule is configured, pass a `quantity` field in the signal's `data` object. This is useful when a single action should deduct more than one credit. For example, a long-running workflow that consumes 5 credits, or a batch operation where credit cost scales with the size of the work performed.

#### Python

```python
paid_client.signals.create_signals(
    signals=[
        Signal(
            event_name="document_processed",
            customer=CustomerByExternalId(
                external_customer_id="cus_acme",
            ),
            attribution=ProductByExternalId(
                external_product_id="doc_processor",
            ),
            data={
                "quantity": 5,
                "pages": 47,
                "format": "pdf",
            },
        )
    ],
)
```

#### Node.js

```typescript
await paidClient.signals.createSignals({
  signals: [
    {
      eventName: "document_processed",
      customer: { externalCustomerId: "cus_acme" },
      attribution: { externalProductId: "doc_processor" },
      data: {
        quantity: 5,
        pages: 47,
        format: "pdf",
      },
    },
  ],
});
```

#### Go

```go
_, err := paidClient.Signals.CreateSignals(ctx, &paid.BulkSignalsRequest{
    Signals: []*paid.Signal{
        {
            EventName: "document_processed",
            Customer: &paid.CustomerAttribution{
                CustomerByExternalID: &paid.CustomerByExternalID{
                    ExternalCustomerID: "cus_acme",
                },
            },
            Attribution: &paid.Attribution{
                ProductByExternalID: &paid.ProductByExternalID{
                    ExternalProductID: "doc_processor",
                },
            },
            Data: map[string]interface{}{
                "quantity": 5,
                "pages":    47,
                "format":   "pdf",
            },
        },
    },
})
if err != nil {
    return err
}
```

The `quantity` value is multiplied by the credit cost on the pricing rule. For example, if the pricing rule defines a credit cost of `1` and the signal sends `quantity: 5`, that signal deducts 5 credits. If the credit cost is `2` and `quantity` is `3`, that signal deducts 6 credits.

The credit cost may be fractional, so the deduction may be too: a credit cost of `0.5` with `quantity: 3` deducts `1.5` credits. Credit values are exact to at most six decimal places. See [Credit precision](/documentation/credits/credits#credit-precision). `quantity` itself is a count of your own units and stays a whole number.

When `quantity` is omitted, it defaults to `1`.

## User-level tracking within a customer

Signals are attributed to customers and products, but you can also track which individual user within a customer organization performed the action. Pass `external_user_id` in the signal's `data` field, matching the external ID of a user created via the [user management API](/documentation/customers-users/user-management).

#### Python

```python
paid_client.signals.create_signals(
    signals=[
        Signal(
            event_name="chat_message",
            customer=CustomerByExternalId(
                external_customer_id="cus_acme",
            ),
            attribution=ProductByExternalId(
                external_product_id="chatbot",
            ),
            data={
                "external_user_id": "usr_jane_doe",
                "model": "gpt-4o",
            },
        )
    ],
)
```

#### Node.js

```typescript
await paidClient.signals.createSignals({
  signals: [
    {
      eventName: "chat_message",
      customer: { externalCustomerId: "cus_acme" },
      attribution: { externalProductId: "chatbot" },
      data: {
        external_user_id: "usr_jane_doe",
        model: "gpt-4o",
      },
    },
  ],
});
```

#### Go

```go
_, err := paidClient.Signals.CreateSignals(ctx, &paid.BulkSignalsRequest{
    Signals: []*paid.Signal{
        {
            EventName: "chat_message",
            Customer: &paid.CustomerAttribution{
                CustomerByExternalID: &paid.CustomerByExternalID{
                    ExternalCustomerID: "cus_acme",
                },
            },
            Attribution: &paid.Attribution{
                ProductByExternalID: &paid.ProductByExternalID{
                    ExternalProductID: "chatbot",
                },
            },
            Data: map[string]interface{}{
                "external_user_id": "usr_jane_doe",
                "model":            "gpt-4o",
            },
        },
    },
})
if err != nil {
    return err
}
```

When `external_user_id` is present, Paid resolves the user and their seat assignment. If the user occupies a seat with seat-scoped credit balances, the credit spend is deducted from that seat's balance. If no seat is found, credits are deducted from the organization pool as usual.

> **Note**
>
> The `data` object is stored exactly as you send it, and the analytics API
> queries fields by their literal key names. Use consistent keys across all SDKs
> and services so that queries return complete results. The examples above use
> `snake_case` keys in both Python and Node.js for this reason.

All fields in the `data` object are also queryable through the [analytics API](/documentation/analytics-api/overview), so you can break down credit consumption by user, team, department, or any other dimension you include in signal data. For credit-backed usage, see [Credits](/documentation/credits/credits) and [How credit balances work](/documentation/credits/how-credit-balances-work).