> ## Documentation Index
> Fetch the complete documentation index at: https://docs.weflayr.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Upload revenue

> Manage revenue programmatically instead of uploading CSVs.

On an [internal project](/internal-projects), go to [Manage Key metrics](/api/key-metrics) instead.

<CodeGroup>
  ```python Python theme={null}
  import weflayr

  client = weflayr.client(api_key="wf-...")
  ```

  ```js Node.js theme={null}
  const weflayr = require('weflayr');

  const client = weflayr.client({ apiKey: 'wf-...' });
  ```

  ```bash cURL theme={null}
  # Passed as a Bearer token on every request
  export WEFLAYR_API_KEY="wf-..."
  ```
</CodeGroup>

## Set revenue

A customer can have several lines in the same month (in case of multiple billings):

<CodeGroup>
  ```python Python theme={null}
  from weflayr.openapi.models import SetRevenueInput

  client.set_revenue(body=SetRevenueInput.from_dict({
      "rows": [
          {"customer_name": "c_123", "amount": 1200.50, "month": "2026-06"},
          {"customer_name": "c_456", "amount": 800, "month": "2026-06"},
      ]
  }))
  ```

  ```js Node.js theme={null}
  await weflayr.api.setRevenue({
    client,
    throwOnError: true,
    body: {
      rows: [
        { customerName: 'c_123', amount: 1200.50, month: '2026-06' },
        { customerName: 'c_456', amount: 800, month: '2026-06' },
      ],
    },
  });
  ```

  ```bash cURL theme={null}
  curl -X POST https://app.weflayr.com/api/revenue/ \
    -H "Authorization: Bearer $WEFLAYR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "rows": [
        {"customer_name": "c_123", "amount": 1200.50, "month": "2026-06"},
        {"customer_name": "c_456", "amount": 800, "month": "2026-06"}
      ]
    }'
  ```
</CodeGroup>

**Output:**

```json theme={null}
{"ok": true, "rows_inserted": 2}
```

**Input params:**

<ParamField body="rows" type="array of row objects" required>
  The revenue lines to book, described below. Up to 1,000 per call.
</ParamField>

<ParamField body="upsert" type="boolean" default="false">
  If a row's `revenue_id` already exists (c.f. `rows` parameters below), overwrite it instead of throwing an error.
</ParamField>

Each object inside `rows` accepts:

<ParamField body="customer_name" type="string" required>
  The same `customer_name` you stamp on your LLM calls when [propagating metadata](/quickstart#add-metadata-to-your-llm-calls-for-finer-analysis), so revenue and AI cost land on the same customer.
</ParamField>

<ParamField body="amount" type="number" required>
  The value to book.
</ParamField>

<ParamField body="month" type="&#x22;YYYY-MM&#x22;">
  The calendar month the value belongs to. Required unless you set `month_start` and `month_end` instead.
</ParamField>

<ParamField body="month_start, month_end" type="&#x22;YYYY-MM&#x22;">
  Bill over a multi-month period (e.g. quarterly) instead of a single `month`. `amount` is split evenly over the period. Set either `month`, or both `month_start` and `month_end`, never both forms on the same row.
</ParamField>

<ParamField body="revenue_id" type="string">
  An id for the revenue row over which uniqueness is checked, typically from a billing management system.
</ParamField>

<Note>
  API values are their own revenue source, alongside [Stripe and CSV](/configure/sync-revenue). Sources add up: if you post revenue for a customer-month that also has a CSV upload or a Stripe sync, Weflayr assumes it is the sum. We recommend using one source only to avoid double counting.
</Note>

## Get revenue

<CodeGroup>
  ```python Python theme={null}
  client.get_revenue(customer_names=["c_123", "c_456"])
  ```

  ```js Node.js theme={null}
  await weflayr.api.getRevenue({
    client,
    throwOnError: true,
    query: { customerNames: ['c_123', 'c_456'] },
  });
  ```

  ```bash cURL theme={null}
  curl "https://app.weflayr.com/api/revenue/?customer_names=c_123&customer_names=c_456" \
    -H "Authorization: Bearer $WEFLAYR_API_KEY"
  ```
</CodeGroup>

**Output:**

```json theme={null}
{
  "rows": [
    {"revenue_id": "b3f2b160-6e12-4f1a-9c3d-0a2e7c9b5d41", "customer_name": "c_123", "metric_name": "revenue", "month": "2026-06", "amount": 1200.5, "source": "api"},
    {"revenue_id": "f7a1d8e0-9b3c-4a2f-8e6d-1c4b7a9f2e05", "customer_name": "c_123", "metric_name": "revenue", "month": "2026-06", "amount": 300.0, "source": "stripe"},
    {"revenue_id": "a1c9e4d2-7b60-4f8a-9e2d-3c4b7a9f1e08", "customer_name": "c_456", "metric_name": "revenue", "month": "2026-06", "amount": 800.0, "source": "api"}
  ],
  "next_cursor": null,
  "has_more": false
}
```

**Input params:** passed in the query string.

<ParamField query="customer_names" type="list[string]">
  Specify the names of the customers from which revenue should be fetched. Not setting it means "all customers".
</ParamField>

<ParamField query="limit" type="integer" default="100">
  Numbers of rows to fetch. Max `1000`.
</ParamField>

<ParamField query="cursor" type="string">
  The route is paginated: pass the previous page's `next_cursor` to fetch the next page. PS: `has_more` from the output says whether another page exists.
</ParamField>

## Delete revenue

<CodeGroup>
  ```python Python theme={null}
  client.delete_revenue(customer_names=["c_123", "c_456"])
  ```

  ```js Node.js theme={null}
  await weflayr.api.deleteRevenue({
    client,
    throwOnError: true,
    query: { customerNames: ['c_123', 'c_456'] },
  });
  ```

  ```bash cURL theme={null}
  curl -X DELETE "https://app.weflayr.com/api/revenue/?customer_names=c_123&customer_names=c_456" \
    -H "Authorization: Bearer $WEFLAYR_API_KEY"
  ```
</CodeGroup>

**Output:**

```json theme={null}
{"ok": true, "rows_deleted": 3}
```

**Input params:** passed in the query string.

<ParamField query="customer_names" type="list[string]" required>
  Delete all these customers' API-booked revenue.
</ParamField>

<ParamField query="month_start, month_end" type="&#x22;YYYY-MM&#x22;">
  Narrow the delete to this inclusive month range. Omit both to delete every API-booked value of the named customers.
</ParamField>

PS: CSV uploads and Stripe syncs are managed by their own source and cannot be deleted via this route
