
[title] Untitled
[path] Billing & Monetization/


[title] Spend
[path] Cost Tracking/

The **Spend** section gives you real-time visibility into how usage translates into cost across your organization.

All data in this section is derived directly from requests flowing through Amberflo and priced using your configured or default model rates. There is no delay, no estimation, and no dependency on cloud billing exports.

AI Spend is designed to answer two fundamental questions:

- **How much are we spending right now?**
- **Where is that spend coming from?**

The AI Spend page provides a high-level overview of AI costs across workloads and customers so you can quickly understand how usage is impacting your business.

Use this page to:

- Track **month-to-date, quarter-to-date, and year-to-date spend**
- Identify which **workloads generate the most cost**
- Monitor **which workloads are trending up or down**
- Understand **which customers drive the most spend**
- Detect unusual changes in spending patterns

This page is intended to provide **immediate situational awareness** for teams, especially those operating AI-powered products.



# **Spend Overview**

The **Spend Overview** page provides a high-level snapshot of how costs are distributed across workloads and customers.

All data shown on this page is derived from actual usage and automatically rated using model pricing. As soon as requests are processed through Amberflo, the resulting usage and cost data becomes visible here.



# **Spend Overview (MTD, QTD, YTD)**

At the top of the page you will see three key spend metrics:

- **Spend MTD** – Total spend for the current month
- **Spend QTD** – Total spend for the current quarter
- **Spend YTD** – Total spend for the current year

Each card includes:

- The total spend for the selected period
- A trend indicator showing how spend is changing compared to the previous period

These metrics provide a quick answer to the question:

**How much are we currently spending on AI?**



# **Time Range and Scope**

All data on this page is scoped to the selected time range, controlled by the date selector.

Available presets include:

- Week-to-date
- Month-to-date
- Quarter-to-date
- Last 7 days
- Last 14 days
- Last 30 days

You can also select a **custom date range**.

Changing the selected range updates all spend calculations and trend indicators on the page.



# **Workload Spend Insights**

Workloads represent the internal unit used to attribute usage and cost. A workload may represent:

- An application
- A product feature
- A team
- A service
- Any logical unit of usage

The workload section highlights where costs are concentrated and how those costs are changing.



## **Top Spend by Workload**

This view shows the workloads generating the **highest total spend** for the selected time range.

This helps you quickly identify:

- The applications or services driving the most cost
- Where optimization efforts may have the greatest impact
- Which parts of your product rely most heavily on AI



## **Top Trending Workloads**

Trending workloads are ranked based on **percentage change in spend** during the selected time period.

This can highlight workloads where costs are:

- Increasing rapidly due to growing usage
- Decreasing due to optimization or reduced activity

Trending analysis is useful for spotting sudden changes in behavior that may require investigation.



# **Customer Spend Insights**

If your usage is associated with customers, Amberflo can attribute usage and cost directly to them.

This allows you to understand how costs align with customer activity and billing.



## **Top Spend by Customer**

This view highlights the customers responsible for the **largest share of spend** during the selected timeframe.

This helps you understand:

- Which customers generate the most AI usage
- Where infrastructure costs are concentrated
- Which accounts may require pricing adjustments or optimization



## **Top Trending Customers**

Trending customers are ranked based on **percentage change in spend** over the selected time period.

This helps identify:

- Customers whose AI usage is growing rapidly
- Sudden changes in usage behavior
- Accounts that may require monitoring or follow-up



# **How This Page Is Meant to Be Used**

The AI Spend Overview page is designed to provide a **fast operational snapshot of cost activity**.

Use it to:

- Monitor overall spending
- Detect sudden changes in workload activity
- Understand which customers or services drive cost
- Identify where deeper investigation may be needed

From this page, teams can quickly determine whether AI usage patterns are behaving as expected or if further analysis is required.

[title] Embeddable UI Kit - React.js
[path] Billing & Monetization/Customer Billing Portal/

### Embeddable UI components for React.js applications

The **Amberflo UI Kit** is a React component library that allows Amberflo accounts to embed key usage and billing views directly within their own applications—for individual customers.

If you are not using a React.js environment, you can alternatively use the [vanilla.js library](docId\:kIPKru3tyXh4axofLKCFG)  to integrate Amberflo widgets.

## Installation

Run `yarn add @amberflo/uikit` or `npm install @amberflo/uikit` to install Amberflo UI Kit in your React project.

�&#xDCD8;***Technologies***

***React >= v17.0.2***
***Node v16.13.1***
***Typescript v4.5.5***

## Available Versions

All versions can be found on npm repository: [@amberflo/uikit](https://www.npmjs.com/package/@amberflo/uikit)

## Getting Started

### Amberflo Provider

The `AmberfloProvider` is a required wrapper component that enables Amberflo components to function correctly by connecting them to the Amberflo API. It must wrap your application and takes in two key inputs: a **customer-specific session token&#x20;**&#x61;nd a **theme** configuration. You can check here how to:

- [Generate a customer-specific session](docId\:I2mADjtkAuAs4YNxv2uNs)&#x20;
- [Customize your theme](docId\:jHFrRrrevQKbDEpckSup6)&#x20;

```html
<AmberfloProvider
  session="<YOUR_SESSION_ID>"
  theme={{
    brandColor: "<YOUR_BRAND_COLOR>",
    fontSize: <DEFAULT_FONT_SIZE>,
    fontFamily: "<DEFAULT_FONT_FAMILY>"
  }}
>
  ...
</AmberfloProvider>
```

### Imports

To import a single Amberflo UI Kit component into your application (Recommended):

```javascript
import { CostExplorerBarGraph, InvoicesList } from "@amberflo/uikit
```

## Theming

In order to customize components with a theme add a `theme` prop to the `AmberfloProvider` with the following object:

```javascript
{
  brandColor: string // it can be hex, or color name
  fontSize: number // Basic font size, all rest of font sizes will scale dynamically to this value
  fontFamily: string // name of the font to use. The font must be available on your site. Or you can use a custom font as showed below.
  customFontUrl: string // google font url to import to your site
}
```

We’ve added custom class names to most of our components. Once the components are rendered in your app, you can identify them by class names that begin with `aflo-`. These class names can be used alongside Material UI classes in your own CSS file to apply custom styling.

So for instance, if you want to set a **dark mode**, to make it look something like this:

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/e5ATBv6fr3NWebdUmlTTN_image.png)

You can add some styles like:

```css
.aflo-button .aflo-text {
    color: black;
}

.aflo-text {
    color: white;
}
```

This way you can change the text color for the whole components to `white`. And in the buttons that have a light background, we can set a dark color as `black`.

## Types

Amberflo UI Kit is fully typed. If you're using TypeScript in your project, this makes it easy to view the expected properties for each component and understand the return types of functions and hooks.

## Invoice-related Options

Several UI Kit components display invoice-related data, such as **Invoice List**, **Invoice Details**, and **Invoice PDF**. These components are influenced by settings configured under [Amberflo | Settings | Invoice](https://ui.amberflo.io/settings/invoice), which also apply to the Customer Portal and main Amberflo application.

Current invoice display options include:

- **Displaying Cached Invoices**
  &#x20;Invoice data can be shown using either cached or real-time values. Cached data helps improve load times.
- **Group Product Items on Invoices**
  &#x20;Product items can be grouped into categories, allowing for a clearer breakdown of consumption and billing.

## How to

Read how to use the [widgets, views and components in here](docId:9ZJRkjeaTQ1Bb5XoBcZno).

Read how to use [hooks here](docId\:wMqXhaR7t8Fwot5qkfWHw)&#x20;

## Appendix 1: Full Code Example

```javascript
import { useEffect, useState } from "react";
import {
  AmberfloProvider,
  UsageByMeterLineGraph,
  UsageByMeterTable,
} from "@amberflo/uikit";

function App() {
  const [sessionToken, setSessionToken] = useState(null);

  const getSession = async () => {
    const response = await fetch("https://app.amberflo.io/session", {
      method: "POST",
      body: JSON.stringify({
        // replace customer123 with an actual customer id
        customerId: "customer123",
      }),
      headers: {
        // YOU SHOULD NOT HAVE YOUR API KEY IN YOUR CLIENT SIDE CODE.
        // THIS IS ONLY FOR DEMONSTRATION PURPOSES TO TRY OUT A QUICK WORKING EXAMPLE.
        // Replace your_api_key with your account's API key.
        // Please see this section for more info.
        // (https://docs.amberflo.io/docs/get-session-token)
        "x-api-key": "your_api_key",
      },
    });

    const data = await response.json();

    setSessionToken(data.sessionToken);
  };

  useEffect(() => {
    getSession();
  }, []);

  if (!sessionToken) {
    // You can handle this many different ways (e.g. loaders, etc.), but we will just
    // return null until you have a token for simplicity.
    return null;
  }

  return (
    // When using UI Kit components, they must be a child of the AmberfloProvider.
    // Find out more about the AmberfloProvider here: https://docs.amberflo.io/docs/amberflo-components#getting-started
    // You will give need to provide it the sessionToken we receive from the
    // https://app.amberflo.io/session API.
    <AmberfloProvider session={sessionToken}>
      {/*
          A Usage Graph and Usage Table will be displayed 
          populated with the usage of the customer you selected a session for above.
          Your account must have meters and the selected customer must have usage
          to be able to display any data within the table and graph.
      */}
      <div style={{ display: "flex", flexDirection: "column", gap: "2rem" }}>
        <UsageByMeterLineGraph withTitle withMeterSelector withContainer />
        <UsageByMeterTable withTitle withContainer />
      </div>
    </AmberfloProvider>
  );
}

export default App;
```


[title] Amberflo Playground Overview
[path] Playground/

The **Amberflo Playground** lets you interact with real AI models through a simple chat interface while automatically capturing usage and cost data.

It is designed as the fastest way to understand how Amberflo tracks AI usage, attributes it to workloads, and calculates cost in real time.

Using the Playground, you can send prompts to models from providers such as OpenAI, Azure OpenAI, AWS Bedrock, and others using your own credentials. Every request made through the interface is captured, metered, and priced so you can immediately see how AI usage translates into cost.

This allows you to experiment with models and prompts while also seeing how Amberflo organizes usage data across workloads and customers.



# **How the Playground Works**

When you send a prompt from the Playground:

1. The request is sent to the AI provider using your configured provider credentials.
2. Amberflo captures usage details such as input tokens, output tokens, request timing, and metadata.
3. The request is automatically attributed to the selected workload.
4. Amberflo calculates the cost using model pricing and records the result.

Within moments, this data becomes visible in the **AI Spend dashboards**, allowing you to see exactly how usage is translated into cost.

This gives you a safe environment to explore how Amberflo tracks AI usage before integrating it into applications or production workflows.



# **Core Concepts**

The Playground operates using the same underlying governance and attribution model used throughout the Amberflo platform. Four core concepts define how access is configured and how usage is tracked.



## **Providers**

Providers define how Amberflo connects to external AI services.

For each provider you want to use, you create a credential and supply the required authentication information such as an API key or cloud credentials.

Supported providers include:

- OpenAI
- Azure OpenAI
- AWS Bedrock
- Google AI Studio
- Anthropic

These credentials are stored securely and used when requests are sent from the Playground.



## **Models**

Models represent the specific AI models you want to make available.

Each model is associated with a provider credential and maps to a specific model offered by that provider.

For example:

- GPT models from OpenAI
- Claude models from Anthropic via Bedrock
- Other provider-specific models

Defining models allows Amberflo to:

- Track usage by model
- Calculate costs accurately
- Control which models workloads are allowed to use



## **Workloads**

Workloads represent the logical consumers of AI within your system.

A workload might correspond to:

- An application
- A product feature
- A team
- A service
- A customer-facing capability

Every request sent through the Playground is attributed to a workload. This allows Amberflo to organize usage and cost data around how AI is actually used inside your organization.

Workloads are the **primary unit of attribution** for usage and spend.



## **Virtual Keys**

Virtual keys control access to models through a specific workload.

Each key is associated with a workload and inherits that workload’s allowed models. When requests are made using the key, Amberflo automatically attributes all usage and cost to the associated workload.

In the Playground, you select a workload and virtual key before sending prompts. This ensures that all activity generated through the Playground is tracked consistently with how production usage would be tracked.



# **How It All Fits Together**

Conceptually, the process looks like this:

1. You configure **providers** to connect to AI services such as OpenAI or AWS Bedrock.
2. You define **models** that map to specific AI models from those providers.
3. You create **workloads** to represent applications, teams, or product features.
4. You generate **virtual keys** that control access and attribution.
5. You use the **Playground** to send prompts and generate real AI usage.

Every request made through the Playground produces usage and cost data that is automatically attributed and visible inside Amberflo.



# **What Happens Next**

After sending a few prompts through the Playground, you can explore how Amberflo organizes and analyzes the resulting data.

Navigate to **Spend** to see:

- Cost attributed to workloads
- Trends in AI usage and spend
- Which workloads and customers are driving AI cost

This is where the full power of Amberflo becomes clear: the same usage signals generated by AI interactions can be used to understand **cost, optimize usage, and power customer billing**.

If you want, I can also help you create a **much stronger doc structure around the Playground** (right now it’s still a bit conceptual). The ideal flow would be:

1. Playground Overview
2. Send Your First Prompt
3. Understanding Attribution (workloads, customers)
4. Viewing Cost in Spend

That sequence matches the **actual first user experience** much better.

[title] Meter Integration Options
[path] Metering/

A successful usage-based business model with metered billing is built on the foundation of an **accurate usage data collection strategy**.

Amberflo provides multiple easy ways to begin collecting data without the need to re-tool your systems.

## SDK

Using the Amberflo SDK gives you:

- More control
- Immediate feedback via response codes
- Support for batching records
- Flush-on-demand functionality

**Batching Records**
Amberflo SDKs are designed to support **high-throughput environments**.

You can confidently:

- Send **hundreds of meter records per second**
- Deploy the SDK on web servers handling **high volumes of traffic**
- Ensure accuracy and availability **from day one**

**How it works:**

Each call is **queued in memory**, not sent immediately over HTTP. Messages are **batched and flushed in the background**, allowing for much faster performance.

You can configure the **batch size&#x20;**&#x61;nd th&#x65;**&#x20;flush rate.**



**Flush on demand (Blocking call)**
You can **flush the queue manually**—for example, at the end of your program—to ensure no data is left unsent. Just call the flush method:

`metering.flush()`

**Important:**
This is a **blocking call**—it will pause the calling thread until the queue is empty.
We recommend using it only in **cleanup scripts**, not during request processing.

## Non-SDK

We **strongly recommend using our SDK for AWS S3**, as it offers the best of both worlds:

1. Access to **rich SDK features** (see above)
2. Guaranteed **AWS S3 uptime SLA** for meter ingestion

However, we fully support cases where using a third-party SDK isn’t possible or preferred. In such scenarios, Amberflo offers multiple alternatives:

**Amberflo Application**
The Amberflo web application provides a **rich set of real-time dashboards** for visualizing:

- **Usage**
- **Revenue**

Dashboards can be filtered by:

- **Customer**
- **Meter name**
- **Custom dimensions**

**Amberflo API**
Amberflo is an **API-first platform**. Every feature and object can be accessed programmatically via our APIs and SDKs.

**AWS S3**
Amberflo provides access to an **Amazon S3 bucket** with predefined **permissions and controls&#x20;**&#x74;o write meter data directly.

[S3 Ingestion](docId\:bpsQYJmbb-xO1x9uHMB1T)&#x20;

**Logging Systems**
Many companies already use **centralized logging infrastructure** (e.g., **AWS CloudWatch**) for operational insights.

While logging systems aren't built specifically for metering, they can serve as a **"catch-all"** for usage data streams because they’re deeply integrated into your systems.

Amberflo supports this approach by:

- Allowing you to **write meter events to your logging system**
- **Extracting** meter events from it using supported connectors

We currently provide a connector for **AWS CloudWatch** ([see Connector to AWS CloudWatch](docId\:R-qosXvSCtQab16a83uzk) ). We’re continuously adding **new out-of-the-box integrations**—check the **Integrations&#x20;**&#x73;ection for updates.&#x20;

If you have a specific request, please contact us.

See [Log Ingestion](docId\:R-qosXvSCtQab16a83uzk)&#x20;


[title] AWS CloudWatch
[path] Metering/Meter Integration Options/

# Meter Ingestion via serverless agent for AWS CloudWatch

If you’d like to try Amberflo **without modifying your existing code or importing new libraries**, you can use our **Serverless Agent for AWS CloudWatch&#x20;**&#x74;o automatically extract meter data from your CloudWatch logs.

### **How It Works**

1. **Configure the Serverless Agent**
   &#x20;Connect Amberflo's serverless agent to your **AWS CloudWatch environment** (within your AWS VPC).
2. **Log Meter Records with Tags**
   &#x20;Log your meter events in CloudWatch, including a special **Amberflo tag**. This tag tells the agent which log entries represent meter data.
3. **Automatic Extraction and Ingestion**
   &#x20;The agent scans the logs, identifies entries with the Amberflo tag, and **ingests them as meter events** into Amberflo—automatically.

### **Example Project**

We provide a **demo project** that shows:

- A **REST API** powered by AWS Lambda
- How to **log and meter** the number of API calls using CloudWatch
- Other approaches for metering Lambda functions

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/MLSaYvhKABixpT7AmVHtD_image.png)

# How does it work?

Here is a step-by-step overview of how the **CloudWatch agent** operates:

1. Your code logs meter records
   Your application logs meter records to a **CloudWatch log group** (this is the default behavior for AWS Lambda functions).
2. Kinesis Stream collects the logs
   A **Kinesis Stream** captures the relevant log entries via a **CloudWatch Subscription Filter**.
   - If you have multiple log groups, you can route them all to the **same Kinesis Stream**.
3. Lambda agent processes the stream
   &#x20;A **Lambda function** (the agent) consumes log entries from the Kinesis Stream,
   &#x20;extracts the **meter records**, and sends them to **Amberflo for ingestion**.

**Note:&#x20;**&#x49;n the example setup, the filter pattern used is: `meter_record_for_stream`

You can use the code below (NodeJS) for the **Kinesis Stream consumer lambda**:

```javascript
'use strict';

const crypto = require('crypto');
const zlib = require('zlib');
const AWS = require('aws-sdk');

const bucketName = process.env.INGEST_BUCKET_NAME;
const accessKeyId = process.env.ACCESS_KEY;
const secretAccessKey = process.env.SECRET_KEY;

const s3 = new AWS.S3({
    region: 'us-west-2',
    accessKeyId,
    secretAccessKey,
});

const prefix = 'meter_record_for_stream';

exports.handler = async (event) => {
    const records = event
        .Records
        .map(r => r.kinesis.data)
        .map(d =>
            // Decompress and parse the CloudWatch payload
            JSON.parse(zlib.gunzipSync(Buffer.from(d, 'base64')).toString())
        )
        .filter(m => m.messageType !== 'CONTROL_MESSAGE')
        .map(p => p
            .logEvents
            .map(x => x.message)
            .map(m => {
                const i = m.indexOf(prefix);
                if (i < 0) return;  // get only messages containing meter records
                return JSON.parse(m.slice(i + prefix.length + 1));
            })
            .filter(x => x)
        ).flat();

    await ingest(records);
};

async function ingest(records) {
    const date = new Date().toISOString().slice(0, 10);
    const key = `ingest/records/${date}/${crypto.randomBytes(20).toString('hex')}.json`;

    const params = {
        Bucket: bucketName,
        Key: key,
        Body: JSON.stringify(records),
    };
    return s3.putObject(params).promise();
}
```

This code will take log entries like the one below and ingest them in batches through your Amberflo-provided S3 bucket.

```none
INFO meter_record_for_stream {
    "meterApiName": "api-calls",
    "customerId": "70f1dd87-6978-4d96-a934-5a83b63cdeb1",
    "meterTimeInMillis": 1663094105062,
    "meterValue": 1,
    "uniqueId": "c91b8860-3392-11ed-a17b-bfa2e899d2c9",
    "dimensions": {
        "method": "GET"
        "endpoint": "meter-example"
    }
}
```

📘 *Ingest record format*

*If you log the meter records in a different format, you can modify the code above to marshall the records into the Amberflo event**&#x20;format.*

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/E_Pultxwb3R3QlOVwiENf_image.png)


[title] Metering Guardrails
[path] Metering/



# **Default Limits**

Limits are defined per meter and scale linearly with the number of meters.

| Feature                          | Limit / Description                    | Comments                                                                                            |
| -------------------------------- | -------------------------------------- | --------------------------------------------------------------------------------------------------- |
| Meter events ingestion           | 1M events per hour per meter           | Standard ingestion rate per meter                                                                   |
| Meter events ingestion (roll-up) | 100M events per hour per meter         | Roll-up meters aggregate events into hourly intervals (e.g., sum), increasing ingestion throughput. |
| Usage query rows                 | 100K rows per query (\~6MB limit)      |                                                                                                     |
| Event cancellation               | 1 batch (up to 250 rules) per 12 hours | Batch operation for filtering events                                                                |
| Backfills                        | Up to 60 days                          | Historical data ingestion supported                                                                 |

# **Tuning Guardrails**

Amberflo is built with cloud-first scalability principles. These limits represent default guardrails to ensure consistent performance and reliability, and are not hard caps and can be increased by provisioning additional compute and memory.&#x20;



For higher limits or workload-specific tuning, contact Amberflo support.

[title] AI Optimization
[path] /


[title] Prometheus
[path] Metering/Meter Integration Options/

# Ingesting Meters with Prometheus Remote Write

Amberflo supports ingesting metrics directly from Prometheus using its native remote write functionality. This enables real-time ingestion of usage data as meter events, allowing you to monitor and allocate infrastructure costs with precision.

This approach works particularly well for environments where Prometheus is already deployed, such as in Kubernetes clusters, but it is not limited to Kubernetes. You can send any relevant Prometheus metrics from your infrastructure or applications.

Why Use Prometheus for Meter Ingestion?

- **Low-lift integration:** No custom agent or Amberflo library required. Simply configure remote\_write in Prometheus.
- **Real-time metering:** Prometheus pushes events directly to Amberflo as they are generated.
- **Flexible attribution:** Use dimensions like namespace, node, tenant, or application to enable fine-grained usage tracking and cost allocation.
- **Custom metrics:** Supports ingestion of both built-in system metrics and any custom metrics sent to Prometheus from your IT environment.

### Example Use Case: Kubernetes

Prometheus is often used to monitor Kubernetes clusters. With Amberflo, you can ingest metrics such as:

- **Kube-state metrics:** For example, pod states, resource requests, and limits.
- **Node metrics:** CPU, memory, disk, and I/O usage from underlying nodes.
- **Kubelet metrics:** Container-level metrics like actual resource consumption.
- **Custom application metrics:** Data sent to Prometheus by in-cluster apps.

Amberflo uses the namespace label as the customerId by default, which allows for accurate cost allocation by namespace (or by other dimensions if preferred).

## To use Prometheus remote write with Amberflo, you need the following:

**You provide:**

- Any Prometheus-monitored system (e.g. a Kubernetes cluster)
- Prometheus deployed and collecting metrics
- Outbound internet access from Prometheus


**Amberflo provides:**

- An API to accept the remote write
- Ability to set custom ingestion schema for metric data

## Prometheus Remote Write Configuration

Below is an example configuration snippet to enable remote\_write in your Prometheus setup:

```yaml
remote_write:
- url: https://app.amberflo.io/ingest/prometheus
  name: amberflo
  write_relabel_configs:
  - source_labels: [__name__, pod]
    separator: ":"
    regex: 'kube_pod_info:coredns(.+)'
    action: keep
  - action: replace
    source_labels: [namespace]
    target_label: customerId
  - regex: 'customerId|node|pod|helm_sh_chart|__name__'
    action: labelkeep
  headers:
    x-api-key: XXX-XXX-XXX
```

This configuration:

- Keeps only relevant metrics based on name and pod.
- Maps the Prometheus namespace label to Amberflo’s customerId.
- Limits which labels are sent to Amberflo for ingestion.
- Authenticates using your Amberflo API key.


[title] Workloads
[path] Cost Tracking/

Workloads are Amberflo’s way of representing **internal systems or components** that you want to attribute usage and cost to.

They provide a structured way to answer a simple question:

**Where is this usage happening, and what is it costing us?**

## **What is a Workload?**

A workload represents a **unit of infrastructure or functionality** within your system.

Examples include:

- An image generation pipeline
- A customer support chatbot
- A recommendation engine
- A background processing job
- GPU clusters or compute resources
- Any service or system that drives usage

Workloads are not tied to customers. They are **internal constructs** used to organize and analyze usage and cost.

## **How Workloads Fit into the System**

Workloads are used during **metering**.

When you send usage data, you can attach a workload identifier. That usage is then:

- **Attributed to that workload**
- **Rated into cost using your cost models**
- **Aggregated into spend views**

The same usage data can also be used for billing. You are not forced to separate them.

You can:

- Use metering **only for cost tracking**
- Use metering **only for billing**
- Use the same meter for **both cost and billing**

Workloads sit on the cost side, but they work with the same underlying usage data.

## **Workloads Page Overview**

Navigate to the **Workloads** page to view and manage your workloads.

### **What You’ll See**

Each row in the table represents a workload and includes:

- **Workload Name**
- **Current Spend (Month-to-Date)**
- **Created Date**

### **Expanding a Workload**

Click on a workload row to expand it.

You will see:

- A list of **previous months**
- Spend totals for each month

### **Drill Into Monthly Spend**

Click on a specific month to view detailed cost data.

This opens a breakdown similar to an invoice, where you can see:

- All usage attributed to that workload
- How that usage was rated into cost
- The components contributing to total spend

This is how you move from high-level totals to detailed cost analysis.

## **Creating a Workload**

### **Navigate**

- Go to the **Workloads** page
- Click **Create Workload** (top right)

### **Required Fields**

A workload consists of two fields:

- **Name**
  A human-readable label
  Example: Image Generation Pipeline
- **Workload ID**
  A unique identifier used in meteringConstraints:
  - Letters, numbers, hyphens, underscores only
  - No spaces

Click **Save** once complete.

## **How Workloads Are Used in Metering**

The **Workload ID** is critical.

When you send usage data to a meter, you include this ID as part of the workload dimension.

Example flow:

1. Create a workload with ID image\_pipeline
2. Send usage with workload = image\_pipeline
3. Amberflo attributes that usage to the workload
4. Cost is calculated and aggregated under that workload

Without this linkage, usage cannot be attributed to internal systems.

## **Why Workloads Matter**

Workloads allow you to:

- Understand **which parts of your system drive cost**
- Compare spend across different services or pipelines
- Identify **high-cost components**
- Track trends over time
- Make informed optimization decisions

This is typically used by **engineering and infrastructure teams** to:

- Analyze system efficiency
- Monitor cost growth
- Prioritize optimization efforts

## **Summary**

Workloads are the foundation for internal cost attribution.

They:

- Represent parts of your infrastructure
- Receive attributed usage through metering
- Aggregate cost based on your rate models
- Enable detailed analysis through spend views

If you want to understand how your system consumes resources and where your costs come from, workloads are where that starts.

[title] Hooks
[path] Billing & Monetization/Customer Billing Portal/Embeddable UI Kit - React.js/

We provide several hooks to access the same data used in our components.

Hooks are used to **retrieve data directly from the API**, and they return the same data shown in the widgets. The key benefit is that the data is automatically cached, helping your app run more efficiently.

You can use these hooks if you want to:

- Build and render your own components using Amberflo data
- Track the loading status or data availability of a component
- Subscribe to updates used in the widgets

Each widget’s documentation specifies which hooks it uses.

All hooks can be imported from:

```javascript
import { ... } from '@amberflo/uikit`
```

And they can be used as:

```javascript
const { data, isFetching, isError, error, isSuccess, isIdle } = useHookName(<some parameters>)
```

**Notice**

- Amberflo hooks are supported in React 16.8 and above. If you're using an older version of React, hooks will not be available.
- Additionally, hooks can only be used within functional components. If you're working with class components, you’ll need to either refactor them into functional components or use a Higher-Order Component (HOC) as a workaround.

### Common attributes

Every Amberflo hook returns an object containing the following keys:

- `data`: The response returned by the hook. The structure and contents of this value depend on the specific hook being used.
- `isFetching`: A boolean indicating whether the hook is currently fetching data. Useful for displaying loading indicators.
- `isError`: A boolean that becomes `true` if an error occurred while fetching data.
- `error`: Contains the error object or message if the hook encountered an issue during data retrieval.
- `isSuccess`: A boolean indicating that the data fetch has completed successfully.
- `isIdle`: A boolean indicating that the hook is not currently fetching data and is in an idle state—whether due to completion or failure of the fetch.


### **Available hooks are**

### useCost

**Parameters**

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/AP4gUpbl_X4o-Op7n8Lug_image.png)

This hook will internally call the  [cost API call](docId:_gYayvV8x8xi6rnbwYs4M) . And returns it's data.

**Structure of the hook:**

```javascript
const useCost = (
  startDate: number,
  endDate: number,
  period: string
) => {
  data: CostResponse
  /* This hook will have all common attributes also */
}
```

You can see all [common attributes here](docId\:wMqXhaR7t8Fwot5qkfWHw) .

### useCreditsLedger

*No parameters are required*

This hook will internally call the [customer credits ledger api call](docId\:TS5TGocbLhan4EF1zYgda) and return its data.

**Structure of the hook**

```javascript
const {
    data: creditsLedger,
    /* This hook will have all common attributes also */
  }
```

You can see all [common attributes here](docId\:wMqXhaR7t8Fwot5qkfWHw).

### useCurrentPricingPlan

*No parameters are required*

This hook will internally call the [current pricing plan details api call](docId\:KL74kaS4_pDFhTdWo8EIr) and return its data.

Structure of the hook:

```javascript
const useCurrentPricingPlan = () => {
  data: PricingPlanResponse,
  handleSelectPlan: (pricingPlanId: string) => void
  /* This hook will have all common attributes also */
}
```

### usePricingPlanDetails

*No parameters are required*

This hook will internally call the [current pricing plan details api call](docId\:KL74kaS4_pDFhTdWo8EIr) and the [set new pricing plan api call](docId\:wuHSySgtDPbWsDd-kSVGh)  a pricing plan and return its data.

**Structure of the hook:**

```javascript
const usePricingPlanDetails = () => {
  data: PricingPlanResponse,
  handleSelectPlan: (pricingPlanId: string) => void
  /* This hook will have all common attributes also */
}
```

`handleSelectPlan`will receive a new pricing plan, and set it to the customer.

You can see all [common attributes here](docId\:wMqXhaR7t8Fwot5qkfWHw).

### useInvoiceDetails

**Parameters**

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/_jcF9VIR_aJ3tc3ELOV7G_image.png)

This hook will internally call the [get customer portal api call](docId\:GJQ-DFmyvvZpkrfNkOQ--) and return its data.

**Structure of the hook:**

```javascript
const useInvoiceDetails = (
  invoiceDate?: string,
  planId?: string,
  enabled?: boolean = true
) => {
  data: InvoiceType
  /* This hook will have all common attributes also */
}
```

You can see all [common attributes here](docId\:wMqXhaR7t8Fwot5qkfWHw).

### useLatestInvoice

*No parameters are required*

This hook will internally call the [get customer api](docId\:yCtmA72Y3qwD9doJW83P2)  and return its data.

**Structure of the hook:**

```javascript
const useLatestInvoice = () => {
  data: InvoiceType
  /* This hook will have all common attributes also */
}
```

You can see all [common attributes here](docId\:wMqXhaR7t8Fwot5qkfWHw).

### useInvoices

*No parameters are required*

This hook will internally call the [get all cutomer invoices api call](docId\:Zz5H6X6h1QrMovlGItbbW) and return its data.

**Structure of the hook:**

```javascript
const useInvoices = () => {
  data: InvoiceType[]
  /* This hook will have all common attributes also */
}
```

You can see all [common attributes here](docId\:wMqXhaR7t8Fwot5qkfWHw).

### useMeters

*No parameters are required*

This hook will internally call the [get all meters api call](docId\:i66o22RFPYjUNKud2fZ6i) and return its data.

**Structure of the hook:**

```javascript
cosnt useMeters = () => {
  data: Meter[]
  /* This hook will have all common attributes also */
}
```

You can see all [common attributes here](docId\:wMqXhaR7t8Fwot5qkfWHw).

### usePrepaidSummary

*No parameters are required*

This hook will internally call the [customer prepaid wallet api call](docId\:FqY2I42oVs3vVLlKkw9gh) and return its data.

**Structure of the hook**

```javascript
const usePrepaidSummary = () => {
  data: PrepaidItem[]
  /* This hook will have all common attributes also */
}
```

You can see all [common attributes here](docId\:wMqXhaR7t8Fwot5qkfWHw).

### useProductItems

*No parameters are required*

This hook will internally call the `/product-items`api call. Returning a list of product items, including its: `id`, `description`, `lastUpdateTimeInMillis`, `lockingStatus`, `productId`, `meterApiName`, `productItemName`.

**Structure of the hook:**

```javascript
const useProductItems = () => {
  data: ProductItemDefinition[]
  /* This hook will have all common attributes also */
}
```

You can see all [common attributes here](docId\:wMqXhaR7t8Fwot5qkfWHw).

### useStripeClientSecret

*No parameters are required*

This hook will internally call the [customer setup intent api call](docId:74SI-4wUuwXpfl8vj8IMv) and return its data.

**Structure of the hook:**

```javascript
const useStripeClientSecret = () => {
  data: string
  /* This hook will have all common attributes also */
}
```

You can see all [common attributes here](docId\:wMqXhaR7t8Fwot5qkfWHw).

### useUsage

**Parameters**

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/9MLMLGwEmBhP90g0alqFp_image.png)

**Structure of the hook:**

```javascript
const useUsage = (
  meters: Array<{ meterApiName: string; filter?: Record<string, string[]> }>,
  startDate: number,
  endDate: number,
  period: string,
  groupBy?: string
) => {
  data: UsageResponse[]
  /* This hook will have all common attributes also */
}
```

You can see all [common attributes here](docId\:wMqXhaR7t8Fwot5qkfWHw).

[title] MongoDB
[path] Metering/Meter Integration Options/

## Ingest Meters from MongoDB

Amberflo supports metering data ingestion from **MongoDB**, allowing you to set up metering **alongside your current system** without disruption.

If your system already uses MongoDB, you can configure Amberflo to ingest data without impacting existing functionality.

### **Recommended Methods for MongoDB Integration**

We recommend the following approaches to ingest data from MongoDB into Amberflo:

### 1.Using Logstash

Logstash is an open-source data collection engine with real-time data pipelining capabilities.

- It can **unify data from different sources**, normalize it, and forward it to your target systems.
- By using Logstash’s **input, output, filtering, and buffering** capabilities, you can reliably move data from MongoDB to Amberflo.

**Plugin Setup:**

- Use either the **MongoDB input plugin** or the **JDBC input plugin**
- Configure the **S3 output plugin** to write to the **Amberflo-provided S3 bucket**

Once data is in the S3 bucket, Amberflo will automatically pick it up for ingestion.
[Elastic Logstash](docId:8cHAoPY04KVMaRbaid5wV)
[https://www.elastic.co/guide/en/logstash/current/plugins-inputs-jdbc.html](https://www.elastic.co/guide/en/logstash/current/plugins-inputs-jdbc.html)
[https://github.com/phutchins/logstash-input-mongodb](https://github.com/phutchins/logstash-input-mongodb)

### 2.Query MongoDB using code

You can also query **MongoDB programmatically** using its SDK and then use the **Amberflo SDK** to ingest the retrieved data.

### **Example (Python)**

- Use **PyMongo** to query data from MongoDB.
- Use the **Amberflo Python SDK** to ingest the results as meter records.

This approach gives you full control over:

- **Filtering and transforming** data before ingestion
- **Handling new data only**, by persisting the **last processed timestamp or ID**

Amberflo automatically performs **deduplication**, so repeated ingestion of the same data will not create duplicates.

### 3.Use MongoDB's Change data stream

With the Amberflo SDK, you can use the MongoDB real-time data stream to act upon every insert:
[https://developer.mongodb.com/quickstart/python-change-streams/
](https://developer.mongodb.com/quickstart/python-change-streams/)
[Python SDK](https://docs.amberflo.io/docs/python-library)

::::CodeDrawer{title="1. Install the SDK" codeEditorData="[object Object]" responsesEditorData="[object Object]" isResponseExpanded="true" autoGeneratedAnchorSlug="1-install-the-sdk" legacyHash="s1hK-Y_GvP9YKv-QZ1nAq"}
:::CodeblockTabsExamples
```none
Set up virtual environment and install SDK.
```
:::

:::CodeblockTabsResponses
```javascript
```
:::
::::

::::CodeDrawer{title="2. Get your API key - Go to Settings and copy your API key." codeEditorData="[object Object]" responsesEditorData="[object Object]" isResponseExpanded="true" autoGeneratedAnchorSlug="2-get-your-api-key-go-to-settings-and-copy-your-ap" legacyHash="l5s02qu9QA9NInmUIPXNB"}
:::CodeblockTabsExamples
```python
# Set up virtual environment and install SDK
#
#   $ python3 -m virtualenv venv
#   $ . venv/bin/activate
#   $ pip install amberflo-metering-python

import os

# Sign up for free at https://ui.amberflo.io/ to get an API key
API_KEY = os.environ["AMBERFLO_API_KEY"]


```
:::

:::CodeblockTabsResponses
```javascript
```
:::
::::

::::CodeDrawer{title="3. Set up the Customer API client - You can use it to manage your customers. Meter records are associated with customers." codeEditorData="[object Object]" responsesEditorData="[object Object]" isResponseExpanded="true" autoGeneratedAnchorSlug="3-set-up-the-customer-api-client-you-can-use-it-to" legacyHash="laWKN4mWUTTHE_OuWT3If"}
:::CodeblockTabsExamples
```python
# Set up virtual environment and install SDK
#
#   $ python3 -m virtualenv venv
#   $ . venv/bin/activate
#   $ pip install amberflo-metering-python

import os

# Sign up for free at https://ui.amberflo.io/ to get an API key
API_KEY = os.environ["AMBERFLO_API_KEY"]

# Set up customer API client and create a customer
from metering.customer import CustomerApiClient, create_customer_payload

customer_api = CustomerApiClient(os.environ.get("API_KEY"))
```
:::

:::CodeblockTabsResponses
```javascript
```
:::
::::

::::CodeDrawer{title="4. Create a customer" codeEditorData="[object Object]" responsesEditorData="[object Object]" isResponseExpanded="true" autoGeneratedAnchorSlug="4-create-a-customer" legacyHash="wrm9Z-RTopL95_ustST5R"}
:::CodeblockTabsExamples
```python
# Set up virtual environment and install SDK
#
#   $ python3 -m virtualenv venv
#   $ . venv/bin/activate
#   $ pip install amberflo-metering-python

import os

# Sign up for free at https://ui.amberflo.io/ to get an API key
API_KEY = os.environ["AMBERFLO_API_KEY"]

# Set up customer API client and create a customer
from metering.customer import CustomerApiClient, create_customer_payload

customer_api = CustomerApiClient(os.environ.get("API_KEY"))

message = create_customer_payload(
    customer_id="customer-123",
    customer_name="Customer 123",
    # email is optional
    customer_email="customer-123@example.com",
    # traits are optional. They can be used as filters or aggregation buckets
    traits={
        "region": "us-east-1",
    },
)
customer = customer_api.add_or_update(message)
```
:::

:::CodeblockTabsResponses
```javascript
```
:::
::::

::::CodeDrawer{title="5. Set up Ingest API client - By default, it will collect the meter records and send them in batches asynchronously." codeEditorData="[object Object]" responsesEditorData="[object Object]" isResponseExpanded="true" autoGeneratedAnchorSlug="5-set-up-ingest-api-client-by-default-it-will-coll" legacyHash="ascP-vjDnyjQ-1S-LYZth"}
:::CodeblockTabsExamples
```python
# Set up virtual environment and install SDK
#
#   $ python3 -m virtualenv venv
#   $ . venv/bin/activate
#   $ pip install amberflo-metering-python

import os

# Sign up for free at https://ui.amberflo.io/ to get an API key
API_KEY = os.environ["AMBERFLO_API_KEY"]

# Set up customer API client and create a customer
from metering.customer import CustomerApiClient, create_customer_payload

customer_api = CustomerApiClient(os.environ.get("API_KEY"))

message = create_customer_payload(
    customer_id="customer-123",
    customer_name="Customer 123",
    # email is optional
    customer_email="customer-123@example.com",
    # traits are optional. They can be used as filters or aggregation buckets
    traits={
        "region": "us-east-1",
    },
)
customer = customer_api.add_or_update(message)

# Set up ingest API client and ingest a meter
from time import time
from metering.ingest import create_ingest_client

ingest_api = create_ingest_client(api_key=API_KEY)
```
:::

:::CodeblockTabsResponses
```javascript
```
:::
::::

::::CodeDrawer{title="6. Send a meter record - Meter records will be collected in batches and sent asynchronously." codeEditorData="[object Object]" responsesEditorData="[object Object]" isResponseExpanded="true" autoGeneratedAnchorSlug="6-send-a-meter-record-meter-records-will-be-collec" legacyHash="8SerLfqWt9QZ0NPAOlvEv"}
:::CodeblockTabsExamples
```python
# Set up virtual environment and install SDK
#
#   $ python3 -m virtualenv venv
#   $ . venv/bin/activate
#   $ pip install amberflo-metering-python

import os

# Sign up for free at https://ui.amberflo.io/ to get an API key
API_KEY = os.environ["AMBERFLO_API_KEY"]

# Set up customer API client and create a customer
from metering.customer import CustomerApiClient, create_customer_payload

customer_api = CustomerApiClient(os.environ.get("API_KEY"))

message = create_customer_payload(
    customer_id="customer-123",
    customer_name="Customer 123",
    # email is optional
    customer_email="customer-123@example.com",
    # traits are optional. They can be used as filters or aggregation buckets
    traits={
        "region": "us-east-1",
    },
)
customer = customer_api.add_or_update(message)

# Set up ingest API client and ingest a meter
from time import time
from metering.ingest import create_ingest_client

ingest_api = create_ingest_client(api_key=API_KEY)

ingest_api.meter(
    meter_api_name="api-calls",
    meter_value=1,
    meter_time_in_millis=int(time() * 1000),
    customer_id=customer["customerId"],
)
```
:::

:::CodeblockTabsResponses
```javascript
```
:::
::::

::::CodeDrawer{title="7. Shut down Ingest API client - This is a blocking call that ensures all batches have been sent. Use it in your server shutdown procedure." codeEditorData="[object Object]" responsesEditorData="[object Object]" isResponseExpanded="true" autoGeneratedAnchorSlug="7-shut-down-ingest-api-client-this-is-a-blocking-c" legacyHash="Upf5u6ZnDIh5_3dThkYA0"}
:::CodeblockTabsExamples
```python
# Set up virtual environment and install SDK
#
#   $ python3 -m virtualenv venv
#   $ . venv/bin/activate
#   $ pip install amberflo-metering-python

import os

# Sign up for free at https://ui.amberflo.io/ to get an API key
API_KEY = os.environ["AMBERFLO_API_KEY"]

# Set up customer API client and create a customer
from metering.customer import CustomerApiClient, create_customer_payload

customer_api = CustomerApiClient(os.environ.get("API_KEY"))

message = create_customer_payload(
    customer_id="customer-123",
    customer_name="Customer 123",
    # email is optional
    customer_email="customer-123@example.com",
    # traits are optional. They can be used as filters or aggregation buckets
    traits={
        "region": "us-east-1",
    },
)
customer = customer_api.add_or_update(message)

# Set up ingest API client and ingest a meter
from time import time
from metering.ingest import create_ingest_client

ingest_api = create_ingest_client(api_key=API_KEY)

ingest_api.meter(
    meter_api_name="api-calls",
    meter_value=1,
    meter_time_in_millis=int(time() * 1000),
    customer_id=customer["customerId"],
)

# Shut down ingest API client
ingest_api.shutdown()
```
:::

:::CodeblockTabsResponses
```javascript
```
:::
::::

::::CodeDrawer{title="8. Set up Usage API client" codeEditorData="[object Object]" responsesEditorData="[object Object]" isResponseExpanded="true" autoGeneratedAnchorSlug="8-set-up-usage-api-client" legacyHash="eDWJRZOoD75CtMihdHnub"}
:::CodeblockTabsExamples
```python
# Set up virtual environment and install SDK
#
#   $ python3 -m virtualenv venv
#   $ . venv/bin/activate
#   $ pip install amberflo-metering-python

import os

# Sign up for free at https://ui.amberflo.io/ to get an API key
API_KEY = os.environ["AMBERFLO_API_KEY"]

# Set up customer API client and create a customer
from metering.customer import CustomerApiClient, create_customer_payload

customer_api = CustomerApiClient(os.environ.get("API_KEY"))

message = create_customer_payload(
    customer_id="customer-123",
    customer_name="Customer 123",
    # email is optional
    customer_email="customer-123@example.com",
    # traits are optional. They can be used as filters or aggregation buckets
    traits={
        "region": "us-east-1",
    },
)
customer = customer_api.add_or_update(message)

# Set up ingest API client and ingest a meter
from time import time
from metering.ingest import create_ingest_client

ingest_api = create_ingest_client(api_key=API_KEY)

ingest_api.meter(
    meter_api_name="api-calls",
    meter_value=1,
    meter_time_in_millis=int(time() * 1000),
    customer_id=customer["customerId"],
)

# Shut down ingest API client
ingest_api.shutdown()

# Setup usage API client and get a usage report
from metering.usage import (AggregationType, Take, TimeGroupingInterval,
                            TimeRange, UsageApiClient, create_usage_query)

usage_api = UsageApiClient(os.environ.get("API_KEY"))
```
:::

:::CodeblockTabsResponses
```javascript
```
:::
::::

::::CodeDrawer{title="9. Get a usage report" codeEditorData="[object Object]" responsesEditorData="[object Object]" isResponseExpanded="true" autoGeneratedAnchorSlug="9-get-a-usage-report" legacyHash="u0aFC0fgnQyLUmE2THMdm"}
:::CodeblockTabsExamples
```python
# Set up virtual environment and install SDK
#
#   $ python3 -m virtualenv venv
#   $ . venv/bin/activate
#   $ pip install amberflo-metering-python

import os

# Sign up for free at https://ui.amberflo.io/ to get an API key
API_KEY = os.environ["AMBERFLO_API_KEY"]

# Set up customer API client and create a customer
from metering.customer import CustomerApiClient, create_customer_payload

customer_api = CustomerApiClient(os.environ.get("API_KEY"))

message = create_customer_payload(
    customer_id="customer-123",
    customer_name="Customer 123",
    # email is optional
    customer_email="customer-123@example.com",
    # traits are optional. They can be used as filters or aggregation buckets
    traits={
        "region": "us-east-1",
    },
)
customer = customer_api.add_or_update(message)

# Set up ingest API client and ingest a meter
from time import time
from metering.ingest import create_ingest_client

ingest_api = create_ingest_client(api_key=API_KEY)

ingest_api.meter(
    meter_api_name="api-calls",
    meter_value=1,
    meter_time_in_millis=int(time() * 1000),
    customer_id=customer["customerId"],
)

# Shut down ingest API client
ingest_api.shutdown()

# Setup usage API client and get a usage report
from metering.usage import (AggregationType, Take, TimeGroupingInterval,
                            TimeRange, UsageApiClient, create_usage_query)

usage_api = UsageApiClient(os.environ.get("API_KEY"))

since_two_days_ago = TimeRange(int(time()) - 60 * 60 * 24 * 2)

query = create_usage_query(
    meter_api_name="api-calls",
    aggregation=AggregationType.SUM,
    time_grouping_interval=TimeGroupingInterval.DAY,
    time_range=since_two_days_ago,
    group_by=["customerId"],
    usage_filter={"customerId": ["customer-123"]},
    take=Take(limit=10, is_ascending=False),
)
report = usage_api.get(query)
```
:::

:::CodeblockTabsResponses
```javascript
```
:::
::::


[title] AWS S3
[path] Metering/Meter Integration Options/

# Write events directly to an Amberflo supplied and secured AWS S3 bucket

Amberflo provisions an **AWS S3 bucket** for your account with the necessary **access rights and permissions** to write meter data.

- **Meter files are automatically picked up** for processing as soon as they arrive in the bucket.
- If processing fails, **error reports are written back** into the same bucket for review.

There are several ways to write data to S3, including:

- **AWS S3 SDK**
- **AWS Glue**
- **Logstash**
- **Fluentd**
- And other ingestion tools

To get your S3 bucket provisioned, please contact us.

## Format

The **meter records** you send to the S3 bucket should be of the same standardized format as accepted by the ingestion API. Here are some examples:

```json
[{
     "customerId": "customer-123",
     "meterApiName": "ComputeHours",
     "meterValue": 5,
     "meterTimeInMillis": 1619445706909,
     "dimensions": {
        "region": "us-west-2",
        "az": "az1"
     }
}]
```

We also support [NDJSON](https://github.com/ndjson/ndjson-spec) format (JSON separated by a newline)

```json
{ "customerId": "customer-123", "meterApiName": "ComputeHours", "meterValue": 5, "meterTimeInMillis": 1619445706909 }
{ "customerId": "customer-321", "meterApiName": "ComputeHours", "meterValue": 4, "meterTimeInMillis": 1619445712341 }
{ "customerId": "customer-123", "meterApiName": "ComputeHours", "meterValue": 1, "meterTimeInMillis": 1619445783456 }
```

## Code Example

The S3 object key should have the date to allow easier troubleshooting (e.g.: `/ingest/amberdata/06-07-2022/0000a2e4-e6ad-11ec-8293-6a8da1c4f9f0`);

```python
import json
from uuid import uuid1
from datetime import date

import boto3

records_to_send = [{
     'customerId': 'customer-123',
     'meterApiName': 'ComputeHours',
     'meterValue': 5,
     'meterTimeInMillis': 1619445706909,
     'dimensions': {
        'region': 'us-west-2',
        'az': 'az1'
     }
}]

bucket_name = '183-amberflo'

date = datetime.now().strftime(r'%m-%d-%Y')

object_key = 'ingest/amberdata/' + date + '/' + str(uuid1())

s3 = boto3.resource('s3')

s3.Object(bucket_name, object_key).put(Body=json.dumps(records_to_send), ACL='bucket-owner-full-control')
```

## Troubleshooting

If you encounter issues while ingesting data through S3, Amberflo will generate a **failure report&#x20;**&#x69;n the following S3 path:

`s3://<bucket_name>/failed-requests/<date>/<original uri>.reason.txt`

This file contains the reason for the failure and can help you diagnose and resolve the issue.

## Compression (gzip support)

Amberflo supports ingesting **gzip-compressed files**.

- Simply upload files with the **.gz extension**
- Amberflo will **automatically decompress** these files during ingestion


[title] Data Export
[path] Metering/

## **Exporting Usage Data**

Amberflo allows you to export usage data so it can be analyzed, archived, shared with internal teams, or loaded into downstream systems. Usage exports are useful when you need a recurring feed of metered usage outside of Amberflo, such as for data warehousing, finance workflows, customer reporting, or custom analytics.

Exports can be configured to run on a schedule, delivered to cloud storage, and formatted for the systems that will consume the data.

## **What You Can Export**

Usage exports allow you to extract metered usage data from Amberflo over a defined time range. This can include usage records used for:

- Internal reporting
- Cost allocation
- Customer billing analysis
- Custom dashboards
- Finance or revenue operations workflows
- Data warehouse ingestion
- Audit and reconciliation processes

Because Amberflo stores usage as structured meter events, exported data can include the usage values, timestamps, meters, customers, dimensions, and other metadata needed to analyze consumption outside the application.

## **Export Formats**

Amberflo supports multiple export formats so you can choose the format that best fits your downstream system:

- **CSV**: Best for spreadsheets, finance workflows, and simple data analysis.
- **JSON**: Best for systems that consume structured objects or API-style payloads.
- **NDJSON**: Best for large-scale ingestion pipelines where each line is processed as a separate JSON record.

The right format depends on how the data will be used. CSV is usually easiest for business users, while JSON and NDJSON are better suited for engineering workflows and automated pipelines.

## **Scheduled Exports**

Usage exports can run on a recurring schedule. This allows teams to receive fresh usage data automatically without manually pulling reports.

Supported recurrence options include:

- Daily
- Weekly
- Monthly
- Yearly

Scheduled exports are useful for recurring finance close processes, regular customer reporting, internal chargeback workflows, or syncing usage data into a data warehouse.

## **Time Ranges and Export Windows**

Each export can be configured with a time range that defines which usage records should be included. This allows you to export usage for a specific reporting period or create recurring exports that look back over a defined window.

For example, a daily export might include usage from the previous day, while a monthly export might include usage from the prior billing or reporting period.

This helps ensure downstream systems receive the right data for the correct operational or financial window.

## **Delivery Options**

Usage exports can be delivered to an S3 bucket. This makes it easy to integrate Amberflo usage data with existing cloud data pipelines, analytics tools, or internal storage processes.

When configuring delivery, you will provide the destination bucket details so Amberflo can write the export output to the correct location.

## **Notifications**

You can provide an email address for export notifications. This email can be used to notify the appropriate team if an export fails to run successfully.

This is useful for operational monitoring, especially when exports support billing, reporting, or other time-sensitive workflows.

## **Common Use Cases**

Usage exports are commonly used to support:

- **Finance reporting**: Export usage data for revenue analysis, reconciliation, or billing review.
- **Customer reporting**: Provide customers with detailed usage records or power customer-facing dashboards.
- **Data warehouse ingestion**: Load usage data into systems such as Snowflake, BigQuery, Redshift, or Databricks.
- **Internal analytics**: Combine Amberflo usage data with product, customer, or infrastructure data.
- **Audit and compliance**: Maintain historical usage records outside Amberflo for review or retention.
- **Chargeback and showback workflows**: Share detailed usage records with internal business units or departments.

## **Benefits**

Exporting usage data gives teams more flexibility in how they use Amberflo data across the business:

- Keep external systems synchronized with metered usage
- Support recurring reporting without manual work
- Give finance, engineering, and analytics teams access to the same usage source of truth
- Preserve detailed usage records for audits and reconciliation
- Build custom dashboards or workflows outside Amberflo
- Automate downstream billing, allocation, or reporting processes

## **Summary**

Usage exports provide a flexible way to move metered usage data from Amberflo into the systems where your teams need it. You can choose the export format, define the reporting window, schedule recurring exports, and deliver the results to cloud storage for downstream processing.

[title] Instructions to Export Usage Data
[path] Metering/Data Export/

Jobs to export data can be configured by calling the Jobs API.

### API Endpoints

| Method   | Endpoint                             | Description                     |
| -------- | ------------------------------------ | ------------------------------- |
| `GET`    | `https://app.amberflo.io/jobs`       | List all jobs                   |
| `GET`    | `https://app.amberflo.io/jobs/{id}`  | Get a job by ID                 |
| `POST`   | `https://app.amberflo.io/jobs`       | Create a job                    |
| `PUT`    | `https://app.amberflo.io/jobs`       | Update a job                    |
| `DELETE` | `https://app.amberflo.io/jobs/{id}`  | Delete a job by ID              |
| `POST`   | `https://app.amberflo.io/jobs/check` | Validate a job without creating |

All endpoints require the `X-API-KEY` header for authentication.

### Required Fields

| Field               | Description                                                                                                                                  |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `jobName`           | Unique name (alphanumeric, hyphens, spaces, commas, parentheses only)                                                                        |
| `s3Url`             | S3 bucket/path where exported data will be written                                                                                           |
| `format`            | Output format: `json`, `csv`, or `ndjson`                                                                                                    |
| `aggregationBucket` | Time granularity: `hour`, `day`, `week`, or `month`                                                                                          |
| `range`             | Lookback window in format `ago(#x)` where `#` is a positive integer and `x` is `d` (days), `h` (hours), or `m` (minutes). Example: `ago(7d)` |

### Scheduling (pick one)

| Field                    | Description                                                                   |
| ------------------------ | ----------------------------------------------------------------------------- |
| `cron`                   | Standard 5-field cron expression for recurring execution (e.g. `*/5 * * * *`) |
| `scheduledTimeInSeconds` | Unix epoch timestamp (seconds) for a one-off execution                        |

See [https://crontab.guru/](https://crontab.guru/) for constructing cron expression.

:::BlockQuote
**Note:** `cron` and `scheduledTimeInSeconds` are mutually exclusive. Provide one or neither.
:::

### Optional Fields

| Field             | Default | Description                                                    |
| ----------------- | ------- | -------------------------------------------------------------- |
| `description`     | `null`  | Human-readable description                                     |
| `email`           | `null`  | Notification email address                                     |
| `enabled`         | `true`  | Whether the job is active                                      |
| `sparse`          | `true`  | Return sparse query results                                    |
| `minimizeFresh`   | `true`  | Minimize fresh data in results                                 |
| `endTimeOffset`   | `null`  | End time offset in `ago(#x)` format                            |
| `meterApiNames`   | `null`  | Array of meter API names to query                              |
| `groupBy`         | `null`  | Array of dimensions to group by                                |
| `filter`          | `null`  | Object mapping dimension names to arrays of allowed values     |
| `rescaleFunction` | `null`  | Object with `function` (`multiply`) and `coefficient` (number) |

### Create a Job

```bash
curl -X POST https://app.amberflo.io/jobs \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: <YOUR_API_KEY>" \
  -d '{
    "jobName": "Daily export",
    "s3Url": "my-usage-bucket",
    "format": "csv",
    "aggregationBucket": "day",
    "range": "ago(7d)",
    "cron": "0 2 * * *",
    "enabled": true,
    "meterApiNames": ["api_calls", "storage"],
    "groupBy": ["customer_id"],
    "filter": {
      "region": ["us-east-1", "eu-west-1"]
    }
  }'
```

### Update a Job

```bash
curl -X PUT https://app.amberflo.io/jobs \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: <YOUR_API_KEY>" \
  -d '{
    "id": "<JOB_ID>",
    "jobName": "Daily export",
    "s3Url": "my-usage-bucket",
    "format": "csv",
    "aggregationBucket": "day",
    "range": "ago(7d)",
    "cron": "0 3 * * *",
    "enabled": true
  }'
```

### Delete a Job

```bash
curl -X DELETE https://app.amberflo.io/jobs/<JOB_ID> \
  -H "X-API-KEY: <YOUR_API_KEY>"
```

### Validate a Job (dry run)

```bash
curl -X POST https://app.amberflo.io/jobs/check \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: <YOUR_API_KEY>" \
  -d '{
    "jobName": "Daily export",
    "s3Url": "my-usage-bucket",
    "format": "csv",
    "aggregationBucket": "day",
    "range": "ago(7d)",
    "cron": "0 2 * * *"
  }'
```

### Validation

Use `POST /jobs/check` to validate a job configuration without creating it. It returns the validated job and the query that would be built.

### S3 Bucket Permissions

The user must grant Amberflo cross-account write access to their S3 bucket. The only permission required is `s3:PutObject`.

Add a bucket policy like:

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::<AMBERFLO_ACCOUNT_ID>:root"
      },
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::<CUSTOMER_BUCKET>/*",
      "Condition": {
        "StringEquals": {
          "s3:x-amz-acl": "bucket-owner-full-control"
        }
      }
    }
  ]
}
```

Objects are written with the `bucket-owner-full-control` ACL so the customer retains full ownership of the files in their bucket. No read, list, or delete permissions are needed.

[title] API Gateway - Kong
[path] Metering/Ingestion Options/

You can use Amberflo's API Metering plugin for [Kong](https://docs.konghq.com/gateway/latest/) to:

- **Meter API requests** handled by your Kong instance
- **Monetize your APIs** based on usage

For implementation details, see the [GitHub repository](https://github.com/amberflo/kong-plugin-amberflo)

## &#x20;How it Works

1. **Request Interception**
   &#x20;The plugin intercepts incoming API requests handled by Kong.
2. **Customer Identification**
   &#x20;It determines which customer is making the request by inspecting request headers.
   &#x20;You can configure Kong to inject the customerId header **before** this plugin runs.
   - Example: If you use [Kong's Key Authentication plugin](https://docs.konghq.com/hub/kong-inc/key-auth/), this is done automatically.
3. **Meter Event Generation**
   &#x20;The plugin generates a meter event for each request.
4. **Efficient Metering**
   &#x20;To avoid performance overhead:
   - Meter records are **batched**
   - Records are sent to Amberflo **asynchronously**

This setup ensures high-throughput usage tracking with **minimal impact on gateway performance**.

## &#x20;Installation

1. **Compile the Plugin Binary**
   Compile the plugin and make the resulting binary available to your Kong instance.
   - **Important:** Ensure your compilation environment matches your Kong runtime environment. A mismatch may result in a binary that won't run correctly.
2. **Update Kong Configuration**&#x20;
   Add the plugin to your Kong configuration.
   &#x20;For example, if you place the plugin server binary at: `/opt/amberflo/metering` then the relevant sections of your kong.conf file should resemble [this one.](https://github.com/amberflo/kong-plugin-amberflo/blob/main/kong.conf) For more detailed guidance on configuring Kong, refer to their official documentation [here](https://docs.konghq.com/gateway/latest/plugin-development/pluginserver/go/#example-configuration) and [here](https://docs.konghq.com/gateway/latest/reference/configuration/).
3. **Enable the plugin**
   You can enable the plugin in one of two ways:
   - Add it to your kong.yaml file
   - Or enable it via a call to the **Kong Admin API**

## Configuration

Please find a sample configuration file [here](https://github.com/amberflo/kong-plugin-amberflo/blob/main/metering.json).

Here's a breakdown of the fields and their respective meanings.

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/JPotmyY8MZvKiZtfIfdW0_image.png)


[title] MongoDB
[path] Metering/Ingestion Options/

## Ingest Meters from MongoDB

Amberflo supports metering data ingestion from **MongoDB**, allowing you to set up metering **alongside your current system** without disruption.

If your system already uses MongoDB, you can configure Amberflo to ingest data without impacting existing functionality.

### **Recommended Methods for MongoDB Integration**

We recommend the following approaches to ingest data from MongoDB into Amberflo:

### 1.Using Logstash

Logstash is an open-source data collection engine with real-time data pipelining capabilities.

- It can **unify data from different sources**, normalize it, and forward it to your target systems.
- By using Logstash’s **input, output, filtering, and buffering** capabilities, you can reliably move data from MongoDB to Amberflo.

**Plugin Setup:**

- Use either the **MongoDB input plugin** or the **JDBC input plugin**
- Configure the **S3 output plugin** to write to the **Amberflo-provided S3 bucket**

Once data is in the S3 bucket, Amberflo will automatically pick it up for ingestion.
[https://www.elastic.co/guide/en/logstash/current/plugins-inputs-jdbc.html](https://www.elastic.co/guide/en/logstash/current/plugins-inputs-jdbc.html)
[https://github.com/phutchins/logstash-input-mongodb](https://github.com/phutchins/logstash-input-mongodb)

### 2.Query MongoDB using code

You can also query **MongoDB programmatically** using its SDK and then use the **Amberflo SDK** to ingest the retrieved data.

### **Example (Python)**

- Use **PyMongo** to query data from MongoDB.
- Use the **Amberflo Python SDK** to ingest the results as meter records.

This approach gives you full control over:

- **Filtering and transforming** data before ingestion
- **Handling new data only**, by persisting the **last processed timestamp or ID**

Amberflo automatically performs **deduplication**, so repeated ingestion of the same data will not create duplicates.

### 3.Use MongoDB's Change data stream

With the Amberflo SDK, you can use the MongoDB real-time data stream to act upon every insert:
[https://developer.mongodb.com/quickstart/python-change-streams/
](https://developer.mongodb.com/quickstart/python-change-streams/)
[Python SDK](https://docs.amberflo.io/docs/python-library)

::::CodeDrawer{title="1. Install the SDK" codeEditorData="[object Object]" responsesEditorData="[object Object]" isResponseExpanded="true" autoGeneratedAnchorSlug="1-install-the-sdk" legacyHash="s1hK-Y_GvP9YKv-QZ1nAq"}
:::CodeblockTabsExamples
```none
Set up virtual environment and install SDK.
```
:::

:::CodeblockTabsResponses
```javascript
```
:::
::::

::::CodeDrawer{title="2. Get your API key - Go to Settings and copy your API key." codeEditorData="[object Object]" responsesEditorData="[object Object]" isResponseExpanded="true" autoGeneratedAnchorSlug="2-get-your-api-key-go-to-settings-and-copy-your-ap" legacyHash="l5s02qu9QA9NInmUIPXNB"}
:::CodeblockTabsExamples
```python
# Set up virtual environment and install SDK
#
#   $ python3 -m virtualenv venv
#   $ . venv/bin/activate
#   $ pip install amberflo-metering-python

import os

# Sign up for free at https://ui.amberflo.io/ to get an API key
API_KEY = os.environ["AMBERFLO_API_KEY"]


```
:::

:::CodeblockTabsResponses
```javascript
```
:::
::::

::::CodeDrawer{title="3. Set up the Customer API client - You can use it to manage your customers. Meter records are associated with customers." codeEditorData="[object Object]" responsesEditorData="[object Object]" isResponseExpanded="true" autoGeneratedAnchorSlug="3-set-up-the-customer-api-client-you-can-use-it-to" legacyHash="laWKN4mWUTTHE_OuWT3If"}
:::CodeblockTabsExamples
```python
# Set up virtual environment and install SDK
#
#   $ python3 -m virtualenv venv
#   $ . venv/bin/activate
#   $ pip install amberflo-metering-python

import os

# Sign up for free at https://ui.amberflo.io/ to get an API key
API_KEY = os.environ["AMBERFLO_API_KEY"]

# Set up customer API client and create a customer
from metering.customer import CustomerApiClient, create_customer_payload

customer_api = CustomerApiClient(os.environ.get("API_KEY"))
```
:::

:::CodeblockTabsResponses
```javascript
```
:::
::::

::::CodeDrawer{title="4. Create a customer" codeEditorData="[object Object]" responsesEditorData="[object Object]" isResponseExpanded="true" autoGeneratedAnchorSlug="4-create-a-customer" legacyHash="wrm9Z-RTopL95_ustST5R"}
:::CodeblockTabsExamples
```python
# Set up virtual environment and install SDK
#
#   $ python3 -m virtualenv venv
#   $ . venv/bin/activate
#   $ pip install amberflo-metering-python

import os

# Sign up for free at https://ui.amberflo.io/ to get an API key
API_KEY = os.environ["AMBERFLO_API_KEY"]

# Set up customer API client and create a customer
from metering.customer import CustomerApiClient, create_customer_payload

customer_api = CustomerApiClient(os.environ.get("API_KEY"))

message = create_customer_payload(
    customer_id="customer-123",
    customer_name="Customer 123",
    # email is optional
    customer_email="customer-123@example.com",
    # traits are optional. They can be used as filters or aggregation buckets
    traits={
        "region": "us-east-1",
    },
)
customer = customer_api.add_or_update(message)
```
:::

:::CodeblockTabsResponses
```javascript
```
:::
::::

::::CodeDrawer{title="5. Set up Ingest API client - By default, it will collect the meter records and send them in batches asynchronously." codeEditorData="[object Object]" responsesEditorData="[object Object]" isResponseExpanded="true" autoGeneratedAnchorSlug="5-set-up-ingest-api-client-by-default-it-will-coll" legacyHash="ascP-vjDnyjQ-1S-LYZth"}
:::CodeblockTabsExamples
```python
# Set up virtual environment and install SDK
#
#   $ python3 -m virtualenv venv
#   $ . venv/bin/activate
#   $ pip install amberflo-metering-python

import os

# Sign up for free at https://ui.amberflo.io/ to get an API key
API_KEY = os.environ["AMBERFLO_API_KEY"]

# Set up customer API client and create a customer
from metering.customer import CustomerApiClient, create_customer_payload

customer_api = CustomerApiClient(os.environ.get("API_KEY"))

message = create_customer_payload(
    customer_id="customer-123",
    customer_name="Customer 123",
    # email is optional
    customer_email="customer-123@example.com",
    # traits are optional. They can be used as filters or aggregation buckets
    traits={
        "region": "us-east-1",
    },
)
customer = customer_api.add_or_update(message)

# Set up ingest API client and ingest a meter
from time import time
from metering.ingest import create_ingest_client

ingest_api = create_ingest_client(api_key=API_KEY)
```
:::

:::CodeblockTabsResponses
```javascript
```
:::
::::

::::CodeDrawer{title="6. Send a meter record - Meter records will be collected in batches and sent asynchronously." codeEditorData="[object Object]" responsesEditorData="[object Object]" isResponseExpanded="true" autoGeneratedAnchorSlug="6-send-a-meter-record-meter-records-will-be-collec" legacyHash="8SerLfqWt9QZ0NPAOlvEv"}
:::CodeblockTabsExamples
```python
# Set up virtual environment and install SDK
#
#   $ python3 -m virtualenv venv
#   $ . venv/bin/activate
#   $ pip install amberflo-metering-python

import os

# Sign up for free at https://ui.amberflo.io/ to get an API key
API_KEY = os.environ["AMBERFLO_API_KEY"]

# Set up customer API client and create a customer
from metering.customer import CustomerApiClient, create_customer_payload

customer_api = CustomerApiClient(os.environ.get("API_KEY"))

message = create_customer_payload(
    customer_id="customer-123",
    customer_name="Customer 123",
    # email is optional
    customer_email="customer-123@example.com",
    # traits are optional. They can be used as filters or aggregation buckets
    traits={
        "region": "us-east-1",
    },
)
customer = customer_api.add_or_update(message)

# Set up ingest API client and ingest a meter
from time import time
from metering.ingest import create_ingest_client

ingest_api = create_ingest_client(api_key=API_KEY)

ingest_api.meter(
    meter_api_name="api-calls",
    meter_value=1,
    meter_time_in_millis=int(time() * 1000),
    customer_id=customer["customerId"],
)
```
:::

:::CodeblockTabsResponses
```javascript
```
:::
::::

::::CodeDrawer{title="7. Shut down Ingest API client - This is a blocking call that ensures all batches have been sent. Use it in your server shutdown procedure." codeEditorData="[object Object]" responsesEditorData="[object Object]" isResponseExpanded="true" autoGeneratedAnchorSlug="7-shut-down-ingest-api-client-this-is-a-blocking-c" legacyHash="Upf5u6ZnDIh5_3dThkYA0"}
:::CodeblockTabsExamples
```python
# Set up virtual environment and install SDK
#
#   $ python3 -m virtualenv venv
#   $ . venv/bin/activate
#   $ pip install amberflo-metering-python

import os

# Sign up for free at https://ui.amberflo.io/ to get an API key
API_KEY = os.environ["AMBERFLO_API_KEY"]

# Set up customer API client and create a customer
from metering.customer import CustomerApiClient, create_customer_payload

customer_api = CustomerApiClient(os.environ.get("API_KEY"))

message = create_customer_payload(
    customer_id="customer-123",
    customer_name="Customer 123",
    # email is optional
    customer_email="customer-123@example.com",
    # traits are optional. They can be used as filters or aggregation buckets
    traits={
        "region": "us-east-1",
    },
)
customer = customer_api.add_or_update(message)

# Set up ingest API client and ingest a meter
from time import time
from metering.ingest import create_ingest_client

ingest_api = create_ingest_client(api_key=API_KEY)

ingest_api.meter(
    meter_api_name="api-calls",
    meter_value=1,
    meter_time_in_millis=int(time() * 1000),
    customer_id=customer["customerId"],
)

# Shut down ingest API client
ingest_api.shutdown()
```
:::

:::CodeblockTabsResponses
```javascript
```
:::
::::

::::CodeDrawer{title="8. Set up Usage API client" codeEditorData="[object Object]" responsesEditorData="[object Object]" isResponseExpanded="true" autoGeneratedAnchorSlug="8-set-up-usage-api-client" legacyHash="eDWJRZOoD75CtMihdHnub"}
:::CodeblockTabsExamples
```python
# Set up virtual environment and install SDK
#
#   $ python3 -m virtualenv venv
#   $ . venv/bin/activate
#   $ pip install amberflo-metering-python

import os

# Sign up for free at https://ui.amberflo.io/ to get an API key
API_KEY = os.environ["AMBERFLO_API_KEY"]

# Set up customer API client and create a customer
from metering.customer import CustomerApiClient, create_customer_payload

customer_api = CustomerApiClient(os.environ.get("API_KEY"))

message = create_customer_payload(
    customer_id="customer-123",
    customer_name="Customer 123",
    # email is optional
    customer_email="customer-123@example.com",
    # traits are optional. They can be used as filters or aggregation buckets
    traits={
        "region": "us-east-1",
    },
)
customer = customer_api.add_or_update(message)

# Set up ingest API client and ingest a meter
from time import time
from metering.ingest import create_ingest_client

ingest_api = create_ingest_client(api_key=API_KEY)

ingest_api.meter(
    meter_api_name="api-calls",
    meter_value=1,
    meter_time_in_millis=int(time() * 1000),
    customer_id=customer["customerId"],
)

# Shut down ingest API client
ingest_api.shutdown()

# Setup usage API client and get a usage report
from metering.usage import (AggregationType, Take, TimeGroupingInterval,
                            TimeRange, UsageApiClient, create_usage_query)

usage_api = UsageApiClient(os.environ.get("API_KEY"))
```
:::

:::CodeblockTabsResponses
```javascript
```
:::
::::

::::CodeDrawer{title="9. Get a usage report" codeEditorData="[object Object]" responsesEditorData="[object Object]" isResponseExpanded="true" autoGeneratedAnchorSlug="9-get-a-usage-report" legacyHash="u0aFC0fgnQyLUmE2THMdm"}
:::CodeblockTabsExamples
```python
# Set up virtual environment and install SDK
#
#   $ python3 -m virtualenv venv
#   $ . venv/bin/activate
#   $ pip install amberflo-metering-python

import os

# Sign up for free at https://ui.amberflo.io/ to get an API key
API_KEY = os.environ["AMBERFLO_API_KEY"]

# Set up customer API client and create a customer
from metering.customer import CustomerApiClient, create_customer_payload

customer_api = CustomerApiClient(os.environ.get("API_KEY"))

message = create_customer_payload(
    customer_id="customer-123",
    customer_name="Customer 123",
    # email is optional
    customer_email="customer-123@example.com",
    # traits are optional. They can be used as filters or aggregation buckets
    traits={
        "region": "us-east-1",
    },
)
customer = customer_api.add_or_update(message)

# Set up ingest API client and ingest a meter
from time import time
from metering.ingest import create_ingest_client

ingest_api = create_ingest_client(api_key=API_KEY)

ingest_api.meter(
    meter_api_name="api-calls",
    meter_value=1,
    meter_time_in_millis=int(time() * 1000),
    customer_id=customer["customerId"],
)

# Shut down ingest API client
ingest_api.shutdown()

# Setup usage API client and get a usage report
from metering.usage import (AggregationType, Take, TimeGroupingInterval,
                            TimeRange, UsageApiClient, create_usage_query)

usage_api = UsageApiClient(os.environ.get("API_KEY"))

since_two_days_ago = TimeRange(int(time()) - 60 * 60 * 24 * 2)

query = create_usage_query(
    meter_api_name="api-calls",
    aggregation=AggregationType.SUM,
    time_grouping_interval=TimeGroupingInterval.DAY,
    time_range=since_two_days_ago,
    group_by=["customerId"],
    usage_filter={"customerId": ["customer-123"]},
    take=Take(limit=10, is_ascending=False),
)
report = usage_api.get(query)
```
:::

:::CodeblockTabsResponses
```javascript
```
:::
::::


[title] AWS CloudWatch
[path] Metering/Ingestion Options/

# Meter Ingestion via serverless agent for AWS CloudWatch

If you’d like to try Amberflo **without modifying your existing code or importing new libraries**, you can use our **Serverless Agent for AWS CloudWatch&#x20;**&#x74;o automatically extract meter data from your CloudWatch logs.

### **How It Works**

1. **Configure the Serverless Agent**
   &#x20;Connect Amberflo's serverless agent to your **AWS CloudWatch environment** (within your AWS VPC).
2. **Log Meter Records with Tags**
   &#x20;Log your meter events in CloudWatch, including a special **Amberflo tag**. This tag tells the agent which log entries represent meter data.
3. **Automatic Extraction and Ingestion**
   &#x20;The agent scans the logs, identifies entries with the Amberflo tag, and **ingests them as meter events** into Amberflo—automatically.

### **Example Project**

We provide a **demo project** that shows:

- A **REST API** powered by AWS Lambda
- How to **log and meter** the number of API calls using CloudWatch
- Other approaches for metering Lambda functions

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/MLSaYvhKABixpT7AmVHtD_image.png)

# How does it work?

Here is a step-by-step overview of how the **CloudWatch agent** operates:

1. Your code logs meter records
   Your application logs meter records to a **CloudWatch log group** (this is the default behavior for AWS Lambda functions).
2. Kinesis Stream collects the logs
   A **Kinesis Stream** captures the relevant log entries via a **CloudWatch Subscription Filter**.
   - If you have multiple log groups, you can route them all to the **same Kinesis Stream**.
3. Lambda agent processes the stream
   &#x20;A **Lambda function** (the agent) consumes log entries from the Kinesis Stream,
   &#x20;extracts the **meter records**, and sends them to **Amberflo for ingestion**.

**Note:&#x20;**&#x49;n the example setup, the filter pattern used is: `meter_record_for_stream`

You can use the code below (NodeJS) for the **Kinesis Stream consumer lambda**:

```javascript
'use strict';

const crypto = require('crypto');
const zlib = require('zlib');
const AWS = require('aws-sdk');

const bucketName = process.env.INGEST_BUCKET_NAME;
const accessKeyId = process.env.ACCESS_KEY;
const secretAccessKey = process.env.SECRET_KEY;

const s3 = new AWS.S3({
    region: 'us-west-2',
    accessKeyId,
    secretAccessKey,
});

const prefix = 'meter_record_for_stream';

exports.handler = async (event) => {
    const records = event
        .Records
        .map(r => r.kinesis.data)
        .map(d =>
            // Decompress and parse the CloudWatch payload
            JSON.parse(zlib.gunzipSync(Buffer.from(d, 'base64')).toString())
        )
        .filter(m => m.messageType !== 'CONTROL_MESSAGE')
        .map(p => p
            .logEvents
            .map(x => x.message)
            .map(m => {
                const i = m.indexOf(prefix);
                if (i < 0) return;  // get only messages containing meter records
                return JSON.parse(m.slice(i + prefix.length + 1));
            })
            .filter(x => x)
        ).flat();

    await ingest(records);
};

async function ingest(records) {
    const date = new Date().toISOString().slice(0, 10);
    const key = `ingest/records/${date}/${crypto.randomBytes(20).toString('hex')}.json`;

    const params = {
        Bucket: bucketName,
        Key: key,
        Body: JSON.stringify(records),
    };
    return s3.putObject(params).promise();
}
```

This code will take log entries like the one below and ingest them in batches through your [Amberflo-provided](docId:93m75sPSnr86OIYVdg-W3)  S3 bucket.

```none
INFO meter_record_for_stream {
    "meterApiName": "api-calls",
    "customerId": "70f1dd87-6978-4d96-a934-5a83b63cdeb1",
    "meterTimeInMillis": 1663094105062,
    "meterValue": 1,
    "uniqueId": "c91b8860-3392-11ed-a17b-bfa2e899d2c9",
    "dimensions": {
        "method": "GET"
        "endpoint": "meter-example"
    }
}
```

📘 *Ingest record format*

*If you log the meter records in a different format, you can modify the code above to marshal the records into the&#x20;*[Amberflo ingest](docId\:NsJPk3F7DuAkP-05PFyHV)*&#x20; format.*

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/E_Pultxwb3R3QlOVwiENf_image.png)


[title] Elastic Logstash
[path] Metering/Meter Integration Options/

You can integrate metering data into Amberflo using the open-source tool **Logstash**.

### **How It Works**

- The integration utilizes the **Logstash S3 output plugin** to write metering records to your **Amberflo-provided S3 bucket**.
- You can use **Logstash's powerful parsing language** to extract metering data from your logs or repositories.

### **Data Sources**

Logstash supports a wide variety of **input plugins**, allowing you to extract data from:

- **Files**
- **Elasticsearch**
- **JDBC**
- **S3**
- **MongoDB**
- And many other systems

This flexibility makes it easy to integrate metering into your existing infrastructure. [https://www.elastic.co/guide/en/logstash/current/input-plugins.html](https://www.elastic.co/guide/en/logstash/current/input-plugins.html)

## Example - Logstash configuration

The following is a sample **Logstash configuration** that reads log lines from a file and writes the relevant **metering records&#x20;**&#x74;o Amberflo’s **S3 bucket**.

### **Input Setup**

Logstash will **tail files** located at: `/Users/demoaccount/input/*`

### Sample File Content

:::BlockQuote
log line 1
log line 2
myMeter 2 myCustomerId amberflo\_meter
log line 3
:::

### **How It Works**

- Logstash continuously monitors the input folder for **new files**.
- It **filters log lines** using the condition: contains amberflo\_meter.
- Matching lines are **split by space (" ")**.
- The split values are mapped as follows:
  - meterApiName → first item
  - meterValue → second item
  - customerId → third item

The records are then transformed using **mutate logic** and forwarded to **Amberflo's S3 bucket**.

```ruby
input { 
   file {  
      path => "/Users/demoaccount/input/*" 
    } 
} 

filter {
    if "amberflo_meter" in [message]{
    ruby { code => "event.set('time', ((event.get('@timestamp').to_f*1000).to_i).to_s)" } 
     mutate {
            split => { "message" => " " }
            add_field => { "meterApiName" => "%{[message][0]}" }
            add_field => { "meterValue" => "%{[message][1]}" }
            add_field => { "customerId" => "%{[message][2]}" }
            remove_field => ["host","message","@version","@timestamp"]
        } 
    }else{
    drop {}
    }

}

output {
   s3{
     access_key_id => "XXXXXX"             
     secret_access_key => "YYYYYYYYYYYYYY" 
     region => "us-west-2"                    
     bucket => "demo-amberflo"               
     size_file => 2048                        
     time_file => 5                          
     codec => "json"                        
     canned_acl => "bucket-owner-full-control"                
   }

}
```

The resulting file (meter record) will be:

```json
{
	"meterApiName": "myMeter",
	"meterValue": "2",
	"customerId": "myCustomerId",
	"time": "1621619742810",
	"path": "/Users/demoaccount/input/sample.txt"
}
```

## JDBC source

You can use the **Logstash JDBC input plugin** to extract metering data directly from your database or repository.

- This is useful when your metering data is stored in structured tables.

The plugin allows you to run SQL queries to retrieve only the relevant data for metering.&#x20;
For more details, refer to the official documentation:
[https://www.elastic.co/guide/en/logstash/current/plugins-inputs-jdbc.html](https://www.elastic.co/guide/en/logstash/current/plugins-inputs-jdbc.html)

```ruby
input {
  jdbc {
    statement => "SELECT id, mycolumn1, mycolumn2 FROM my_table WHERE id > :sql_last_value"
    use_column_value => true
    tracking_column => "id"
    jdbc_driver_library => "mysql-connector-java-5.1.36-bin.jar"
    jdbc_driver_class => "com.mysql.jdbc.Driver"
    jdbc_connection_string => "jdbc:mysql://localhost:3306/mydb"
    jdbc_user => "mysql"
    schedule => "* * * * *"
    # ... other configuration bits
  }
}
```

## Elasticsearch input

If your metering data is stored in **Elasticsearch logs**, you can use the **Elasticsearch input plugin** to extract it into Logstash.

Here’s a basic example configuration:

```ruby
input {
elasticsearch {
        hosts => "search-myes-cluster.us-west-2.es.amazonaws.com:443"
        index => "cwl--aws-lambda-meter-definition-api-lambda-2021.07"
        query => '{ "query": {"bool": {"filter": [{"range": {"@timestamp": {"from": "now-1d/d", "to": "now/d", "include_lower": true, "include_upper": true, "format": "epoch_millis", "boost": 1 } } }, {"query_string": {"query": "*amberflo_meter*", "default_field": "@message", "fields": [], "type": "best_fields", "default_operator": "or", "max_determinized_states": 10000, "enable_position_increments": true, "fuzziness": "AUTO", "fuzzy_prefix_length": 0, "fuzzy_max_expansions": 50, "phrase_slop": 0, "escape": false, "auto_generate_synonyms_phrase_query": true, "fuzzy_transpositions": true, "boost": 1 } } ], "adjust_pure_negative": true, "boost": 1 } }, "aggregations": {} }'
        size => 500
        scroll => "5m"
        docinfo => true
        ssl => true
        user => "myuser"
        password => "mypwd"
        #schedule => "*/1 * * * *"
      }

}
```


[title] POSTMAN Collections
[path] Metering/Meter Integration Options/

## Ingesting Meters using POSTMAN

One of the easiest ways to get started with the Amberflo API is by using our **Postman collection**.

With Postman, you can:

- **Create meters**
- **Ingest metering data**
- **Query ingested data**

All within **just a few minutes**—no coding required.

This is an ideal approach for:

- Testing the API
- Exploring available endpoints

Getting a quick hands-on understanding of the metering workflow


::embed[]{url="https://youtu.be/kqB06ybaZwE"}

Github repo [https://github.com/amberflo/api-postman](https://github.com/amberflo/api-postman)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/Z9xZebsdwPiuhPZuOltzE_image.png)

## **Prerequisites**

**Postman**

[https://www.postman.com/downloads/](https://www.postman.com/downloads/)

**node.js**

[https://docs.npmjs.com/downloading-and-installing-node-js-and-npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)

**newman - Postman runtime**

[https://www.npmjs.com/package/newman](https://www.npmjs.com/package/newman)

**Installation**
Download github repo [https://github.com/amberflo/api-postman](https://github.com/amberflo/api-postman)
(also on the github page)

## **Postman Instructions**

**Import collection** `/api-postman/api-tests.collection.json` into Postman

[https://dash.readme.com/project/amberflo/v1.0/refs/postman-for-api](https://dash.readme.com/project/amberflo/v1.0/refs/postman-for-api)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/2OM-1tfsAYhtuK2cnYYd-_image.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/0aVEQ_BRHYJ_XJCQY8LGb_image.png)

Impor&#x74;**&#x20;environment file** `/api-postman/env-prod.json` into Postman

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/ETbdHZg8RAsv87C2AthLo_image.png)

Set Amberflo’s username, password, X\_API\_XEY, and s3\_bucket in prod environment file.

```json
{
			"key": "username",
			"value": "",
			"enabled": true
		},
		{
			"key": "password",
			"value": "",
			"enabled": true
		},
		{
			"key": "X_API_KEY",
			"value": "",
			"enabled": true
		},
		{
			"key": "s3_bucket",
			"value": "",
			"enabled": true
		}
```

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/fe5NuFZdF4tG6c6Wx6kIk_image.png)

Launch **Runner**, select collection and environment file and run.

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/29kU5m0kpIPfnqQ9RFUkl_image.png)


[title] Portal, Widgets, and APIs
[path] Billing & Monetization/Customer Billing Portal/

**The Amberflo Customer Usage and Billing Portal** allows accounts to extend Amberflo’s capabilities directly to their customers. There are multiple ways to implement this functionality depending on your needs and tech stack.

You can choose from the following options:

1. Use the Customer Usage and Billing Portal APIs.
2. Deploy the fully hosted Amberflo.io Customer Usage and Billing Portal web application.
3. Amberflo embeddable UI components:
   - For React.js, use the [UI Kit library integration](docId\:jHFrRrrevQKbDEpckSup6).
   - For vanilla JavaScript, use the [SDK library integration](docId\:kIPKru3tyXh4axofLKCFG) .

## Workflow

1. Authenticate the customer user in your app.
2. Call **Customer Usage and Billing Portal Session API** with the customer ID and your API key to generate a customer-specific session. See [Customer Usage and Billing Portal Session](docId:1bstcSALFuLkpZbOg5WcP) .
3. Use that session token in any of the Customer Usage and Billing Portal apps.

## Customer Usage and Billing Portal Session

When using the Customer Usage and Billing Portal, the API key authenticates the Amberflo account and grants access to customer data. However, in the context of the portal, it’s important to restrict data access to a specific customer.

To enable customer access to the portal, a **session token** is required. This token securely encapsulates both the Amberflo account ID and the customer ID. Your application is responsible for authenticating the customer, after which it should call the Session API using the customer ID to generate the session token.

This process enables **Single Sign-On (SSO)** between your application and the Amberflo Customer Usage and Billing Portal.

**Note:&#x20;**&#x54;he customer ID must already exist in Amberflo for the session token to be created.

Session endpoint: [https://app.amberflo.io/session](https://app.amberflo.io/session)

API Reference [Create a session](docId\:p_45dCxEKUOsbG4vBhz06)&#x20;

```curl
curl --location --request POST 'https://app.amberflo.io/session' \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: IdToken {{API-KEY}}' \
--data-raw '{
    "customerId": "bca5be25-f145-4448-b85c-1236dce1a2ac",
    "expirationEpochMilliSeconds": 1644004025153,
    "returnUrl":"https://StarkIndustries.com/"
}'
```

API Response:

```json
{
    "url": "https://portal.amberflo.io/session/9cd47871391814249841843c2fd82c6483f2c47bbc7f2c1fba6f975f1f9d7599605c4a580f99?returnUrl=https%3A%2F%2FStarkIndustries.com%2F",
    "sessionToken": "9cd47871391814249841843c2fd82c6483f2c47bbc7f2c1fba6f975f1f9d7599605c4a580f99"
}
```

The response from the Session API includes two key values:

- **sessionToken** – This token can be used to authenticate API requests to the Customer Usage and Billing Portal. It should be included in the `X-API-KEY `header or used with the Amberflo UI Kit.
- **url** – This is the landing page URL for the Customer Usage and Billing Portal web app. It includes the session token and a `returnUrl` parameter. The `returnUrl` allows users to navigate back to your application from the portal.

**sessionToken** can now be used to call Customer Usage and Billing Portal APIs.

## Adding a Custom Business Logo

If you're using the fully hosted Amberflo.io Customer Usage and Billing Portal web app, you can customize the appearance by adding your own business logo.

To do this, navigate to **Settings** > **Business Details** within [Amberflo](https://ui.amberflo.io/settings/business/branding). Click the **+** button to upload your logo. Accepted formats include PNG, JPG, and SVG, and the file size must be under 1MB.

This allows your customers to see your brand identity consistently across the portal.

![](https://files.readme.io/fa9e875-Screenshot_2022-11-01_at_10.28.28.png)

A preview of what your new customer portal header will look like will appear after uploading your new logo.

![](https://files.readme.io/12af420-Screenshot_2022-11-01_at_10.27.17.png)

You now have a custom business logo being displayed in your customer portal

![](https://files.readme.io/0a7ba38-31b8dfe-Screen_Shot_2022-08-05_at_10.55.07_PM.png)

## Customer Portal Customization

You can configure which items to show in the **Customer Portal**. To do this, go to [Amberflo.io](https://ui.amberflo.io/integrations/customer-portal) Integrations -> Customer Portal





![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/BjH8RKAb84Ni9SRbCs_mi_screenshot-2024-10-29-at-53017-pm.png)


[title] AWS SQS
[path] Metering/Ingestion Options/

# Write events directly to an Amberflo supplied and secured AWS SQS queue

Amberflo provides your account with a **FIFO (First-In-First-Out) AWS SQS queue** that includes the necessary **access rights and permissions** to submit meter records.

- Meter records are **automatically processed** as they arrive in the queue.
- No additional configuration is needed on your end for ingestion.

To get your **SQS queue and S3 bucket provisioned**, please **contact us**.

## Format

The **meter records sent to the SQS queue&#x20;**&#x6D;ust follow the same standardized format as used by the Amberflo Ingest API.&#x20;
Here are some examples:

```json
[{
     "customerId": "customer-123",
     "meterApiName": "ComputeHours",
     "meterValue": 5,
     "meterTimeInMillis": 1619445706909,
     "dimensions": {
        "region": "us-west-2",
        "az": "az1"
     }
}]
```

We also support [NDJSON](https://github.com/ndjson/ndjson-spec) format (JSON separated by a newline)

```json
{ "customerId": "customer-123", "meterApiName": "ComputeHours", "meterValue": 5, "meterTimeInMillis": 1619445706909 }
{ "customerId": "customer-321", "meterApiName": "ComputeHours", "meterValue": 4, "meterTimeInMillis": 1619445712341 }
{ "customerId": "customer-123", "meterApiName": "ComputeHours", "meterValue": 1, "meterTimeInMillis": 1619445783456 }
```

## Code Example

```python
import json
from uuid import uuid1
from datetime import date

import boto3

records_to_send = [{
     'customerId': 'customer-123',
     'meterApiName': 'ComputeHours',
     'meterValue': 5,
     'meterTimeInMillis': 1619445706909,
     'dimensions': {
        'region': 'us-west-2',
        'az': 'az1'
     }
}]

queue_url = 'https://sqs.us-west-2.amazonaws.com/624335419252/62-ingest.fifo'

sqs = boto3.client('sqs')

sqs.send_message(
    QueueUrl=queue_url,
    MessageBody=json.dumps(records_to_send),
    MessageGroupId=str(uuid1()),
    MessageDeduplicationId=str(uuid1()),
)
```

## Troubleshooting

Meter records sent to the **SQS queue&#x20;**&#x61;re first saved in an **S3 bucket** before being processed.&#x20;

If there's a failure during processing:

- A **failure report file** will be created in the S3 bucket with the reason for the error.
- A **dead-letter queue** is also set up to capture messages that could not be written to S3.

This setup ensures reliabl&#x65;**&#x20;troubleshooting** and **traceability** for all ingestion issues.

[title] Meter Definition
[path] Metering/

# Defining Your First Meter

There are three main parts to a meter definition. These parts are detailed below to help you create the right meter for your needs.

## Meter Event

A meter event is a usage event sent to Amberflo for ingestion and aggregation. Any single usage event ingested via any ingestion sources and options supported constitutes a meter event.

## **Name & Description F**ields

**Label** is the meter's name that will be displayed throughout the dashboard. This field can be changed after activating the meter.

**Meter Api Name** is the technical label of the meter used by the backend processes. This is also the identifier for event ingestion. This field **can not** be changed after activating the meter.

**Description** allows you to leave a brief explanation of what the meter is. This is viewable when looking at the meter definitions. This field can be changed after activating the meter.

## Types of Meters

Amberflo supports three types of meters: count meters, continuous meters, and seat-based meters. Each type serves a specific purpose and is tailored to handle event data in different ways.

1. **Count Meters**&#x20;
   - **Functionality**: Count meters increment their value each time an event occurs and provide summary statistics when queried.
   - **Use Cases**:
     - Monitoring total API calls.
     - Tracking the number of transactions in a system.
     - Measuring the total volume of error occurrences.
   - **Advantages**:
     - Simple to implement and use.
     - Offers a clear overview of event totals over time.
     - Provides flexibility in summarizing data with MAX, AVG, and SUM statistics.
2. **Continuous Meters**&#x20;
   - **Functionality**: Continuous meters log the start and stop of an event, calculating the total duration or quantity of usage between these points.
   - **Use Cases**:
     - Measuring application uptime.
     - Tracking active session durations for users.
     - Monitoring resource utilization, such as CPU or memory usage, over time.
   - **Advantages**:
     - Provides precise tracking of time-based usage.
     - Ideal for billing systems and resource management.
3. **Seat-based Meters**&#x20;
   - **Functionality**: Seat-based meters keep track of the number of active users or instances at any given time, providing a way to measure compliance with seat-based licensing agreements. (This can be found under "Advanced Mode" in the Create Meter UI flow)
   - **Use Cases**:
     - Monitoring software license utilization.
     - Ensuring compliance with subscription-based services.
     - Tracking active user accounts in a system.
   - **Advantages**:
     - Simplifies compliance with licensing agreements.
     - Provides visibility into resource allocation and usage.
     - Supports capacity planning and cost optimization

To create your&#x20;

## Meter Dimensions

Meter dimensions are the last piece to meter definitions. These dimensions may take a bit more thought into what should qualify as a dimension and be sent as additional information to a meter event. More details on this can be found [here](docId:9cdmnulSBFjPn_tQ42h_O).


[title] React.js Widgets
[path] Billing & Monetization/Customer Billing Portal/Embeddable UI Kit - React.js/

Here is the list of all the available components that you can integrate into your own React.js application.

## Available Widgets

All widgets use internally some hooks to retrieve and update data. You can subscribe to them, to show loading or error statuses. Or just to get the raw data to render in the format you desire. (Advanced). [You can read more about hooks here](docId\:wMqXhaR7t8Fwot5qkfWHw) &#x20;

Each view has linked the hooks that are consuming, so you can subscribe to them.

### Billed Usage

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/V6GRwqx9CGfvQ9ZVLxcL2_image.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/awYqR5t0IB2mDKrgx7REK_image.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/dhf-ZJyQnMQ2FtiZiDOQC_image.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/qGU8bmrc1MUwrb082viRT_image.png)

**How to import:**

`import { CostAndBilledUsage } from '@amberflo/uikit'`

**Available Props**

To see common props check [ReactJS Integration / Common Props](docId:9ZJRkjeaTQ1Bb5XoBcZno)&#x20;



![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/Rh3LnOQZiXoeOIfkP_5Li_image.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/9b6WgzmMlGchD1TXLWD_6_image.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/ekuD6lsrYK2cmynt6qZBW_image.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/WvgQuJg0go0DFrHv07-f0_image.png)

**Used Hooks**

- [Hooks | useMeters()](docId\:wMqXhaR7t8Fwot5qkfWHw)&#x20;
- [Hooks | useUsage()](docId\:wMqXhaR7t8Fwot5qkfWHw)&#x20;

### Charge Explorer Bar Graph

![](https://files.readme.io/303fe34-pic.png)

**How to import:**
`import { CostExplorerBarGraph } from "@amberflo/uikit";`

**Available Props**

There are no specific props for this view. To see common props check [ReactJS Integration / Common Props](docId:9ZJRkjeaTQ1Bb5XoBcZno)&#x20;

**Used Hooks**

- [Hooks | useCost()](docId\:wMqXhaR7t8Fwot5qkfWHw)&#x20;
- [Hooks | usePricingPlanDetails()](docId\:wMqXhaR7t8Fwot5qkfWHw)&#x20;

### Charge Table

![](https://files.readme.io/e8447cc-pic.png)

**How to import:**

`import { CostExplorerTable } from '@amberflo/uikit'`

**Available Props**

To see common props check [ReactJS Integration / Common Props](docId:9ZJRkjeaTQ1Bb5XoBcZno)&#x20;

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/eWSgNj5yLME_QKkVettxh_image.png)

- [Hooks | useCost()](docId\:wMqXhaR7t8Fwot5qkfWHw)&#x20;

### Included Units

![](https://files.readme.io/ca2d8c4-Screen_Shot_2023-01-09_at_11.36.15_AM.png)

**How to import:**
`import { IncludedUnits } from '@amberflo/uikit'`

**Available Props**

To see common props check [ReactJS Integration / Common Props](docId:9ZJRkjeaTQ1Bb5XoBcZno)&#x20;

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/VrVQmrg7LyuP_UrEPpUeb_image.png)

**Used hooks**

- [Hooks | useLatestInvoice()](docId\:wMqXhaR7t8Fwot5qkfWHw)&#x20;
- [Hooks | usePricingPlanDetails()](docId\:wMqXhaR7t8Fwot5qkfWHw)&#x20;

### Credits Ledger

![](https://files.readme.io/3911365-image.png)



![](https://files.readme.io/9efa3e1-image.png)

**How to import:**
`import { CreditsLedger } from "@amberflo/uikit";`

**Available Props**

To see common props check [ReactJS Integration / Common Props](docId:9ZJRkjeaTQ1Bb5XoBcZno)&#x20;

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/n66wAIg3gyKHJt4rw5Gy5_image.png)

**Used Hooks**

- [Hooks | useCreditsLedger()](docId\:wMqXhaR7t8Fwot5qkfWHw)&#x20;
- [Hooks | usePrepaidSummary()](docId\:wMqXhaR7t8Fwot5qkfWHw)&#x20;

### Invoice Details

![](https://files.readme.io/321afbe-pic.png)

**How to import:**

`import { InvoiceDetails } from '@amberflo/uikit'`

Available Props

To see common props check [ReactJS Integration / Common Props](docId:9ZJRkjeaTQ1Bb5XoBcZno)&#x20;

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/vPIjPfaZxC0frndoJOSCP_image.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/Lg1nuLe63CbTQ6TjK42mr_image.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/4Se92D9tR-OBpMOLobnBR_image.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/pSkg8yCVTsm-TNnTP_3pc_image.png)

-**Note:** Invoice data displayed in this component is affected by [Invoice-related options](docId\:jHFrRrrevQKbDEpckSup6).

**Used Hooks**

- [Hooks | useInvoiceDetails()](docId\:wMqXhaR7t8Fwot5qkfWHw)&#x20;
- [Hooks | useLatestInvoice()](docId\:wMqXhaR7t8Fwot5qkfWHw)&#x20;
- [Hooks | usePrepaidSummary()](docId\:wMqXhaR7t8Fwot5qkfWHw)&#x20;

### Invoice PDF

::Image[]{src="https://files.readme.io/32b2095-Screen_Shot_2022-12-08_at_3.31.21_PM.png" size="38" width="202" height="164" position="center" showCaption="false"}

**How to import:**

`import { InvoicePdf } from '@amberflo/uikit'`

**Available Props**

To see common props check [ReactJS Integration / Common Props](docId:9ZJRkjeaTQ1Bb5XoBcZno)&#x20;

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/JLEDxLIYQpyMEVkWtq7Rb_image.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/WfNTpakQm7oK8JOhRD1GL_image.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/xeMP0lQTepVRXCyGma0Iq_image.png)

**Note:** Invoice data displayed in this component is affected by [Invoice-related options](docId\:jHFrRrrevQKbDEpckSup6).

**Used Hooks**

- [Hooks | usePrepaidSummary()](docId\:wMqXhaR7t8Fwot5qkfWHw)&#x20;

### Invoices List

![](https://files.readme.io/508b2b5-pic.png)

**How to import:**

`import { InvoicesList } from '@amberflo/uikit'`

**Available Props**

To see common props check [ReactJS Integration / Common Props](docId:9ZJRkjeaTQ1Bb5XoBcZno)&#x20;

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/DnuAa6Dd5g0nUxxCJ8iPX_image.png)

**Note:** Invoice data displayed in this component is affected by [Invoice-related options](docId\:jHFrRrrevQKbDEpckSup6).

**Used Hooks**

- [Hooks | useInvoices()](docId\:wMqXhaR7t8Fwot5qkfWHw)&#x20;
- [Hooks | usePrepaidSummary()](docId\:wMqXhaR7t8Fwot5qkfWHw)&#x20;

### Orders Usage Graph and Table

![](https://files.readme.io/e6498f5-Screenshot_2024-01-08_at_2.20.36_PM.png)

How to import:

`import { OrdersUsageGraphAndTable } from '@amberflo/uikit'`

To see common props check [ReactJS Integration / Common Props](docId:9ZJRkjeaTQ1Bb5XoBcZno) . The following Common Props are not available for this component: `callToAction`, `title`, `withContainer` and `withTitle`. The [ReactJS Integration | Common Graph Props](docId:9ZJRkjeaTQ1Bb5XoBcZno) are also available for this component.

**Used Hooks**

- [Hooks | useInvoices()](docId\:wMqXhaR7t8Fwot5qkfWHw)&#x20;
- [Hooks | useMeters()](docId\:wMqXhaR7t8Fwot5qkfWHw)&#x20;
- [Hooks | useProductItems()](docId\:wMqXhaR7t8Fwot5qkfWHw)&#x20;
- [Hooks | useUsage()](docId\:wMqXhaR7t8Fwot5qkfWHw)&#x20;

### Prepaid Summary Graph

![](https://files.readme.io/daa8671-image.png)

**How to import:**

`import { PrepaidSummaryGraph } from '@amberflo/uikit'`

**Available Props**

To see common props check [ReactJS Integration / Common Props](docId:9ZJRkjeaTQ1Bb5XoBcZno)&#x20;

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/1dK6qN0JnrhmHz3VUT8Wk_image.png)

**Used Hooks**

- [Hooks | useLatestInvoice()](docId\:wMqXhaR7t8Fwot5qkfWHw)&#x20;
- [Hooks | usePrepaidSummary()](docId\:wMqXhaR7t8Fwot5qkfWHw)&#x20;

### Prepaid Summary Table

![](https://files.readme.io/b610468-pic.png)

**How to import:**

`import { PrepaidSummaryTable } from '@amberflo/uikit'`

**Available Props**

To see common props check [ReactJS Integration / Common Props](docId:9ZJRkjeaTQ1Bb5XoBcZno)&#x20;

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/PeVXUNGmTLEZPpGLVje42_image.png)

**Used Hooks**

- [Hooks | useProductItems()](docId\:wMqXhaR7t8Fwot5qkfWHw)&#x20;
- [Hooks | usePrepaidSummary()](docId\:wMqXhaR7t8Fwot5qkfWHw)&#x20;

### Pricing Plan

![](https://files.readme.io/d6c46e9-pic.png)

How to import:

`import { PricingPlan } from '@amberflo/uiki'`

To see common props check [ReactJS Integration / Common Props](docId:9ZJRkjeaTQ1Bb5XoBcZno)&#x20;

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/BlftuRA96LY1BarZ5l5rf_image.png)

Used Hooks

- [Hooks | useCurrentPricingPlan()](docId\:wMqXhaR7t8Fwot5qkfWHw)&#x20;

### Pricing Plan Summary

![](https://files.readme.io/8796582-Screen_Shot_2023-02-02_at_4.18.53_AM.png)

How to import:
`import { PricingPlanSummary } from '@amberflo/uikit'`

Available Props

To see common props check [ReactJS Integration / Common Props](docId:9ZJRkjeaTQ1Bb5XoBcZno)&#x20;

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/DIevy2cV7HFLhL7FUMajw_image.png)

**Used Hooks**

- [Hooks | useCurrentPricingPlan()](docId\:wMqXhaR7t8Fwot5qkfWHw)&#x20;

### &#xA;Usage Graph

![](https://files.readme.io/1571fa0-pic.png)

How to import:

`import { UsageByMeterLineGraph } from '@amberflo/uikit'`

**Available Props**

To see common props check [ReactJS Integration / Common Props](docId:9ZJRkjeaTQ1Bb5XoBcZno)&#x20;

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/8ys1Gy5l9_hWZsm4rHIUW_image.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/WxwJ0N_qRZt-VVJCUyjbu_image.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/kEcLe0ORhzAEkzSDmX_vW_image.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/R9PTeqbcpnXtkhZmaa6Bv_image.png)

**Used Hooks**

- [Hooks | useMeters()](docId\:wMqXhaR7t8Fwot5qkfWHw)&#x20;
- [Hooks | useUsage()](docId\:wMqXhaR7t8Fwot5qkfWHw)&#x20;

### Usage Table

![](https://files.readme.io/fbd2eec-pic.png)

**How to import:**

`import { UsageByMeterTable } from '@amberflo/uikit'`

**Available Props**

To see common props check [ReactJS Integration / Common Props](docId:9ZJRkjeaTQ1Bb5XoBcZno)&#x20;

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/Cc7qm1imLA2u13xxALDqN_image.png)

**Used Hooks**

- [Hooks | useMeters()](docId\:wMqXhaR7t8Fwot5qkfWHw)
- [Hooks | useUsage()](docId\:wMqXhaR7t8Fwot5qkfWHw)&#x20;

### Order List

![](https://files.readme.io/bc35cdb-image.png)

**How to import:**

`import { OrderList } from '@amberflo/uikit'`

**Available Props**

To see common props check [ReactJS Integration / Common Props](docId:9ZJRkjeaTQ1Bb5XoBcZno)&#x20;

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/7LSZNQdFWbyofsoBpCiu7_image.png)

### Order Create

![](https://files.readme.io/8f3c4fb-image.png)



![](https://files.readme.io/c095161-image.png)

**How to import:**

`import { OrderCreate } from '@amberflo/uikit'`

**Available Props**

To see common props check [ReactJS Integration / Common Props](docId:9ZJRkjeaTQ1Bb5XoBcZno)&#x20;

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/pdUEj00uCQxtTiMRiOTI8_image.png)

### Order Details

![](https://files.readme.io/fe531f2-image.png)

**How to import:**

`import { OrderDetails } from '@amberflo/uikit'`

**Available Props**

To see common props check [ReactJS Integration / Common Props](docId:9ZJRkjeaTQ1Bb5XoBcZno)&#x20;

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/YFTa-eR1Y51IXYNiTUwTO_image.png)

### Orders Usage Graph and Table

![](https://files.readme.io/99bba84-image.png)

**How to import:**

`import { OrdersUsageGraphAndTable } from '@amberflo/uikit'`

**Available Props**

To see common props check [ReactJS Integration / Common Props](docId:9ZJRkjeaTQ1Bb5XoBcZno)&#x20;

To see graph props check [ReactJS Integration / Graph Props](docId:9ZJRkjeaTQ1Bb5XoBcZno)&#x20;

### Stripe Setup Intent

![](https://files.readme.io/41ec243-Screen_Shot_2023-01-06_at_4.50.00_PM.png)



![](https://files.readme.io/2948d7d-Screen_Shot_2023-01-06_at_4.50.09_PM.png)

**How to import:**

`import { StripeSetupIntent } from '@amberflo/uikit'`

**Available Props**

To see common props check [ReactJS Integration / Common Props](docId:9ZJRkjeaTQ1Bb5XoBcZno)&#x20;

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/V3ezVVoezhDrjjn9dsQXx_image.png)

After the payment method is set. A redirect will happen to the `returnUrl`. It can be the same url as the actual one.

If the redirect url, has this component included, even if hidden, it will automatically set the default payment method. If not, read below how to set the default payment method.

## How To Get Secret Key From Stripe

Call this endpoint:

�&#xDCD8;***&#x20;POST***

[https://app.amberflo.io/customer-portal/setup-intent](https://app.amberflo.io/customer-portal/setup-intent)

**Payload:**

:::BlockQuote
\{
&#x20;    "provider": "stripe"
}
:::

**Response Body:**

:::BlockQuote
\{
&#x20;   "id": "seti\_1LIBSbFZnIN3GxYXnNiuvLGe",
&#x20;   "clientSecret": "seti\_3MKDTdHBpKP5IzAZpNiuvLGe\_secret\_M0BqOmOj5LWcKZ910nPEzRCy1wFfe8M"
}
:::

## How to make a payment the default payment method

1. After creating a new payment method, you’ll get redirected to a new URL with some query string parameters. The redirect is needed in order to get the query param called `setup_intent_client_secret` to retrieve payment method from Stripe.
2. You need to retrieve the payment method from Stripe, by running:
   `const setupIntentClientSecret = getQueryString("setup_intent_client_secret");`
   `const { setupIntent } = await stripe.retrieveSetupIntent(setupIntentClientSecret);`
3. Submit the payment method to endpoint to set as default payment method:

***📘POST***

[https://app.amberflo.io/customer-portal/payment-method](https://app.amberflo.io/customer-portal/payment-method)



**Payload**

:::BlockQuote
\{
&#x20; provider: "stripe",
&#x20; paymentMethodId: \<Payment method ID from Stripe>,
}
:::

If on the redirect url, you place the `StripeSetupIntent`component, even if hidden, it will trigger automatically the set default payment method.

You can also subscribe to `usePaymentMethod() `hook from `@maberflo/uikit/hooks`, like:

```javascript
const { isSuccess } = usePaymentMethod()
```

&#x20;This isSuccess will change to true after setting the payment method.

## Common Props

These are props common to all views, and non of them are required:

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/1U6KlFoonxAYfbze2yAIx_image.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/MdtmWBIdYNmvhIwW6TlVG_image.png)

## Common Date Picker Props

There are a few props shared by views that have a date picker. And none of them are required.

Views with date picker:

- [Cost and Billed Usage](docId:9ZJRkjeaTQ1Bb5XoBcZno)&#x20;
- [Cost explorer Bar Graph](docId:9ZJRkjeaTQ1Bb5XoBcZno)&#x20;
- [Cost table](docId:9ZJRkjeaTQ1Bb5XoBcZno)&#x20;
- [Usage Graph](docId:9ZJRkjeaTQ1Bb5XoBcZno)&#x20;
- [Usage Table](docId:9ZJRkjeaTQ1Bb5XoBcZno)&#x20;

Shared props:

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/y1-9PK_p6x-uFYEOwzEf5_image.png)

## Common Graph Props

There are a few props shared by views that have a graph. And none of them are required.

Views with graphs:

- [Cost and Billed Usage](docId:9ZJRkjeaTQ1Bb5XoBcZno)&#x20;
- [Cost explorer Bar Graph](docId:9ZJRkjeaTQ1Bb5XoBcZno)&#x20;
- [Usage Graph](docId:9ZJRkjeaTQ1Bb5XoBcZno)&#x20;
- [Orders Usage Graph and Table](docId:9ZJRkjeaTQ1Bb5XoBcZno)&#x20;

Shared props:

| **Name**           | **Type**     | **Default** | **Description**                                                                                                                                                                                                                                                                                                                                                                              |
| ------------------ | ------------ | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `graphTextColor`   | `string`     | `undefined` | Color for the axis of graphs                                                                                                                                                                                                                                                                                                                                                                 |
| customGraphOptions | ChartOptions | undefined   | We use [Chart.js](https://www.chartjs.org/docs/latest/) for our graphs. This prop allows to customize the graph using all the options they provide. For more info check: [https://www.chartjs.org/docs/latest/general/options.html](https://www.chartjs.org/docs/latest/general/options.html)&#xA;&#xA;For example, you could change text colors, lines or bars weight, add animations, etc. |


[title] Customer Billing Quick Start
[path] Quick Start/

This guide walks you through a complete billing flow in Amberflo.

By the end, you will have:

- A **meter** capturing usage
- A **pricing plan** defining how that usage is charged
- A **customer** assigned to that plan
- **Usage data** reflected on an invoice

The goal is to get a simple, working setup. You can expand from there.

## **What You’ll Build**

Example use case:

You want to charge customers **$0.01 per API request**.

You will:

1. Create a meter to track requests
2. Create a pricing plan
3. Create a customer and assign the plan
4. Send usage and verify billing

## **Step 1: Create a Meter**

### **Navigate**

- In the left navigation:
  - Go to **Meters** (under **Manage**)

### **Create the Meter**

- Click **Create Meter** (top right)

### **Configure**

- **Label**
  Example: API Requests
- **API Name**
  Auto-generated
  - No spaces
  - Allowed: letters, numbers, hyphens, underscores
  - Max 50 characters
- **Description (optional)**
- **Meter Type**
  Select **Count**

### **Dimensions (Optional)**

Add dimensions if you want more detail later.

Example:

- endpoint
- region

You can skip this for now.

Click **Create**.

## **Step 2: Create a Pricing Plan**

### **Navigate**

- In the left navigation:
  - Go to **Pricing**

### **Create the Plan**

- Click **Create Pricing Plan** (top right)

### **Configure**

- **Name**
  Example: API Usage Plan
- **Billing Period**
  Choose:
  - **Monthly**, or
  - **Calendar Monthly**
- **Description (optional)**

Click **Continue**.

### **Add a Rate**

- Click **Add Rate** (right side)
- Select **Usage-Based**

Configure:

- **Meter**
  Select your meter (e.g., API Requests)
- **Rate Model**
  Leave as **Per Unit**
- **Price**
  Enter 0.01

Leave all advanced options empty.

Click **Continue**, then **Save**.

### **Activate the Pricing Plan**

Make sure the pricing plan is **active**.
If it is not active, it will not appear when assigning it to a customer.

## **Step 3: Create a Customer and Assign the Plan**

### **Navigate**

- In the left navigation:
  - Go to **Customers**

### **Create Customer**

- Click **Create Customer** (top right)

Fill in:

- **Customer Name**
  Example: Test Customer
- **Customer ID**
  Any unique identifier

Click **Save**.

### **Assign Pricing Plan**

- Open the customer you just created

In the **Pricing Plans** section:

- Click **Assign Plan**

Configure:

- **Plan**
  Select your pricing plan
- **Start Date**
  Choose:
  - **Now**, or
  - **Custom date**

Click **Assign Plan**.

## **Step 4: Send Usage**

### **Navigate**

- In the left navigation:
  - Go to **Meters**
- Click on your meter to open it

### **Upload an Event**

- Click **Event Upload**

Fill in:

- **Customer**
  Select your customer (may already be auto-populated)
- **Value**
  Example: 100
- **Dimensions (optional)**
  Only if you added them earlier

Click **Ingest Meter**.

You have now sent usage tied to that customer.

## **Step 5: Verify Billing**

### **Navigate**

- In the left navigation:
  - Go to **Customers**
- Open your customer

### **What to Expect**

- You should see a **non-zero invoice total**

Example:

- 100 units × $0.01 = $1.00

### **If You Do Not See Data**

Amberflo does not automatically refresh data views.

- **Refresh the page**

If data still does not appear:

- Confirm the customer has a pricing plan assigned
- Confirm usage was sent to the correct customer
- Confirm the pricing plan is active

## **What You Just Did**

You completed the full billing loop:

- Metered usage
- Defined pricing
- Assigned a customer
- Sent usage
- Generated billable charges

## **What’s Next**

From here, you can expand into:

- Tiered or volume pricing
- Discounts and promotions
- Multiple meters per plan
- Automated ingestion via API

Or move to the **Cost Visibility Quick Start** to understand how the same usage can be used to track internal cost.

[title] Traditional Recurring Subscription
[path] Common Use Cases/

This guide explains how to implement a **subscription-only pricing model**.

In this model, customers pay a fixed recurring fee for access to your product. There is no usage-based billing or overages.

## **The Scenario**

You are building a SaaS product that charges customers based on **seats**.

Your pricing model:

- $500 per month
- Includes up to 10 seats

There are no overage charges in this version. The subscription fee covers access to the product.

## **Core Concept**

:::BlockQuote
Fixed Monthly Fee &#x20;
→ Access to Product &#x20;
→ No usage-based charges
:::

This is the simplest pricing model.

## **Step 1: Define What the Subscription Represents**

Even though billing is not usage-based, it is still important to define what the customer is paying for.

In this example:

- The subscription includes **10 seats**

You may still track seat usage internally, but it is not required for billing in this model.

## **Step 2: Create a Pricing Plan**

### **Where to Configure**

- Go to **Pricing**
- Create a new pricing plan

### **Configure**

- **Name**: Team Plan
- **Billing Period**: Monthly

## **Step 3: Add a Subscription Rate**

### **Where to Configure**

- Add a **Subscription** rate (previously called flat rate)

### **Configure**

- **Label**: Team Plan Subscription
- **Rate**: $500

### **Billing Options**

- Select **Beginning of billing period**

This ensures the subscription is charged at the start of each cycle.

## **Step 4: Assign the Plan to a Customer**

### **Where to Configure**

- Go to **Customers**
- Select a customer
- Assign the pricing plan

Once assigned, the customer will be billed the subscription amount each billing period.

## **Example Outcome**

A customer is subscribed for one full billing period.

### **Billing Calculation**

:::BlockQuote
Monthly Subscription = $500
:::

There are no additional charges, regardless of how the product is used.

## **Optional: Tracking Seats**

Even though seats are not used for billing in this example, you may still want to track them.

You can do this by:

- Creating a seats meter
- Sending usage events when seats are added or removed

This allows you to:

- Monitor usage
- Enforce limits externally
- Prepare for future pricing changes

## **When to Use This Model**

Subscription-only pricing is best when:

- Usage is predictable
- You want simple, predictable billing
- You are early in product development
- You do not need granular usage-based pricing

## **Understanding Cost Behind the Subscription**

Even if you are not billing based on usage, your system is still generating cost.

You can (and should) meter the underlying activity that drives your product, such as:

- API calls
- LLM tokens
- Background processing jobs
- Infrastructure usage

This data is not used for billing in this model, but it is critical for:

- Understanding the true cost of your plans
- Measuring margins
- Identifying inefficiencies
- Making pricing decisions over time

These usage signals can be attributed to internal systems and analyzed separately from billing.

For internal cost tracking and attribution, see:
[Workloads](docId\:XToRHVsoI2AOK1BZPwfDU)

## **How This Can Evolve**

Many products start with subscription-only pricing and later expand to:

- Subscription + overages
- Usage-based pricing
- Tiered pricing

See the hybrid subscription guide for adding included usage and overage billing.

This model provides simplicity and predictability, while still allowing you to understand the underlying cost structure of your product.

[title] Hybrid: Subscription with Overages
[path] Common Use Cases/

This guide explains how to implement a **hybrid pricing model**:

- A **monthly subscription fee**
- **Included usage** for specific features
- **Overage charges** when usage exceeds those limits

This is one of the most common pricing models used in SaaS and usage-based products.

## **The Scenario**

You are building a **voice and media processing platform**.

Your pricing model:

**$199/month includes:**

- 1,000 audio processing minutes
- 500 transcription minutes

**Overages:**

- $0.05 per additional audio minute
- $0.08 per additional transcription minute

## **Core Concept**

:::BlockQuote
Subscription Fee &#x20;
\+ Included Usage (per meter) &#x20;
\+ Overage Charges (same meters) &#x20;
→ Final Invoice
:::

The key idea:

Included usage and overage charges are defined on the **same meter**.

There is no separate system for overages. Once included units are consumed, the same meter continues billing at the defined rate.

## **Step 1: Define What You Want to Measure**

Start by identifying the usage you want to track.

In this example:

- **Audio Minutes**
- **Transcription Minutes**

Each of these becomes a **meter**.

### **Where to Configure**

- Go to **Meters**
- Create one meter per usage type

### **What Matters**

- Each meter represents something you want to:
  - include in the subscription
  - charge overages for

You can optionally add dimensions for analytics or cost tracking, but they are not required for this billing model.

## **Step 2: Create a Pricing Plan**

### **Where to Configure**

- Go to **Pricing**
- Create a new pricing plan

### **What You Define**

- Plan name (e.g., Voice Platform - Pro Plan)
- Billing period (typically monthly)

## **Step 3: Add the Subscription Fee**

This is the base recurring charge.

### **Where to Configure**

- Add a **Subscription** rate (previously called flat rate)

### **What You Define**

- A label (e.g., Pro Plan Subscription)
- A monthly price (e.g., $199)
- Billing timing: **Beginning of billing period**

This ensures the subscription fee is charged at the start of each cycle.

## **Step 4: Define Included Usage and Overage Pricing**

For each meter, you will configure:

- A **per-unit rate**
- A number of **included units**

### **Where to Configure**

- Add a **Usage-Based** rate for each meter

### **Example Configuration**

**Audio Minutes**

- Meter: Audio Minutes
- Rate Model: Per Unit
- Rate: $0.05 per minute
- Included Units: 1,000

**Transcription Minutes**

- Meter: Transcription Minutes
- Rate Model: Per Unit
- Rate: $0.08 per minute
- Included Units: 500

## **How This Works**

For each meter:

1. Usage is tracked over the billing period
2. Included units are applied first
3. Any usage beyond that is billed at the defined rate

There is no additional configuration required for overages.

## **Step 5: Assign the Plan to a Customer**

### **Where to Configure**

- Go to **Customers**
- Select a customer
- Assign the pricing plan

Once assigned, all usage tied to that customer will be evaluated against this plan.

## **Step 6: Sending Usage (Conceptual)**

Usage is sent to the meter over time.

If you want to test this manually:

- Open a meter
- Use **Event Upload**
- Enter:
  - Customer
  - Usage value (e.g., minutes)
- Ingest the event

In production, this would typically be done via API.

## **Example Outcome**

Assume a customer uses:

- 1,200 audio minutes
- 600 transcription minutes

### **Billing Calculation**

**Subscription**

- $199 base fee

**Audio Minutes**

- 1,000 included
- 200 overage × $0.05 = $10

**Transcription Minutes**

- 500 included
- 100 overage × $0.08 = $8

### **Total**

:::BlockQuote
$199 + $10 + $8 = $217
:::

## **Important Notes**

- Included usage is always tied to the same meter as overages
- You can define multiple meters within a single plan
- This model works with simple or complex usage types
- You can extend this with:
  - tiered pricing
  - dimensional pricing
  - discounts

## **Why This Model Is Used**

This pricing model balances:

- **Predictability** → subscription fee
- **Flexibility** → usage-based overages
- **Scalability** → revenue grows with usage

It is widely used because it aligns pricing with both **baseline value** and **actual consumption**.

## **Related Concepts**

- For dimensional pricing (e.g., model-based pricing), see the LLM usage guide
- For internal cost tracking and attribution, see:
  [Workloads](docId\:XToRHVsoI2AOK1BZPwfDU)

This pattern is foundational. Once understood, it can be adapted to almost any usage-based product.

[title] Pricing Plans Overview
[path] Billing & Monetization/Pricing Plans/



## **Understanding Pricing Plans**

In Amberflo, **Pricing Plans** are collections of rate templates that define how customers are billed. These plans determine how usage is measured, priced, and rolled up into a final invoice.

**Available Rate Templates**

Amberflo provides out-of-the-box support for several common pricing models:

- **Usage-Based:&#x20;**&#x43;ustomers are charged based on actual usage during the billing period.
- **Seat-Based:&#x20;**&#x43;ustomers are billed for the number of active entities (such as users, files, or devices) at the end of the billing cycle.
- **Flat Rate:&#x20;**&#x43;ustomers pay a fixed amount per billing period. Flat rate plans can also include overage charges if usage exceeds predefined limits.
- **Service Fees:&#x20;**&#x41; percentage-based charge is applied to the total invoice (for example, as a support or platform fee).



![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/W1DjsUE-jPWSoEPeLQAti_screenshot-2024-10-29-at-50528-pm.png)



## **Default Pricing Plan**

Amberflo always maintains a **default pricing plan**:

- If there is only one pricing plan, it is automatically set as the default.
- If multiple plans exist, you can configure which one is the default.

When a new customer is created and no pricing plan is explicitly assigned, the default plan will be applied automatically when their first usage event is ingested.



## **Assigning Pricing Plans to Customers**

Pricing plans can be assigned to customers in two ways:

- **Via the Amberflo Console:&#x20;**&#x55;se the Customer page to manually assign a pricing plan.
- **Via API:&#x20;**&#x50;rogrammatically assign plans using the Amberflo API or SDKs.

Once a pricing plan is assigned, the customer’s usage will be rated according to the plan’s rules. Invoices are then generated at the defined billing frequency using those rates.



![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/t0O8lEz4hbn2N5cLlZcTI_screenshot-2024-10-29-at-50744-pm.png)


[title] Alert Webhooks
[path] Billing & Monetization/Usage & Amount Alerting/

Alerts can be delivered using a webhook. When configured this way, Amberflo will make an HTTP POST request to the specified endpoint whenever the alert threshold condition is met.

The request will contain a JSON payload with the relevant alert information. If the endpoint responds with a 200 status code, the delivery is considered successful. Any other status code will be treated as a failure, and the system will retry sending the alert.

## Context Variables

The following context variables can be used to set a webhook request header and payload:

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/pYxtzp5tSudIbfrxJlQvq_image.png)

## Sample Webhook for Slack

Create the alert using the [Alerts UI](https://ui.amberflo.io/alerts/create):

![](https://files.readme.io/1c85b5f-webhook_UI.jpg)

Or using the [Alerts API](docId\:TXaVag-nUfaH_djTdJXlS) :

```curl
curl --location --request POST 'https://app.amberflo.io/notification' \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: xyz' \
--data-raw '{
    "name": "usage-alert",
    "meterId" : "4d1ca680-4974-11ec-ad9b-e1ce3f8b2ad3",
    "cron": "0 0 * * 0",
    "webhookUrl": "https://hooks.slack.com/services/T01HZB7A0M6/B01N83NQV6H/lMa2sYqDUnV3a7cLbBD9234",
    "webhookPayload": "{\"text\":\"Amberflo Notification {{notification-name}} \",\"blocks\":[{\"type\":\"section\",\"text\":{\"type\":\"mrkdwn\",\"text\":\"Amberflo notification: {{notification-name}}\"}},{\"type\":\"section\",\"block_id\":\"section567\",\"text\":{\"type\":\"mrkdwn\",\"text\":\"Your meter value has exceeded the allowed threshold limit.\"}},{\"type\":\"section\",\"block_id\":\"section789\",\"fields\":[{\"type\":\"mrkdwn\",\"text\":\"*Customer*\\n {{customer-name}}\"},{\"type\":\"mrkdwn\",\"text\":\"*Meter*\\n {{meter-name}}\"},{\"type\":\"mrkdwn\",\"text\":\"*Threshold condition*\\n {{threshold-condition}}\"},{\"type\":\"mrkdwn\",\"text\":\"*Threshold limit*\\n {{threshold-value}}\"},{\"type\":\"mrkdwn\",\"text\":\"*Current value*\\n {{current-value}}\"}]},{\"type\":\"section\",\"block_id\":\"section790\",\"text\":{\"type\":\"mrkdwn\",\"text\":\"<{{ui-hyperlink}}|Click here to view usage details>\"}}]}",
    "thresholdCondition": "greater-than",
    "thresholdValue": "1000",
    "range":"day",
    "customerFilterMode": "per-customer",
    "enabled": true
}'
```

Sample webhook payload for **Slack**

:::CodeblockTabs
webhook\_payload

```json
{
    "text": "Amberflo Notification {{notification-name}} ",
    "blocks": [
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": "Amberflo notification: {{notification-name}}"
            }
        },
        {
            "type": "section",
            "block_id": "section567",
            "text": {
                "type": "mrkdwn",
                "text": "Your meter value has exceeded the allowed threshold limit."
            }
        },
        {
            "type": "section",
            "block_id": "section789",
            "fields": [
                {
                    "type": "mrkdwn",
                    "text": "*Customer*\n {{customer-name}}"
                },
                {
                    "type": "mrkdwn",
                    "text": "*Meter*\n {{meter-name}}"
                },
                {
                    "type": "mrkdwn",
                    "text": "*Threshold condition*\n {{threshold-condition}}"
                },
                {
                    "type": "mrkdwn",
                    "text": "*Threshold limit*\n {{threshold-value}}"
                },
                {
                    "type": "mrkdwn",
                    "text": "*Current value*\n {{current-value}}"
                }
            ]
        },
        {
            "type": "section",
            "block_id": "section790",
            "text": {
                "type": "mrkdwn",
                "text": "<{{ui-hyperlink}}|Click here to view usage details>"
            }
        }
    ]
}
```
:::

Alerts will send the following HTTP request to the **webhook endpoint**:

```curl
curl --location --request POST 'https://hooks.slack.com/services/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' \
--header 'Content-Type: application/json' \
--data-raw '{
    "text": "Amberflo Notification alert-for-prepaid ",
    "blocks": [
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": "Amberflo notification: alert-for-prepaid"
            }
        },
        {
            "type": "section",
            "block_id": "section567",
            "text": {
                "type": "mrkdwn",
                "text": "Your meter value has exceeded the allowed threshold limit."
            }
        },
        {
            "type": "section",
            "block_id": "section789",
            "fields": [
                {
                    "type": "mrkdwn",
                    "text": "*Customer*\n Oceanic Airlines"
                },
                {
                    "type": "mrkdwn",
                    "text": "*Threshold condition*\n less-than"
                },
                {
                    "type": "mrkdwn",
                    "text": "*Threshold limit*\n 1000000000"
                },
                {
                    "type": "mrkdwn",
                    "text": "*Current value*\n 40384"
                }
            ]
        },
        {
            "type": "section",
            "block_id": "section790",
            "text": {
                "type": "mrkdwn",
                "text": "<https://ui.amberflo.io/customers/54a43bba-915d-4512-a0bd-9d9debesase|Click here to view usage details>"
            }
        }
    ]
}'
```

And the rendered Slack message will look like this:

![](https://files.readme.io/21ac54f-slack-alert.jpg)


[title] Real-time Alerts
[path] Billing & Monetization/Usage & Amount Alerting/

## Real-time Usage and Billing Amount Alerts

Amberflo supports real-time alerts via email and webhooks, helping your teams take timely action based on usage or revenue thresholds.

Alerts can be configured for individual customers or applied across all customers. Trigger conditions can be based on absolute usage values or on percentage changes in metered data over a defined time period. Billing-related alerts are also supported and can be particularly useful for finance and sales teams.

**Examples of alerts you can configure:**

- Receive an email when any customer exceeds 1000 usage hours per week.
- Send a Slack message if a customer's usage drops by 20% compared to the previous week.
- Notify the account manager by email when a customer surpasses the number of prepaid credits in their wallet.
- Alert the Head of Finance at key revenue milestones — 25%, 50%, 75%, and 90% of the quarterly goal.

The easiest way to configure [Alerts is via the UI](https://ui.amberflo.io/alerts). When configuring an alert in the UI, there are three primary steps.

### Step 1 - Select the alert type

Select the alert **type** from one of the five options provided.



![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/g8W8GONGjTMSsGWwdSvIW_screenshot-2024-10-29-at-52210-pm.png)

### Step 2 - Name & configure the alert

To set up a new alert, start by giving it a clear name and defining its configuration.

The first step is to determine the **scope** of the alert. You can apply it across all customers, target each customer individually, or configure it for a specific customer only. Depending on the type of alert, additional configuration options may become available.

The screenshot below shows the options when the **Meter Usage** alert type is selected.

![](https://files.readme.io/bb32aee-Screenshot_2023-07-14_at_12.42.23_PM.png)

### Step 3 - Select the notification frequency and method

Next, select how often the alert should be triggered by setting the **notification frequency**. This controls how frequently notifications will be sent once the alert conditions are met.

Then choose the **notification method**. By default, alerts are sent via email. However, you can also configure a webhook to trigger automated downstream actions or integrate with messaging platforms like Slack.&#x20;

![](https://files.readme.io/fef931a-Screenshot_2023-07-14_at_12.46.32_PM.png)

Alerts can be delivered through email, webhooks, or both.

Webhook payloads and headers can be customized using context variables to tailor the integration to your system’s needs.&#x20;
See [Alert Webhooks](docId\:PaVpqkVIi_-_PyRGz2Fe6) for details.

Sample email:



![](https://files.readme.io/1edb111-signal_email.jpg)

Sample webhook for Slack:



![](https://files.readme.io/286c0dc-slack-alert.jpg)


[title] Alert Types
[path] Billing & Monetization/Usage & Amount Alerting/

The following alert (aka. notification) types can be created using the [Alerts UI Dashboard](https://ui.amberflo.io/alerts) or [Alerts API](docId\:TXaVag-nUfaH_djTdJXlS)&#x20;

## Usage

Usage alerts are **scheduled** checks that trigger based on meter consumption.&#x20;

1. Specify using a **cron&#x20;**&#x65;xpression ([some cron examples](https://crontab.guru/examples.html)).
2. Set the **range&#x20;**(`hour`, `day`, `week`, `month`).
3. Select the meter by setting the **meterId.&#x20;**&#x49;t can found by calling the \[Meter Definition API].([Get a list of meters](docId\:i66o22RFPYjUNKud2fZ6i)) or [UI Dashboard](https://ui.amberflo.io/meters)
4. Select the **thresholdCondition&#x20;**&#x61;nd set the **thresholdValue**

At the defined schedule, Amberflo calls the [Usage API](docId\:ARr7X-fDEquk7pUKn3VU_)  to evaluate the condition against the current usage. If the condition is met for a customer, the alert is triggered.

```curl
curl --location --request POST 'https://app.amberflo.io/notification' \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: xyz' \
--data-raw '{
    "name": "usage-alert",
    "meterId" : "4d1ca680-4974-11ec-ad9b-e1ce3f8b2ad3",
    "cron": "0 0 * * 0",
    "webhookUrl": "https://hooks.slack.com/services/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3",
    "email":"batman@amberflo.io",
    "thresholdCondition": "greater-than",
    "thresholdValue": "1000",
    "range":"day",
    "customerFilterMode": "per-customer",
    "enabled": false
}'
```


Prepaid Credits
---------------

This is a real-time alert that applies only to prepaid customers. It triggers when a customer's prepaid credits fall below a defined threshold, prompting them to top up.

- Set the `thresholdValue` for the prepaid balance.
- The `thresholdCondition` is always *less-than*.
- The evaluation is done for the **current billing cycle**.

If a customer's prepaid balance on the active invoice drops below the threshold, the alert will be fired.

```curl
curl --location --request POST 'https://app.amberflo.io/notification' \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: xyz' \
--data-raw '{
    "name": "alert-for-prepaid",
    "notificationType": "prepaid",
    "email": ["batman@gmail.com", "batman@wayne.io"],
    "webhookUrl": "https://hooks.slack.com/services/T01HZB7A0M6/B01P3J0LE0H/OBRoTt8TmrJ2Zbv3bQAqH88",
    "thresholdValue": "1000",
    "customerFilterMode": "per-customer",
    "enabled": true
}'
```

## Invoice

Invoice alerts are **real-time** and apply to customers with billing enabled through an assigned pricing plan.

- Set the `thresholdValue` for the total invoice amount.
- The `thresholdCondition` is always *greater-than*.
- The evaluation is based on the **current billing cycle** and applies to the active invoice.

If the total on a customer’s current invoice exceeds the set threshold, an alert is triggered.

```curl
curl --location --request POST 'https://app.amberflo.io/notification' \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: xyz' \
--data-raw '{
    "name": "invoice-tracker",
    "notificationType": "invoice",
    "email": ["batman@wayne.io"],
    "thresholdValue": "500",
    "customerFilterMode": "per-customer",
    "enabled": true
}'
```

## Product Item Usage Limit

This **real-time** alert is relevant only to invoices that include a breakdown by product items. Since product items are mapped to specific meters, the alert monitors meter unit consumption (or quantity) for a product item as displayed on the invoice.

![](https://files.readme.io/730d3a1-invoice_product_item_.jpg)

1. Select the meter by setting the `meterId`**.&#x20;**&#x49;t can found by calling the [List Meters API](docId\:i66o22RFPYjUNKud2fZ6i)  or [UI Dashboard](https://ui.amberflo.io/meters)
2. Select the pricing plan by setting the `productPlanId`. This can be obtained from the [Account Pricing API ](docId:-r4cpBaBC5wUBoiwpm0fb) or [UI Dashboard](https://ui.amberflo.io/billing-cloud/pricing-plans)
3. Set the meter units limit `thresholdValue`**.**
4. The `thresholdCondition` is always **greater-than**
5. The **range&#x20;**&#x6F;r time bucket is always the current billing cycle.

If the *meter units* or quantity on the current invoice for the selected meter and pricing plan exceeds the `thresholdValue`, then it will fire an alert.

```curl
curl --location --request POST 'https://app.amberflo.io/notification' \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: xyz' \
--data-raw '{
    "name": "product-item-units-tracker",
    "notificationType": "product-item-units",
    "productPlanId":"ff9c55a9-4796-480f-ab77-2ae943ae1700",
    "email": ["batman@wayne.io"],
    "thresholdValue": "100",
    "customerFilterMode": "per-customer",
    "meterId":"21b71110-ee51-11ec-ad99-5b1b04dba7a5",
    "enabled": true
}'
```

## Product Item Charge Amount

This is the same as **Product Item Usage Limit**, except it tracks the product item total charge on the current invoice instead of the meter units.

1. Select the meter by setting the `meterId`. It can found by calling the [List Meters API](docId\:i66o22RFPYjUNKud2fZ6i)  or [UI Dashboard](https://ui.amberflo.io/meters)
2. Select the pricing plan by setting the `productPlanId`. This can be obtained from the [Account Pricing API ](docId:-r4cpBaBC5wUBoiwpm0fb) or [UI Dashboard](https://ui.amberflo.io/billing-cloud/pricing-plans)
3. Set the product item charge limit `thresholdValue`
4. The `thresholdCondition` is always **greater-than**
5. The **range&#x20;**&#x6F;r time bucket is always the current billing cycle.

If the total product item total charge on the current invoice for the selected meter and pricing plan exceeds the `thresholdValue`, then it will fire an alert.

```curl
curl --location --request POST 'https://app.amberflo.io/notification' \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: xyz' \
--data-raw '{
    "name": "product-item-price-tracker",
    "notificationType": "product-item-price",
    "productPlanId":"ff9c55a9-4796-480f-ab77-2ae943ae1700",
    "email": ["batman@wayne.io"],
    "thresholdValue": "200",
    "customerFilterMode": "per-customer",
    "meterId":"21b71110-ee51-11ec-ad99-5b1b04dba7a5",
    "enabled": true
}'
```

## Undefined Customer

This scheduled alert is triggered when ingested records reference a customer that does not exist in Amberflo. To prevent alert fatigue, there is a one-hour cool-down period between alerts of this type.

```curl
curl --location --request POST 'https://app.amberflo.io/notification' \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: xyz' \
--data-raw '{
    "name": "Undefined Customer",
    "notificationType": "undefined-customer",
    "email": ["batman@wayne.io"]
}'
```

## Undefined Meter

This scheduled alert is triggered when ingested records reference a meter that has not been defined in Amberflo. Like the undefined customer alert, it includes a one-hour cool-down period between notifications.

```curl
curl --location --request POST 'https://app.amberflo.io/notification' \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: xyz' \
--data-raw '{
    "name": "Undefined Meter",
    "notificationType": "undefined-meter",
    "email": ["batman@wayne.io"]
}'
```


[title] Untitled
[path] Billing & Monetization/


[title] Invoice Creation
[path] Billing & Monetization/Invoicing & Payments/

Invoices are automatically generated at the end of each billing period. They are organized by customer and can be accessed at any time through the **Invoices** tab in the Amberflo console. You can also view invoices directly from each **customer’s record**.

The total invoice amount is calculated using the **pricing plan assigned to the customer**, along with each individual **product item** defined in that plan. All values are aggregated based on usage and pricing rules.

Once generated, invoices are automatically sent to **Stripe** for payment and processing.

![](https://files.readme.io/1b107dd-invoicing.png)


[title] Pricing Plan Management
[path] Billing & Monetization/Pricing Plans/



## **Understanding the Pricing Plan Lifecycle**

**Draft Mode**

When you create a new pricing plan, it begins in **Draft** mode. In this state, you can make any necessary changes, including modifying line items or updating prices. You can save your progress and return later to continue editing.

**Activating a Plan**

To move a plan into production, change its status to **Active** and save it. After a plan is activated and saved:

- The plan becomes read-only and cannot be edited.
- The plan is eligible to be assigned to customers.

## **Assigning Plans to Customers**

### **Assigning Active Plans**

Only plans in the **Active** state can be assigned to customers. Each customer can be assigned only one pricing plan at a time.



**Default Plan Behavior**

Amberflo allows multiple active plans, but one of them must be designated as the **Default Plan**.

- If there is **only one** active plan, it is **automatically set as the default**.
- If there are **two or more** active plans, **you must manually select the default**.

When a new customer is created in Amberflo, **the system automatically assigns the default plan** unless a different plan is specified.

If **pricing plans are created after the customer already exists**, the default plan will be assigned automatically when the customer’s first meter event is ingested. In that case, the plan’s start time will default to the beginning of the day on which the first event was received.

## **Updating a Pricing Plan**

Since active plans cannot be edited, updates are handled through versioning:

1. **Create a New Plan**
2. **Activate the New Plan**
3. **Deprecate the Previous Plan**

Customer migration happens automatically at the end of the current billing cycle.

## **Retiring and Migrating Plans**

Amberflo allows you to **deprecate any plan and migrate customers** to a new one using built-in automation. When you deprecate a plan:

- The system prompts you to **choose a replacement active plan**.
- **Customers are reassigned automatically** based on their billing cycle.

## **Changing a Plan Mid-Cycle**

**You can transition customers to a new plan at any point during a billing cycle.** A customer can only have one active pricing plan at a time, so any update involves switching from the current plan to a new one.

**If Proration Is Enabled**

- **Fixed charges are prorated** based on the number of days each plan was active during the cycle.
- **One for the original plan** with prorated fixed charges and usage-based charges. **One for the new plan**, also with prorated charges.

**Example:** If a customer is transitioned on day 10 of a 30-day month:

- They are billed for one-third of the original plan’s fixed charge.
- They are billed for two-thirds of the new plan’s fixed charge.

**If Proration Is Not Enabled**

- The customer is **billed the full fixed charge from both plans**.
- **Two invoices are issued** for the billing period, each reflecting the applicable fixed and usage-based charges.


[title] Creating Pricing Plans
[path] Billing & Monetization/Pricing Plans/

To define a custom pricing plan using usage-based dimension pricing, you will construct a Pricing Plan object. This object is composed of one or more Price Generators. Each Price Generator corresponds to a specific meter, a resource or service you want to charge for, and defines how usage of that meter is priced based on dimension values.&#x20;

## Conceptual Overview:

Think of a Price Generator as the pricing definition for a single meter. For every meter you intend to bill your customer for, you will create one Price Generator. Each Price Generator will have one or more Price Tiers. Price Tiers are where you will set the price based on specific parameters such as dimensions, block size and tiered pricing. The completed Price Generator are then bundled together in the Pricing plan to create complete pricing for your customers.



**Each Price Generator includes:**

- A list of **dimensionKeys** (e.g., `model_name`, `input-output`, `instance_type`) that determine how pricing is calculated.
- A list of **Price Tiers**, where each tier defines a price for a specific combination of dimension values

**Each Price Tier defines:**

- A specific combination of dimensionValues (e.g., \["gpt-4", "input"])
- One or more volume-based rates (e.g., dimensional pricing, tiered pricing)

## Workflow Summary:

1. Define one Price Generator per meter.
2. Specify the relevant dimensionKeys.
3. Create one or more Price Tiers per generator, each with dimension values and rates.
4. Wrap the Price Generators into a single Pricing Plan object.
5. Send the full object to the API endpoint:
   1. \`POST [https://app.amberflo.io/payments/pricing/amberflo/account-pricing/product-plans/template/custom\_pricing\_plan](https://app.amberflo.io/payments/pricing/amberflo/account-pricing/product-plans/template/custom_pricing_plan)\`



## Pricing Plan Object Structure

```javascript
{
  "id": "uuid",
  "type": "custom_pricing_plan",
  "planName": "Name of pricing plan",
  "description": "Optional plan description",
  "billingPeriod": {
    "interval": "month",
    "intervalsCount": 1,
    "frequency": "month"
  },
  "planLevelFreeTier": null,
  "realTimePricing": false,
  "lockingStatus": "open",
  "isDefault": false,
  "feeMap": {},
  "priceGenerators": []
}
```

## Price Generator Structure

```javascript
{
  "id": "uuid",
  "type": "usage_based_dimension",
  "usageTransformer": null,
  "productItemId": "uuid of the meter you are creating the pricing for",
  "productItemPriceName": "uuid",
  "lockingStatus": "open",
  "dimensionKeys": ["model_name", "input-output"],
  "priceTiers": []
}
```

**Note:&#x20;**&#x54;o get the `productItemId` you need to call the Get all product items API. It will return an object with onformation on your meters for the purposes of pricing. Find the meter you want to price and use the `id` field for the `productItemId` .

## Price Tier Structure

```javascript
{
  "dimensionValues": ["gpt-4", "input"],
  "priceTiers": [
    {
      "startAfterUnit": 0,
      "batchSize": 1,
      "pricePerBatch": 0.000002
    },
    {
      "startAfterUnit": 10000000,
      "batchSize": 1,
      "pricePerBatch": 0.000001
    }
  ],
  "leafNodeType": "PricePerUnitLeafNode"
}
```

# Example Variations

## Basic Usage-Based Pricing

```javascript
{
  "type": "usage_based_no_dimension",
  "productItemId": "uuid",
  "priceTiers": [
    {
      "startAfterUnit": 0,
      "batchSize": 1,
      "pricePerBatch": 3.14
    }
  ],
  "leafNodeType": "PricePerUnitLeafNode"
}
```

## Dimension-Based Pricing

```javascript
{
  "type": "usage_based_dimension",
  "dimensionKeys": ["model_name", "input-output"],
  "priceTiers": [
    {
      "dimensionValues": ["gpt-4", "input"],
      "priceTiers": [
        {"startAfterUnit": 0, "batchSize": 1, "pricePerBatch": 0.000002}
      ],
      "leafNodeType": "PricePerUnitLeafNode"
    }
  ]
}
```

## Flat Rate Pricing

Flat rates are defined in the `feeMap` field within the Pricing Plan object.

```javascript
"feeMap": {
  "a5a5cc62-f2a0-44d2-9ffc-124664fdf69b": {
    "cost": 500,
    "discountable": false,
    "description": "Recurring fee",
    "name": "Access Fee",
    "isOneTimeFee": false,
    "isProrated": false,
    "prorateToDay": false,
    "prepayable": true,
    "id": "a5a5cc62-f2a0-44d2-9ffc-124664fdf69b"
  }
}
```

Notes and Best Practices

- `dimensionKeys` and `dimensionValues` must match in order.
- Use `null` in `dimensionValues` to wildcard a dimension.
- Sort more specific price tiers before general ones. It will use the first matching condition it finds.
- Set `lockingStatus` to `open` if you want to make more changes. Lock your plan (lockingStatus: "close\_to\_changes") to activate.


[title] Meter Corrections (undo events)
[path] Metering/

# Introduction

Occasionally, a source system may send **incorrect or unintended meter events** to Amberflo.
&#x20;This could be due to:

- A **bug** that results in over-reporting or under-reporting usage
- An event that was mistakenly triggered and needs to be **retracted**

**How do you correct an invoice or bill based on inaccurate usage data?**

**How do you undo (delete) incorrect meters?**

### **Amberflo's Solution: Undo Ingested Meters**

Amberflo provides a built-in capability to **cancel (undo)** one or more meter events after ingestion.

This ensures Amberflo remains a **reliable system of record** by supporting:

- **Idempotency**
- **Data deduplication**
- **Accurate correction of historical usage**

### **How It Works**

Amberflo exposes an **easy-to-use API** that allows you to:

- Cancel a **single usage event**
- Cancel a **batch of events**

When meter events are canceled, the system:

- **Updates all relevant usage aggregations**
- **Propagates changes to downstream systems**, including:
  - Invoices
  - Rewards
  - Payment calculations

This guarantees your billing and reporting stay accurate, even when corrections are needed.

# Examples where meter events need to be undone (and corrected)

## System Errors

These errors are typically the result of **bugs or data issues** in the source system that sent incorrect or incomplete meter events to Amberflo. Since the root cause lies within the originating system, we refer to them as **system errors**.

### Example:

Let’s assume a company named Smart-ML uses Amberflo for usage-based pricing.

Smart-ML tracks a meter called api-calls, which includes the following dimensions: region, cluster and host-type.

**What went wrong:**

- On February 2, 2022 at 1:00 AM, a bad deployment introduced a bug.
- As a result, all api-calls events sent to Amberflo after that time were missing values for the cluster and host-type dimensions.

**Discovery and Correction:**

- On February 15, 2022 at 4:00 PM, George, a Product Manager at Smart-ML, noticed irregularities in the usage data.
- After investigating, the Smart-ML on-call engineering team concluded that all api-calls meter events sent since 2/2/2022 were invalid and needed to be undone and corrected.

This is a typical case where meter correction via Amberflo’s undo functionality is essential to maintain data accuracy and ensure downstream billing or analytics remain trustworthy.

## Unintentional or unsuccessful usage

Unlike systematic errors caused by bugs or incorrect data in the source system, **unintentional or unsuccessful usage** refers to events that occur naturally during regular use of a service.

In these cases, a customer attempts (or begins) to use a resource, but the intended action is ultimately **canceled or aborted**, resulting in the resource **not actually being used**.

### Example:

*Rabbit-IO* is a company that enables users to build and run big-data pipelines on its clusters—essentially a PaaS provider. *Smart-ML* is a customer of *Rabbit-IO*.

On 3/3/2022 at 3:21 AM, *Smart-ML* submits a request to start a data pipeline job.
&#x20;*Rabbit-IO*:

- Assigns machines from Cluster X for the job
- Sends an event to Amberflo indicating that *Smart-ML* has started using Cluster X

However, if the job was canceled shortly afterward (e.g., due to a user action or a failure), the resource wasn’t actually used—making the original meter event **invalid** and a candidate for correction.

```json
{
    "customerId": "Smart-ML-123", // the actual customer id of Smart-ML
    "meterApiName": "cpu-used",
    "meterValue": 500, // let's assume the job takes 500 cpus
    "meterTimeInMillis": 1646320860, // 3/3/2022 at 3:21am
    "dimensions":{
        "cluster": "X",
        "tenant_type": "Tech"
    }
}
```

But then, 15 minutes later at 3:36 AM, Cluster X goes down and the job fails.
Since the data pipeline job was never successfully executed, Rabbit-IO decides \*not to charge Smart-ML for the failed usage. As a result, they want to cancel the original meter event sent to Amberflo—because the resource was ultimately not used.

# Coping with System Errors

To help cope with ‘System Errors’ Amberflo exposes a **‘filtering-rules’ API**. This API allows you to define rules for filtering out and removing sets of previously ingested meter events.

### Example:

If we take the ‘Smart-ML’ example, then in order to filter out bad ‘api-calls’, all ‘Smart-ML’ needs to do is:

[https://app.amberflo.io/ingest-snapshot/custom-filtering-rules](https://app.amberflo.io/ingest-snapshot/custom-filtering-rules) :

```json
{
    "type": "by_property_filter_out",
    "id": "2-2-2022-api-calls-cancelation",
    "ingestionTimeRange": {
        "startTimeInSeconds": 1643763600, // 2/2/2022 1am
        "endTimeInSeconds": 1644940800  // 2/15/2022 4pm
    },
    "meterApiName": "api-calls"
}
```

### Advanced filters:

In some cases, you may not need to cancel all meter events within a time range—only a subset that meets specific criteria.

### **Example :**

Let’s assume *Smart-ML* experienced a bad deployment only in the region us-west-1.

In this case, there's no need to undo all api-calls events between 2/2/2022 1:00 AM and 2/15/2022 4:00 PM. Instead, you want to target only the events where the region dimension is equal to us-west-1.

### **How to Apply the Filter**

To define this type of filter, use the dimensionValuesMap property and specify the **dimension keys and values** you want to match.
This ensures that only events with the specified dimension values will be selected for cancellation.

[https://app.amberflo.io/ingest-snapshot/custom-filtering-rules](https://app.amberflo.io/ingest-snapshot/custom-filtering-rules) :

```json
{
    "type": "by_property_filter_out",
    "id": "2-2-2022-api-calls-cancelation",
    "ingestionTimeRange": {
        "startTimeInSeconds": 1643763600, // 2/2/2022 1am
        "endTimeInSeconds": 1644940800  // 2/15/2022 4pm
    },
    "meterApiName": "api-calls",
    "dimensionValuesMap": {
        "region": [
            "us-west-1"
        ]
    }
}
```

For more information, please refer to our filtering rules API:&#x20;
[Create or update a filtering rule](https://docs.amberflo.io/reference/create-or-update-a-filtering-rule)

📘 *Notice*

When using the filtering and meter cancellation tools, please keep the following considerations in mind:

***Time limitations***

- Meter events can only be **canceled within one year** of their original ingestion date.
- Events older than one year are not eligible for cancellation.

***Impact on Payments***

- You can **cancel usage events and replace them** with corrected ones as needed.
- However, cancellations **cannot modify invoices** that are already **locked** (i.e., past their grace period).

**What does “locked” mean?**

- A locked invoice is considered **final and paid**.
- In these cases, any necessary adjustments should be handled via:
  - A refund
  - A future discount or credit


Canceling unintentional or unsuccessful events
==============================================

You can use the **filtering mechanism** described earlier to target and cancel specific events based on their **unique ID**.

### **Method 1: Using Unique ID**

If you have the **unique ID** of the event to be canceled:

- Add it to the dimensionValuesMap property
- This allows for a precise and reliable cancellation, assuming:
  - You have the correct unique ID
  - The same unique ID hasn’t been reused for another event

### **Method 2: Using Resource-Related Properties**

If the unique ID is not available, but you have other identifying information—such as:

- Customer ID
- Meter name
- Dimension values (e.g., region, cluster)

Then Amberflo can support an alternative filtering approach using those **resource-related properties**.

This allows you to **cancel specific events** even without the exact unique ID, provided that the filtering criteria are sufficient to isolate the correct events.

First, let’s define “**resource related properties**”. These related properties depend on the meter type as described below:

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/BQl-NMir1Cj5aQfNCKAWD_image.png)

As described above, the resource-related properties help identify the specific **resource instance** that was used to serve the customer.

To cancel the events associated with that resource, simply send a **new ingest event** that includes the appropriate resource ID and related dimensions and an additional dimension: `“aflo.cancel_previous_resource_event”` with `“true”` as its value. This instructs Amberflo to **locate and cancel any previously ingested events** related to that resource.
For example, for the ‘Rabbit-IO’ example mentioned above, ‘Rabbit-IO’ needs to send the following ingest event in order to cancel the usage:

[https://app.amberflo.io/ingest](https://app.amberflo.io/ingest) :

```json
[{
    "customerId": "Smart-ML-123", // the actual customer id of Smart-ML
    "meterApiName": "cpu-used",
    "meterValue": 0, // the value of the cancellation event doesn’t matter
    "meterTimeInMillis": 1666278270000, // 10/20/2022 at 15:04:30
    "dimensions": {
        "cluster": "X",
        "aflo.cancel_previous_resource_event": "true"
    }
}]
```

📘 ***Limitations***

When canceling events using **resource-related properties** and the aflo.cancel\_previous\_resource\_event flag, there are a few important constraints to keep in mind:

**1. 9-Hour Time Limit**

You have up to **9 hours from the time a resource starts being used** to cancel the associated usage event.

**2. Timestamp of the Cancellation Event**

- The **timestamp of the cancellation event** must be **no later than 9 hours after** the original usage event.
- For best results, set the cancellation event’s timestamp to be:
  - As **close as possible** to the original event
  - Or even use the **exact same timestamp** as the event you are canceling

**Why These Limitations Exist*****?***

When you define a cancellation event using only **resource-related properties**, you're not explicitly identifying the exact meter event to be canceled (e.g., by unique ID).

Because of this, Amberflo operates under the assumption that you're targeting a **recent event** and will:

- Search for the **most recent event** related to the specified resource within the last 9 hours
- Cancel that event **if it exists**

**Important:**
If the same resource was used more than once in that time window, Amberflo will cancel only the most recent of those events.

Also note that the value of the cancellation event does not matter, because its only purpose is to signal the system to drop the matching usage record—not to report usage.

## Canceling unintentional or unsuccessful Usage (advance)

The `aflo.cancel_previous_resource_event flag`, as described earlier, is used for **dynamically canceling usage events**.
This mechanism can target events such as: **start&#x20;**&#x75;sing a resourc&#x65;**, stop&#x20;**&#x75;sing a resourc&#x65;**,&#x20;**&#x6F;r an **update&#x20;**&#x74;o the usage rat&#x65;**.**

For **Max** or **Total Duration meters**, Amberflo supports an additional flag to refine cancellation behavior: `"aflo.ignore_cancellation_if_no_usage"` This secondary flag is only meaningful when used together with `aflo.cancel_previous_resource_event`. If aflo.ignore\_cancellation\_if\_no\_usage is set to true, Amberflo will: ignore the cancellation of a "stop" event if there was **no usage** recorded between the original start and stop events.

This prevents cancellations from **invalidating periods where no actual usage occurred**—a useful safeguard for duration-based meters.

### **Example:**

Let’s assume you are using a total-duration meter and the following three events were ingested:

```json
// Start usage
{
    "customerId": "Smart-ML-123", // the actual customer id of Smart-ML
    "meterApiName": "cpu-used",
    "meterValue": 10, 
    "meterTimeInMillis": 1646321760, // 3/3/2022 at 3:36am
    "dimensions": {
        "cluster": "X",
    }
},
// Stop usage
{
    "customerId": "Smart-ML-123", // the actual customer id of Smart-ML
    "meterApiName": "cpu-used",
    "meterValue": 0, 
    "meterTimeInMillis": 1646321820, // 3/3/2022 at 3:37am
    "dimensions": {
        "cluster": "X",
    }
},
// Cancelation
{
    "customerId": "Smart-ML-123", // the actual customer id of Smart-ML
    "meterApiName": "cpu-used",
    "meterValue": 0,
    "meterTimeInMillis": 1646321880, // 3/3/2022 at 3:38am
    "dimensions": {
        "cluster": "X",
        "aflo.cancel_previous_resource_event": "true"
    }
}
```

As mentioned, event cancelation cancels the latest event regardless of what it indicates. So in the example above, after applying the dynamic cancellation, we will be left with only the start event:

```json
// Start usage
{
    "customerId": "Smart-ML-123", // the actual customer id of Smart-ML
    "meterApiName": "cpu-used",
    "meterValue": 10, 
    "meterTimeInMillis": 1646321760, // 3/3/2022 at 3:36am
    "dimensions": {
        "cluster": "X",
    }
}
```

Now, if we want the dynamic cancellation to cancel existing **usage events&#x20;**(as opposed to cancelling any type of events), we will need to add the `"aflo.ignore_cancellation_if_no_usage"`, with `"true"` as a value, to the 3rd event. So the sequence of events sent to Amberflo will look like:

```json
// Start usage
{
    "customerId": "Smart-ML-123", // the actual customer id of Smart-ML
    "meterApiName": "cpu-used",
    "meterValue": 10, 
    "meterTimeInMillis": 1646321760, // 3/3/2022 at 3:36am
    "dimensions": {
        "cluster": "X",
    }
},
// Stop usage
{
    "customerId": "Smart-ML-123", // the actual customer id of Smart-ML
    "meterApiName": "cpu-used",
    "meterValue": 0, 
    "meterTimeInMillis": 1646321820, // 3/3/2022 at 3:37am
    "dimensions": {
        "cluster": "X",
    }
},
// Cancelation
{
    "customerId": "Smart-ML-123", // the actual customer id of Smart-ML
    "meterApiName": "cpu-used",
    "meterValue": 0,
    "meterTimeInMillis": 1646321880, // 3/3/2022 at 3:38am
    "dimensions": {
        "cluster": "X",
        "aflo.cancel_previous_resource_event": "true",
        "aflo.ignore_cancellation_if_no_usage": "true"  // <== NOTICE: Additional Secondary Flag
    }
}
```

In this case the result of the system run will be:

```json
{
    "customerId": "Smart-ML-123", // the actual customer id of Smart-ML
    "meterApiName": "cpu-used",
    "meterValue": 10, 
    "meterTimeInMillis": 1646321760, // 3/3/2022 at 3:36am
    "dimensions": {
        "cluster": "X",
    }
},
// Stop usage
{
    "customerId": "Smart-ML-123", // the actual customer id of Smart-ML
    "meterApiName": "cpu-used",
    "meterValue": 0, 
    "meterTimeInMillis": 1646321820, // 3/3/2022 at 3:37am
    "dimensions": {
        "cluster": "X",
    }
}
```

This is because there was no usage to cancel for the 3rd event.

📘 ***NOTICE***

***"aflo.ignore\_cancellation\_if\_no\_usage" has a meaning only in the context of "Max" or "Total Duration" meters.***

[title] Meter Ingest Automatic Failover
[path] Metering/

For **continuous** and resilient production ingestion, Amberflo provides a high-availability endpoint: `ingest.amberflo.io`.

This endpoint is backed by a multi-regional deployment, ensuring that even if the primary ingestion region experiences issues, your meter events will still be [received and processed](docId\:NsJPk3F7DuAkP-05PFyHV)  through a failover path.

To take full advantage of this failover capability, we recommend implementing a **retry policy** in your API client.
**Retry guidelines:**

- Use an **exponential backoff strategy**
- Ensure at least a couple of retry attempts are made **after 20 seconds** from the first failure
- Retry specifically on the following HTTP status codes:
  - 429 (Too Many Requests)
  - Any 5xx server error

If you use one of our SDKs, they have built-in logic for a suitable **retry policy**.&#x20;

- `3.1.0` for [Python](docId\:UlRadkCjdq9zXsR6x8jR-)&#x20;
- `2.6.0` for [TypeScript / JavaScript](docId\:MttOVIPbolHY78UPshT4L)&#x20;
- `2.1.0` for [Go](docId\:LBcsMk_jRBKJ_yRnsRTqM)&#x20;
- `2.1.0` for [Java](docId:0yua0Xf8JxOFq5gx48tor)&#x20;


[title] Real-time Alerts
[path] Metering/Usage Alerting /

## Real-time Usage and Billing Amount Alerts

Amberflo supports real-time alerts via email and webhooks, helping your teams take timely action based on usage or revenue thresholds.

Alerts can be configured for individual customers or applied across all customers. Trigger conditions can be based on absolute usage values or on percentage changes in metered data over a defined time period. Billing-related alerts are also supported and can be particularly useful for finance and sales teams.

**Examples of alerts you can configure:**

- Receive an email when any customer exceeds 1000 usage hours per week.
- Send a Slack message if a customer's usage drops by 20% compared to the previous week.
- Notify the account manager by email when a customer surpasses the number of prepaid credits in their wallet.
- Alert the Head of Finance at key revenue milestones — 25%, 50%, 75%, and 90% of the quarterly goal.

The easiest way to configure [Alerts is via the UI](https://ui.amberflo.io/alerts). When configuring an alert in the UI, there are three primary steps.

### Step 1 - Select the alert type

Select the alert **type** from one of the five options provided.



![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/g8W8GONGjTMSsGWwdSvIW_screenshot-2024-10-29-at-52210-pm.png)

### Step 2 - Name & configure the alert

To set up a new alert, start by giving it a clear name and defining its configuration.

The first step is to determine the **scope** of the alert. You can apply it across all customers, target each customer individually, or configure it for a specific customer only. Depending on the type of alert, additional configuration options may become available.

The screenshot below shows the options when the **Meter Usage** alert type is selected.

![](https://files.readme.io/bb32aee-Screenshot_2023-07-14_at_12.42.23_PM.png)

### Step 3 - Select the notification frequency and method

Next, select how often the alert should be triggered by setting the **notification frequency**. This controls how frequently notifications will be sent once the alert conditions are met.

Then choose the **notification method**. By default, alerts are sent via email. However, you can also configure a webhook to trigger automated downstream actions or integrate with messaging platforms like Slack.&#x20;

![](https://files.readme.io/fef931a-Screenshot_2023-07-14_at_12.46.32_PM.png)

Alerts can be delivered through email, webhooks, or both.

Webhook payloads and headers can be customized using context variables to tailor the integration to your system’s needs.&#x20;
See [Alert Webhooks](docId\:PaVpqkVIi_-_PyRGz2Fe6) for details.

Sample email:



![](https://files.readme.io/1edb111-signal_email.jpg)

Sample webhook for Slack:



![](https://files.readme.io/286c0dc-slack-alert.jpg)


[title] Alert Webhooks
[path] Metering/Usage Alerting /

Alerts can be delivered using a webhook. When configured this way, Amberflo will make an HTTP POST request to the specified endpoint whenever the alert threshold condition is met.

The request will contain a JSON payload with the relevant alert information. If the endpoint responds with a 200 status code, the delivery is considered successful. Any other status code will be treated as a failure, and the system will retry sending the alert.

## Context Variables

The following context variables can be used to set a webhook request header and payload:

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/pYxtzp5tSudIbfrxJlQvq_image.png)

## Sample Webhook for Slack

Create the alert using the [Alerts UI](https://ui.amberflo.io/alerts/create):

![](https://files.readme.io/1c85b5f-webhook_UI.jpg)

Or using the [Alerts API](docId\:TXaVag-nUfaH_djTdJXlS) :

```curl
curl --location --request POST 'https://app.amberflo.io/notification' \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: xyz' \
--data-raw '{
    "name": "usage-alert",
    "meterId" : "4d1ca680-4974-11ec-ad9b-e1ce3f8b2ad3",
    "cron": "0 0 * * 0",
    "webhookUrl": "https://hooks.slack.com/services/T01HZB7A0M6/B01N83NQV6H/lMa2sYqDUnV3a7cLbBD9234",
    "webhookPayload": "{\"text\":\"Amberflo Notification {{notification-name}} \",\"blocks\":[{\"type\":\"section\",\"text\":{\"type\":\"mrkdwn\",\"text\":\"Amberflo notification: {{notification-name}}\"}},{\"type\":\"section\",\"block_id\":\"section567\",\"text\":{\"type\":\"mrkdwn\",\"text\":\"Your meter value has exceeded the allowed threshold limit.\"}},{\"type\":\"section\",\"block_id\":\"section789\",\"fields\":[{\"type\":\"mrkdwn\",\"text\":\"*Customer*\\n {{customer-name}}\"},{\"type\":\"mrkdwn\",\"text\":\"*Meter*\\n {{meter-name}}\"},{\"type\":\"mrkdwn\",\"text\":\"*Threshold condition*\\n {{threshold-condition}}\"},{\"type\":\"mrkdwn\",\"text\":\"*Threshold limit*\\n {{threshold-value}}\"},{\"type\":\"mrkdwn\",\"text\":\"*Current value*\\n {{current-value}}\"}]},{\"type\":\"section\",\"block_id\":\"section790\",\"text\":{\"type\":\"mrkdwn\",\"text\":\"<{{ui-hyperlink}}|Click here to view usage details>\"}}]}",
    "thresholdCondition": "greater-than",
    "thresholdValue": "1000",
    "range":"day",
    "customerFilterMode": "per-customer",
    "enabled": true
}'
```

Sample webhook payload for **Slack**

:::CodeblockTabs
webhook\_payload

```json
{
    "text": "Amberflo Notification {{notification-name}} ",
    "blocks": [
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": "Amberflo notification: {{notification-name}}"
            }
        },
        {
            "type": "section",
            "block_id": "section567",
            "text": {
                "type": "mrkdwn",
                "text": "Your meter value has exceeded the allowed threshold limit."
            }
        },
        {
            "type": "section",
            "block_id": "section789",
            "fields": [
                {
                    "type": "mrkdwn",
                    "text": "*Customer*\n {{customer-name}}"
                },
                {
                    "type": "mrkdwn",
                    "text": "*Meter*\n {{meter-name}}"
                },
                {
                    "type": "mrkdwn",
                    "text": "*Threshold condition*\n {{threshold-condition}}"
                },
                {
                    "type": "mrkdwn",
                    "text": "*Threshold limit*\n {{threshold-value}}"
                },
                {
                    "type": "mrkdwn",
                    "text": "*Current value*\n {{current-value}}"
                }
            ]
        },
        {
            "type": "section",
            "block_id": "section790",
            "text": {
                "type": "mrkdwn",
                "text": "<{{ui-hyperlink}}|Click here to view usage details>"
            }
        }
    ]
}
```
:::

Alerts will send the following HTTP request to the **webhook endpoint**:

```curl
curl --location --request POST 'https://hooks.slack.com/services/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' \
--header 'Content-Type: application/json' \
--data-raw '{
    "text": "Amberflo Notification alert-for-prepaid ",
    "blocks": [
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": "Amberflo notification: alert-for-prepaid"
            }
        },
        {
            "type": "section",
            "block_id": "section567",
            "text": {
                "type": "mrkdwn",
                "text": "Your meter value has exceeded the allowed threshold limit."
            }
        },
        {
            "type": "section",
            "block_id": "section789",
            "fields": [
                {
                    "type": "mrkdwn",
                    "text": "*Customer*\n Oceanic Airlines"
                },
                {
                    "type": "mrkdwn",
                    "text": "*Threshold condition*\n less-than"
                },
                {
                    "type": "mrkdwn",
                    "text": "*Threshold limit*\n 1000000000"
                },
                {
                    "type": "mrkdwn",
                    "text": "*Current value*\n 40384"
                }
            ]
        },
        {
            "type": "section",
            "block_id": "section790",
            "text": {
                "type": "mrkdwn",
                "text": "<https://ui.amberflo.io/customers/54a43bba-915d-4512-a0bd-9d9debesase|Click here to view usage details>"
            }
        }
    ]
}'
```

And the rendered Slack message will look like this:

![](https://files.readme.io/21ac54f-slack-alert.jpg)


[title] Metering Overview
[path] Metering/

## Metering Usage

Amberflo Metering Service enables you to ask and answer the following question in real-time and with accuracy:
**What was used by whom, when, where, and how much?**

### Meter Event Data Structure

Amberflo's Meter Event data structure captures this information clearly and concisely:

| Aspect   | Details                 |
| -------- | ----------------------- |
| What     | Meter name and API name |
| Whom     | CustomerID              |
| When     | Timestamp               |
| How much | Meter value             |
| Where    | Dimensions              |

**Accurate, Real-time, Secure, and Reliable Usage Data**

**Meters are facts!** To build trust and efficiency, you need a metering service that provides an accurate, real-time, and secure single source of truth for usage and consumption data. Amberflo Cloud Metering Service is designed from the ground up to deliver just that.

**Standardized Usage Tracking Across All Systems**

Amberflo offers a **standardized and flexible API interface** for metering product usage across your technology stack.

**Benefits of standardization:**

- Accelerate development and deployment
- Enhance cross-team collaboration and data sharing
- Focus resources on core products and services
- Reduce metering and billing errors
- Improve customer satisfaction

**Meter Any Resource Type**

Our standardized and flexible meter definition API and data structure form the foundation for driving accuracy, visibility, and insights — both internally and for your customers.

**Decoupled from Pricing and Billing**

Amberflo Cloud Metering Service operates **independently of pricing and billing**. This ensures you have a highly scalable system of record for usage and consumption data, without dependencies on pricing models.

For those who need it, the system seamlessly integrates with Amberflo's Usage-Based Pricing and Billing Service — but you can also use the Cloud Metering Service alone and export data via API to third-party systems.

**Seamless Integration with Your Tech Stack**

No need for a rip-and-replace approach. Amberflo integrates smoothly with your existing technology stack.

We provide:

- **Language-specific SDKs**
- **Direct third-party system integrations**

**Automatic Data Transformation & Aggregation**

Let us handle the heavy lifting:

- Ingestion
- Transformation
- Aggregation
- Time series distribution
- On-demand invoice and bill generation

Simply send us the meters, we take care of the rest.

**Real-time Alerts & Notifications**

Set **usage and cost limits** with ease. Get real-time alerts and notifications so you’re always in control.

**Usage Explorer**

Access fast, on-demand, and actionable usage reports and invoices, all in real-time.

**API-First Approach**

Amberflo is an **API-first platform**. Every feature and artifact can be accessed and managed through our APIs and SDKs.


[title] Segment.io
[path] Metering/Ingestion Options/

You can ingest events into Amberflo using **Segment.io**.
&#x20;Integration details can be found [here.](https://segment.com/catalog/integrations/amberflo/)

**What You Can Do**

With Segment's **Amberflo Destination**, you can:

- **Ingest meter events** using:
  - track
  - page
  - screen
- **Create customers** in Amberflo using:
  - identify

This integration provides a seamless way to bring your customer and usage data into Amberflo using Segment’s event tracking.


![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/IBShgycDwccAHK0VkBJVX_image.png)

## Getting Started

1. From the
2. Search for
3. Choose which
4. Go to the
5. Enter the “API Key” in the

## Supported methods

## Page

Create a meter called **page** in Amberflo.

&#x20;The ***page*** is ingested as meter with value 1.

```javascript
analytics.page({
  userId: "some_user_id", 
  name: "Home",
  properties: {
   	"title": "Welcome | Initech",
    "keywords": "paper,comedy",
    "search": "schrute farms",
    "referrer": "https://google.com",
    "path": "/home",
    "browser": "Chrome",
    "url": "https://segment.com"
  }
})
```

**Mappings:**

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/8XWBnMbkoRdY0Od_Px9NR_image.png)

**Amberflo webhook endpoint:**

```curl
curl --request POST \
     --url https: //app.amberflo.io/ingest \
     --header 'Accept: application/json' \
     --header 'Content-Type: application/json' \
     --header 'x-api-key: YOUR_API_KEY' \
     --data '
{
    "uniqueId": "messageId",
    "customerId": "some_user_id",
    "meterApiName": "page",
    "meterValue": 1.0,
    "meterTimeInMillis": 1619445706909,
    "dimensions": {
        "name": "Home",
        "title": "Welcome _ Initech",
        "keywords": "paper,comedy",
        "search": "schrute farms",
        "referrer": "https:__google.com",
        "path": "_home",
        "browser": "Chrome",
        "url": "https:__segment.com"
    }
}
```

## Screen

Create a meter called **screen** in Amberflo. The *screen* event is ingested as meter with value 1.

```javascript
analytics.screen({
  userId: "some_user_id", 
  name: "Home",
  properties: {
   	"title": "Welcome | Initech"
  }
})
```

**Mappings:**

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/vV5UwNZq2sWt63PNfXiqe_image.png)

**Amberflo webhook endpoint:**

```curl
curl --request POST \
     --url https://app.amberflo.io/ingest \
     --header 'Accept: application/json' \
     --header 'Content-Type: application/json' \
     --header 'x-api-key: YOUR_API_KEY' \
     --data '
{
     "uniqueId": "messageId",
     "customerId": "segmentUserId",
     "meterApiName": "screen",
     "meterValue": 1.0,
     "meterTimeInMillis": 1619445706909,
     "dimensions": {
       "name": "Home",
       "title": "Welcome _ Initech"
     }
}
'
```

## Identify

Identify creates a customer in Amberflo or updates if customer already exists. Segment's **userId** is required for a customer in Amberflo. If **userId** is not set, then the customer will not be created in Amberflo and the *identify* event will be discarded.

```javascript
analytics.identify({
  userId: "some_user_id", 
  traits: {
    "name": "John Doe",
    "email": "john.doe@email.com",
    "plan": "premium",
    "logins": 5,
    "phone": "650-769-3240",
    "username": "marakasina",
    "website": "example.com",
    "avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/scottkclark/128.jpg"
  }
})
```

**Mappings:**

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/jRaPQeUNyeakdxHFuNwav_image.png)

**Amberflo webhook endpoint:**

```curl
curl --request PUT \
     --url https://app.amberflo.io/customers \
     --header 'Accept: application/json' \
     --header 'Content-Type: application/json' \
     --header 'x-api-key: YOUR_API_KEY' \
     --data '
{
     "customerId": "XXXX-ZZZZZ",
     "customerName": "myCustomerName",
     "customerEmail" "john.doe@email.com",
     "traits": {
        "plan": "premium",
        "logins": 5,
        "phone": "650-769-3240",
        "username": "marakasina",
        "website": "example.com",
        "avatar": "https:__s3.amazonaws.com_uifaces_faces_twitter_scottkclark_128.jpg"
    }
}
'
```

## Track

Create a meter in Amberflo for the track *event* in Segment. The Segment track event is ingested as a meter with value of properties.value, or 1 if the value is not set.

```javascript
analytics.track({
  userId: "some_user_id", 
  event: "ApiCalls",
  properties: {
    "value" : 2.0
   	"region": "us-west-2"
  }
})
```

**Mappings:**

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/3ljwptfYaolRKPA-EbgEH_image.png)

**Amberflo webhook endpoint:**

```curl
curl --request POST \
     --url https://app.amberflo.io/ingest \
     --header 'Accept: application/json' \
     --header 'Content-Type: application/json' \
     --header 'x-api-key: YOUR_API_KEY' \
     --data '
{
     "uniqueId": "messageId",
     "customerId": "some_user_id",
     "meterApiName": "ApiCalls",
     "meterValue": 2.0,
     "meterTimeInMillis": 1619445706909,
     "dimensions": {
      	"region": "us-west-2"
     }
}
'
```

## Links

[Segment Catalog -> Amberflo](https://app.segment.com/amberflo-lior/destinations/catalog/amberflo)
[Segment Destination Documentation -> Amberflo](https://segment.com/docs/connections/destinations/catalog/amberflo/)

[title] POSTMAN Collections
[path] Metering/Ingestion Options/

## Ingesting Meters using POSTMAN

One of the easiest ways to get started with the Amberflo API is by using our **Postman collection**.

With Postman, you can:

- **Create meters**
- **Ingest metering data**
- **Query ingested data**

All within **just a few minutes**—no coding required.

This is an ideal approach for:

- Testing the API
- Exploring available endpoints

Getting a quick hands-on understanding of the metering workflow


::embed[]{url="https://youtu.be/kqB06ybaZwE"}

Github repo [https://github.com/amberflo/api-postman](https://github.com/amberflo/api-postman)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/Z9xZebsdwPiuhPZuOltzE_image.png)

## **Prerequisites**

**Postman**

[https://www.postman.com/downloads/](https://www.postman.com/downloads/)

**node.js**

[https://docs.npmjs.com/downloading-and-installing-node-js-and-npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)

**newman - Postman runtime**

[https://www.npmjs.com/package/newman](https://www.npmjs.com/package/newman)

**Installation**
Download github repo [https://github.com/amberflo/api-postman](https://github.com/amberflo/api-postman)
(also on the github page)

## **Postman Instructions**

**Import collection** `/api-postman/api-tests.collection.json` into Postman

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/2OM-1tfsAYhtuK2cnYYd-_image.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/0aVEQ_BRHYJ_XJCQY8LGb_image.png)

Impor&#x74;**&#x20;environment file** `/api-postman/env-prod.json` into Postman

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/ETbdHZg8RAsv87C2AthLo_image.png)

Set Amberflo’s username, password, X\_API\_XEY, and s3\_bucket in prod environment file.

```json
{
			"key": "username",
			"value": "",
			"enabled": true
		},
		{
			"key": "password",
			"value": "",
			"enabled": true
		},
		{
			"key": "X_API_KEY",
			"value": "",
			"enabled": true
		},
		{
			"key": "s3_bucket",
			"value": "",
			"enabled": true
		}
```

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/fe5NuFZdF4tG6c6Wx6kIk_image.png)

Launch **Runner**, select collection and environment file and run.

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/29kU5m0kpIPfnqQ9RFUkl_image.png)


[title] Elastic Logstash
[path] Metering/Ingestion Options/

You can integrate metering data into Amberflo using the open-source tool **Logstash**.

### **How It Works**

- The integration utilizes the **Logstash S3 output plugin** to write metering records to your **Amberflo-provided S3 bucket**.
- You can use **Logstash's powerful parsing language** to extract metering data from your logs or repositories.

### **Data Sources**

Logstash supports a wide variety of **input plugins**, allowing you to extract data from:

- **Files**
- **Elasticsearch**
- **JDBC**
- **S3**
- **MongoDB**
- And many other systems

This flexibility makes it easy to integrate metering into your existing infrastructure. [https://www.elastic.co/guide/en/logstash/current/input-plugins.html](https://www.elastic.co/guide/en/logstash/current/input-plugins.html)

## Example - Logstash configuration

The following is a sample **Logstash configuration** that reads log lines from a file and writes the relevant **metering records&#x20;**&#x74;o Amberflo’s **S3 bucket**.

### **Input Setup**

Logstash will **tail files** located at: `/Users/demoaccount/input/*`

### Sample File Content

:::BlockQuote
log line 1
log line 2
myMeter 2 myCustomerId amberflo\_meter
log line 3
:::

### **How It Works**

- Logstash continuously monitors the input folder for **new files**.
- It **filters log lines** using the condition: contains amberflo\_meter.
- Matching lines are **split by space (" ")**.
- The split values are mapped as follows:
  - meterApiName → first item
  - meterValue → second item
  - customerId → third item

The records are then transformed using **mutate logic** and forwarded to **Amberflo's S3 bucket**.

```ruby
input { 
   file {  
      path => "/Users/demoaccount/input/*" 
    } 
} 

filter {
    if "amberflo_meter" in [message]{
    ruby { code => "event.set('time', ((event.get('@timestamp').to_f*1000).to_i).to_s)" } 
     mutate {
            split => { "message" => " " }
            add_field => { "meterApiName" => "%{[message][0]}" }
            add_field => { "meterValue" => "%{[message][1]}" }
            add_field => { "customerId" => "%{[message][2]}" }
            remove_field => ["host","message","@version","@timestamp"]
        } 
    }else{
    drop {}
    }

}

output {
   s3{
     access_key_id => "XXXXXX"             
     secret_access_key => "YYYYYYYYYYYYYY" 
     region => "us-west-2"                    
     bucket => "demo-amberflo"               
     size_file => 2048                        
     time_file => 5                          
     codec => "json"                        
     canned_acl => "bucket-owner-full-control"                
   }

}
```

The resulting file (meter record) will be:

```json
{
	"meterApiName": "myMeter",
	"meterValue": "2",
	"customerId": "myCustomerId",
	"time": "1621619742810",
	"path": "/Users/demoaccount/input/sample.txt"
}
```

## JDBC source

You can use the **Logstash JDBC input plugin** to extract metering data directly from your database or repository.

- This is useful when your metering data is stored in structured tables.

The plugin allows you to run SQL queries to retrieve only the relevant data for metering.&#x20;
For more details, refer to the official documentation:
[https://www.elastic.co/guide/en/logstash/current/plugins-inputs-jdbc.html](https://www.elastic.co/guide/en/logstash/current/plugins-inputs-jdbc.html)

```ruby
input {
  jdbc {
    statement => "SELECT id, mycolumn1, mycolumn2 FROM my_table WHERE id > :sql_last_value"
    use_column_value => true
    tracking_column => "id"
    jdbc_driver_library => "mysql-connector-java-5.1.36-bin.jar"
    jdbc_driver_class => "com.mysql.jdbc.Driver"
    jdbc_connection_string => "jdbc:mysql://localhost:3306/mydb"
    jdbc_user => "mysql"
    schedule => "* * * * *"
    # ... other configuration bits
  }
}
```

## Elasticsearch input

If your metering data is stored in **Elasticsearch logs**, you can use the **Elasticsearch input plugin** to extract it into Logstash.

Here’s a basic example configuration:

```ruby
input {
elasticsearch {
        hosts => "search-myes-cluster.us-west-2.es.amazonaws.com:443"
        index => "cwl--aws-lambda-meter-definition-api-lambda-2021.07"
        query => '{ "query": {"bool": {"filter": [{"range": {"@timestamp": {"from": "now-1d/d", "to": "now/d", "include_lower": true, "include_upper": true, "format": "epoch_millis", "boost": 1 } } }, {"query_string": {"query": "*amberflo_meter*", "default_field": "@message", "fields": [], "type": "best_fields", "default_operator": "or", "max_determinized_states": 10000, "enable_position_increments": true, "fuzziness": "AUTO", "fuzzy_prefix_length": 0, "fuzzy_max_expansions": 50, "phrase_slop": 0, "escape": false, "auto_generate_synonyms_phrase_query": true, "fuzzy_transpositions": true, "boost": 1 } } ], "adjust_pure_negative": true, "boost": 1 } }, "aggregations": {} }'
        size => 500
        scroll => "5m"
        docinfo => true
        ssl => true
        user => "myuser"
        password => "mypwd"
        #schedule => "*/1 * * * *"
      }

}
```


[title] Pricing Plans
[path] Billing & Monetization/


[title] Payments - Stripe Integration
[path] Billing & Monetization/Invoicing & Payments/

## **Stripe Integration Setup Guide**

Amberflo offers a built-in integration with Stripe to support automated payment processing.

**When an invoice is generated in Amberflo, the total amount is sent to Stripe, which handles the payment collection.** Amberflo continuously monitors the payment lifecycle and updates the invoice status in real time based on changes in Stripe.

### **Configuration Steps**

To configure Stripe integration:

1. In the Amberflo UI, use the main navigation menu on the left to go to **Settings**.
2. Select **Payments**.
3. Choose whether to connect to Stripe in **test mode** or **production mode**.



![](https://files.readme.io/ff89769-stripe-1.png)

### **Associating Customers Between Amberflo and Stripe**

You have two options for associating customers:

- **Use the Amberflo SDK or API to create a customer.** Set the autoCreateCustomerInStripe flag to true when calling the Create Customer API. In the UI, you can also toggle the option to automatically create customers in Stripe (see image below).
- **If you already have customers in Stripe, you can link them by updating the Amberflo customer object.** Set the StripeId trait on the Amberflo customer to match the customer-id from Stripe.

**The StripeId trait&#x20;**&#x69;s used to associate the Amberflo customer with its corresponding customer in Stripe.

```json
{
	"customerName": "Monsters, Inc.",
	"enabled": true,
	"updateTime": 1648767962572,
	"traits": {
		"stripeId": "cus_LCEP3LBELibfSM",
    "paymentProviderName": "STRIPE"
	},
	"createTime": 1631836633987,
	"customerEmail": "",
	"description": "",
	"id": "54a43bba-915d-4512-a0bd-9d9debe5555",
	"customerId": "54a43bba-915d-4512-a0bd-9d9debe5555"
}
```

If you choose to automatically create the Stripe customer, you can either use the Amberflo SDK or the API [Create a customer](docId\:IWTZyA4jWLlxbgp3qaZSl) with autoCreateCustomerInStripe set to true.
In the UI, you can toggle the option to create in Stripe (image below):



![](https://files.readme.io/4c392e5-stripe-3.png)

If you already have the Customer in Stripe and you would like to set up the Amberflo customer with the payment information, **copy the created Customer ID (cus\_XXXXXX) from Stripe into the Amberflo customer tab to update the payment information.**



![](https://files.readme.io/d28d87a-Screen_Shot_2022-02-25_at_6.59.37_AM.png)



![](https://files.readme.io/e846d7d-stripe-2.png)

Set up the default payment method in Stripe. For testing, you can use dummy credit cards.

📘 ***Further Reading***

- ***For testing documentation on Stripe,&#x20;***[click here.](https://stripe.com/docs/testing)


[title] Quick Start
[path] /

Get up and running with **Amberflo** by focusing on a single outcome first.

Amberflo is a platform for **usage metering, cost visibility, and customer billing**. It connects these into a single system so you can track usage, understand cost, and generate accurate billing from the same data.

The fastest way to understand how the platform works is to pick one path and follow it end-to-end.



## **Why This Approach**

Amberflo is a full platform with a wide range of capabilities. If you try to configure everything at once, you will slow yourself down.

Instead, this quick start gives you two focused paths:

- One for **customer billing**
- One for **internal cost visibility**

Both paths use the same underlying concepts. The goal is to get one working system in place quickly, then expand from there.



## **Choose Your Starting Point**

### **1. Customer Billing**

This path is for teams that want to charge customers based on usage.

You will go from raw usage data to generating invoices.

**What you will do:**

1. **Meter usage:&#x20;**&#x43;apture usage events from your application, APIs, or services.
2. **Define pricing:&#x20;**&#x43;onfigure how that usage is priced (per unit, tiered, volume, or hybrid models).
3. **Generate billing:&#x20;**&#x50;roduce invoices and usage reports for your customers.

**Outcome:**

You have a working usage-based billing system aligned with how your product operates.



### **2. Cost Visibility**

This path is for teams that want to understand how usage translates into cost.

You will go from raw usage data to structured cost insights.

**What you will do:**

1. **Meter usage:&#x20;**&#x43;apture usage across services, environments, or workloads.
2. **Apply cost rates:&#x20;**&#x4D;ap usage to cost using your underlying pricing sources.
3. **Analyze and attribute:&#x20;**&#x42;reak down costs by team, product, workload, or customer.

**Outcome:**

You understand where your costs are coming from and how they change over time.



## **What These Paths Have in Common**

Both paths rely on the same foundation:

- **Metering** is the source of truth for all usage
- **Rating** converts usage into cost or revenue
- **Attribution** determines how usage is grouped and analyzed



The difference is what you do with that data:

- Billing focuses on **charging customers**
- Cost visibility focuses on **understanding spend**

Most teams eventually use both.

## **What to Do Next**

Pick one path and follow its step-by-step guide:

- **Customer Billing Quick Start**
  - Meter → Pricing → Billing
- **Cost Visibility Quick Start**
  - Meter → Cost → Analysis

Once you complete one path, extending to the other is straightforward since both are built on the same usage data.

## **Important Context**

Amberflo is not a single-purpose tool. It supports:

- Multiple pricing models
- Flexible cost attribution
- Real-time usage and billing workflows
- Multi-tenant and multi-product systems

The goal of this quick start is simple:

**Get one real use case working end-to-end.**

After that, you can expand into the rest of the platform with a clear foundation in place.

[title] Usage Reporting (cloned with children)
[path] Billing & Monetization/Customer Billing Portal/

## Real-time Dashboard of Aggregated Usage

The **Usage Analytics** tool provides an interactive, out-of-the-box dashboard that shows detailed, real-time usage data—segmented by:

- **Customer**
- **Metered product features**
- Any defined **custom dimensions**

### **Actionable Insights at Your Fingertips**

Amberflo's dashboard helps you answer key questions like:

- **Who** is using your product?
- **What** resources are being used?
- **How much** is being consumed?
- **When** is the usage occurring?
- **What** patterns are emerging in usage over time?

### **Powerful Visualization Features**

With **Usage Analytics**, you can:

- Visualize usage data over **any time interval**
- See **meter totals** and **percent changes** out of the box
- Gain the most **comprehensive usage profile** available

### **Drive Better Decisions with Usage Insights**

Use the dashboard to:

- Understand **customer behavior and feature adoption**
- Identify **trending features** (up or down)
- Track overall usage trends at both the **feature** and **customer** levels
- Take **proactive action** and guide **product development** with data-backed insights

### Usage by Meter (Product Feature or Custom Event)

- View usage for any **individual meter** or browse your entire **meter library**.
- Select **custom date ranges** and **aggregation intervals** using the controls in the top right corner.

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/wpJ8FEdprKm3P4J_wqY8p_image.png)

Click on a specific meter to display the **top five customers** with the highest usage for that meter.

![](https://files.readme.io/804589c-Screen_Shot_2022-10-07_at_10.15.12_AM.png)

## Usage by Customer

- The **Customer view** allows you to select a specific customer and see their **usage profile across all meters**.
- To analyze usage for a particular meter, simply select it from the **meter table** to view its data in the graph.

![](https://files.readme.io/011890c-Screen_Shot_2022-10-20_at_5.55.12_PM.png)

## Usage by Event

- The **Event view** provides a **chronological list** of each meter event ingested into the system.
- This acts as a **full audit trail**, making it easy to investigate any issues or resolve **billing disputes**.

![](https://files.readme.io/6d69a01-Screen_Shot_2022-10-20_at_5.55.57_PM.png)

When you drill down into a specific meter event, you’ll see:

- The **aggregation rule** applied
- The **associated customer**
- Any related **pricing plan** and **invoice**
  - If linked to an invoice, a **direct link** to the invoice will be available

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/Vs6riDKXA0qTTgn7my70k_image.png)


[title] SDK Sample Code
[path] Metering/Meter Integration Options/

This sample demonstrates how to **ingest meter events** using the Amberflo SDK in a **Spring Boot** application.

**Steps:**

1. **Set Up the Amberflo Environment**
   Prepare your application environment with the required Amberflo client settings (e.g., API keys, region, etc.).
2. **Configure Service Discovery**
   Set up your application to discover and communicate with Amberflo’s ingestion service.
3. **Create a Helper Method**
   Implement a helper function to map your internal objects to the Amberflo ingestion payload format.

Maven dependencies (check for latest versions):

```xml
<dependencies>
  <dependency>
    <groupId>io.amberflo</groupId>
    <artifactId>metering-java-client</artifactId>
    <version>1.3.6</version>
  </dependency>
  <dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<version>1.18.20</version>
	</dependency>
</dependencies>
```

### **Environment Setup Details**

- Configure the necessary environment variables for the Amberflo SDK client.
- Add the required configuration entries to your application properties or environment.
- Ensure the service is **discoverable** by the Amberflo client at runtime.

:::CodeblockTabs
application.properties

```none
amberflo.apiKey=your-api-key-here
amberflo.region=US_West
amberflo.domain=Prod
amberflo.ingestion.frequency.seconds=1
amberflo.ingestion.batch.size=10
amberflo.isDebug=true
```
:::

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/9crRmRcbp7yAA4GQ43lt9_image.png)

```java
import com.amberflo.metering.ingest.meter_message.Domain;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import com.amberflo.metering.ingest.meter_message.Region;

@Getter
@Component
public class AmberfloEnvironment {

    @Value("${amberflo.apiKey}")
    private String apiKey;

    @Value("${amberflo.region}")
    private Region region;

    @Value("${amberflo.domain}")
    private Domain domain;

    @Value("${amberflo.ingestion.frequency.seconds}")
    private Double ingestionFrequencyInSeconds;

    @Value("${amberflo.ingestion.batch.size}")
    private Integer ingestionBatchSize;
}
```

## Setup Amberflo client bean for service discoverability

Amberflo provides a **singleton instance** of the client to ensure efficient ingestion of meter events.

To enable **dependency injection** and make the client discoverable as a service within your Spring Boot application:

- Create a configuration class
- Define a method that returns the Amberflo client as a **Spring bean**

This allows other components in your application to **inject the Amberflo client** where needed, promoting reusability and centralized configuration.

```java
import com.amberflo.metering.ingest.MeteringContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AmberfloClientConfig {

    @Autowired
    private AmberfloEnvironment amberfloEnvironment;

    @Bean
    public MeteringContext meteringContext() {
        MeteringContext meteringContext = null;
        try{
            meteringContext = MeteringContext.createOrReplaceContext(
                    this.amberfloEnvironment.getApiKey(),
              			//TODO: enter your service name here
              			//The service name will be added for all of your meters as an extra dimension.
                    "service-name",
                    this.amberfloEnvironment.getDomain(),
                    this.amberfloEnvironment.getRegion(),
                    this.amberfloEnvironment.getIngestionFrequencyInSeconds(),
                    this.amberfloEnvironment.getIngestionBatchSize());
            if(meteringContext.meteringInstance() != null) {
                LOGGER.info("Amberflo client initialized with region={}, domain={}, frequency={}, batchSize={}",
                        this.amberfloEnvironment.getRegion(),
                        this.amberfloEnvironment.getDomain(),
                        this.amberfloEnvironment.getIngestionFrequencyInSeconds(),
                        this.amberfloEnvironment.getIngestionBatchSize());
            }
        }
        catch(Exception ex){
            LOGGER.error("Amberflo client initialization failed: {}", ex.getMessage());
            ex.printStackTrace();
        }

        return meteringContext;
    }
}
```

## Entity to Amberflo payload mapper

In this approach, your internal entity is transformed into an **Amberflo ingestion payload** using a dedicated helper class.

For example:

- You have an entity class named `Event`
- This entity is mapped to the required SDK format in a helper class called `AmberfloMapper`

The transformation logic is encapsulated in a method like:

:::BlockQuote
AmberfloMapper.ingestMeterForEvent()
:::

This method can now be called **from anywhere in your code**, allowing you to easily meter events across your application in a centralized and reusable way.

Sample entity class:

```java
import java.io.Serializable;
import javax.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "event")
public class Event implements Serializable {
    private static final long serialVersionUID = 1L;
    
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private long id;

    @Column(name = "event_source_type")
    private String eventSourceType;

    @Column(name = "event_type")
    private String eventType;

    @Column(name = "customer_id")
    private String customerId;
}
```

Sample mapper helper:

```java
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.HashMap;
import java.util.Map;

import static com.amberflo.metering.ingest.MeteringContext.metering;

public class AmberfloMapper {
    private static final Logger LOGGER = LogManager.getLogger();

    public static void ingestMeterForEvent(final Event event, final String meterName){
        try {
            final String customerId = event.getCustomerId();
            final double meterValue = 1.0;
            final LocalDateTime eventTime = LocalDateTime.now(ZoneOffset.UTC);
            final Map<String, String> dimensions = new HashMap();
            dimensions.put("event_source_type", event.getEventSourceType());
            dimensions.put("event_type", event.getEventType());
            metering().meter(customerId, meterName, meterValue, eventTime, dimensions);
            LOGGER.info("Amberflo ingestion: event id {}, customerId {}, meter {}, event source type {}, event type {}",
                    event.getId(), customerId, meterName, dimensions.get("event_source_type"), dimensions.get("event_type"));
        } catch (Exception ex) {
            LOGGER.error("Amberflo ingestion failed: {}", ex.getMessage());
            ex.printStackTrace();
        }
    }
}
```


[title] Usage Alerting
[path] Metering/


[title] Databases and JDBC
[path] Metering/Meter Integration Options/

## Ingest Meters from a JDBC Enabled Source

Using metering data from an existing **JDBC-compatible source** is a common practice.
&#x20;Many systems support JDBC, including:

- **MongoDB**
- **Elasticsearch**
- **MySQL**
- **PostgreSQL**
- And many more

### **Options for Ingesting JDBC Data into Amberflo**

You can choose one of the following methods to ingest meter records from your JDBC source:

**1. Use Amberflo SDKs**

Write custom code using one of our **official SDKs** to extract data from your JDBC source and ingest it into Amberflo.

- SDKs are available in multiple languages (e.g., **Python**, **JavaScript**)
- Code samples are available to get you started quickly

[https://github.com/amberflo/metering-python-sample](https://github.com/amberflo/metering-python-sample)

**2. Use Logstash with the JDBC Plugin**

You can use **Logstash** and its **JDBC input plugin** to extract data from your JDBC source and upload it to **Amberflo's S3 bucket**.

**3. Use an ETL Tool**

Any **ETL tool&#x20;**&#x63;apable of pushing data into **S3** can be used.
Once data lands in your Amberflo S3 bucket, **Amberflo will automatically ingest it**.

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/qS6KlzOXZ5hYJz7OUD8p__image.png)


[title] Usage Deep Dive
[path] Cost Tracking/AI Usage/

## Why It Matters

High AI spend rarely comes from a single obvious change. It usually builds up through specific behaviors: a model being overused, a retry loop generating excess calls, a misconfigured integration burning tokens, or a team experimenting without visibility.

Usage Details exist to help you identify exactly what is driving usage and whether it is intentional. This is where you move from noticing that usage increased to understanding precisely why it increased.

Without this level of detail, cost discussions become reactive and speculative. With it, they become factual and actionable.

## How to Use It - Digging Deeper

Use Usage Details when you already know that usage has changed and you need to determine what caused it.

### Investigate spikes and anomalies

Start by narrowing the time range to when usage changed.

Adjust the aggregation interval to:

- Zoom in on short-lived spikes
- Separate transient bursts from sustained growth
- Correlate changes with deployments, launches, or experiments

Short intervals surface bursts. Longer intervals confirm trends.

### Compare teams, applications, or projects

- Switch from a combined view to a per-Workload view.

You can:

- Compare a small set of Workloads directly
- Exclude unrelated units to reduce noise
- Identify which team, application, or project is responsible

This quickly establishes ownership and scope.

### Identify model-driven usage

Filter usage by model to understand:

- Which models account for the majority of usage
- Whether higher-cost models are being used more than expected
- If usage shifted from one model to another

Model-level visibility is often where inefficiencies first appear.

### Examine usage patterns, not just totals

Totals hide behavior.

Use different value calculations to reveal patterns:

- Maximum values highlight peaks and bursts
- Average values show steady-state behavior
- Minimum values help confirm baseline usage

These perspectives help distinguish normal growth from problematic usage.

### Focus on top consumers

Review the top consumers for the selected metric and time range to understand:

- Which Workloads are responsible for most usage
- Whether usage is concentrated or distributed
- Where follow-up conversations should start

Clear ownership speeds resolution.

### Save and revisit investigations

Once you have a useful configuration, save it.

Saved views allow you to:

- Revisit the same investigation over time
- Monitor known problem areas
- Share a consistent view with other stakeholders

This turns ad hoc analysis into a repeatable process.

## When to Use This Page

Use Usage Details when:

- A budget threshold or alert is triggered
- You see unexpected spikes in usage
- Costs are increasing faster than planned
- You need concrete evidence to explain what changed

This is where AI usage becomes understandable, not just measurable.

[title] Customer Portal
[path] Billing & Monetization/


[title] Untitled
[path] /


[title] AI Monetization
[path] /


[title] Backfill Meters and Customers
[path] Metering/

## Backfill Meters

You can backfill as many meter events as you like.
Each event must include a **customerID.**

## Backfill Customers

Amberflo supports customer backfilling at any time, allowing you to ingest as many customer records as needed, independently of meter data.&#x20;
The customer backfill pipeline runs **separately** from meter ingestion.
You can run the customer backfill ingestion pipeline before, after or in parallel to meter backfill.

To ensure meter events are correctly associated with the ingested customers. The **customerId** field in your **meter events** must match the **customerId** of a customer ingested via the backfill pipeline.

The customer object contains the `customerId` (required) and additional attributes (name, email, currency, and custom properties).

## Meter data older than 24 hours can take up to 24 hours to process

When working with **historical meter data** (i.e. events older than 24 hours), you may notice a delay in visibility.
Data in this category can take **up to 24 hours** to appear in the Usage and Billing dashboards.

**Realtime processing**
Meter data that is less than 24 hours old is handled by Amberflo’s real-time pipeline.
This means the data is processed, transformed, aggregated, time-sliced, and grouped by dimension **almost instantly**.
As a result, you’ll see real-time updates reflected in your running invoices—including line item breakdowns—immediately after ingestion.

**Up to 24 hours delayed processing**
In contrast, meter events that are **more than 24 hours old** are processed periodically, outside of the real-time pipeline.
While this introduces a temporary delay, the data will still be processed and reflected in your dashboards within 24 hours. Amberflo is continuously working to reduce this lag window even further.


[title] API Gateway - Kong
[path] Metering/Meter Integration Options/

You can use Amberflo's API Metering plugin for [Kong](https://docs.konghq.com/gateway/latest/) to:

- **Meter API requests** handled by your Kong instance
- **Monetize your APIs** based on usage

For implementation details, see the [GitHub repository](https://github.com/amberflo/kong-plugin-amberflo)

## &#x20;How it Works

1. **Request Interception**
   &#x20;The plugin intercepts incoming API requests handled by Kong.
2. **Customer Identification**
   &#x20;It determines which customer is making the request by inspecting request headers.
   &#x20;You can configure Kong to inject the customerId header **before** this plugin runs.
   - Example: If you use [Kong's Key Authentication plugin](https://docs.konghq.com/hub/kong-inc/key-auth/), this is done automatically.
3. **Meter Event Generation**
   &#x20;The plugin generates a meter event for each request.
4. **Efficient Metering**
   &#x20;To avoid performance overhead:
   - Meter records are **batched**
   - Records are sent to Amberflo **asynchronously**

This setup ensures high-throughput usage tracking with **minimal impact on gateway performance**.

## &#x20;Installation

1. **Compile the Plugin Binary**
   Compile the plugin and make the resulting binary available to your Kong instance.
   - **Important:** Ensure your compilation environment matches your Kong runtime environment. A mismatch may result in a binary that won't run correctly.
2. **Update Kong Configuration**&#x20;
   Add the plugin to your Kong configuration.
   &#x20;For example, if you place the plugin server binary at: `/opt/amberflo/metering` then the relevant sections of your kong.conf file should resemble [this one.](https://github.com/amberflo/kong-plugin-amberflo/blob/main/kong.conf) For more detailed guidance on configuring Kong, refer to their official documentation [here](https://docs.konghq.com/gateway/latest/plugin-development/pluginserver/go/#example-configuration) and [here](https://docs.konghq.com/gateway/latest/reference/configuration/).
3. **Enable the plugin**
   You can enable the plugin in one of two ways:
   - Add it to your kong.yaml file
   - Or enable it via a call to the **Kong Admin API**

## Configuration

Please find a sample configuration file [here](https://github.com/amberflo/kong-plugin-amberflo/blob/main/metering.json).

Here's a breakdown of the fields and their respective meanings.

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/JPotmyY8MZvKiZtfIfdW0_image.png)


[title] Cost Guards
[path] Cost Tracking/

Cost Guards are alerts that help you monitor **usage and spend** and take action before things get out of control.

They are typically used to track activity at the **workload level**, allowing you to detect:

- Unexpected spikes in cost
- Unusual drops in usage (potential outages)
- Sustained increases over time

## **What is a Cost Guard?**

A Cost Guard is a rule that watches usage or cost over a rolling time window and triggers an alert when a condition is met.

Examples:

- Spend **greater than $1,000 in the last 7 days**
- Usage **greater than 10,000 requests in the last hour**
- Spend **less than $10 in the last day** (possible outage signal)

Cost Guards are tied to **workloads**, so you can monitor specific parts of your system independently.

## **Cost Guards Page Overview**

Navigate to **Cost → Cost Guards**.

### **What You’ll See**

Each Cost Guard in the list includes:

- **Name**
- **Alert Type** (Cost or Usage)
- **Rule** (condition and threshold)
- **Workload**
- **Status** (On or Off)

You can toggle a Cost Guard off if you want to temporarily pause alerts without deleting it.

## **Creating a Cost Guard**

### **Navigate**

- Go to **Cost → Cost Guards**
- Click **Create Cost Guard** (top right)

### **Step 1: Define the Alert**

- **Alert Type**
  Choose:
  - **Cost** (monitors spend), or
  - **Usage** (monitors raw usage volume)
- **Name**
  Use a clear, descriptive name
  Example: Image Pipeline Spend Spike
- **Description (optional)**
  Add context if needed

### **Step 2: Select a Workload**

- Choose the workload you want to monitor

If the workload does not exist:

- Create one using the **Workloads** page

Cost Guards operate at the workload level, so this selection is required.

### **Step 3: Define the Rule**

**Condition**

Choose whether the alert triggers when the value is:

- **Greater than** (most common, for detecting spikes)
- **Less than** (useful for detecting drops or outages)

**Threshold Value**

- For cost: enter a **currency amount**
- For usage: enter a **unit value**

**Time Window**

Select a rolling window:

- Last hour
- Last day
- Last week (7 days)
- Last month (30 days)

These are always rolling periods, not fixed calendar intervals.

### **Step 4: Configure Notification Frequency**

Choose how often alerts are sent:

- **Once**
  Trigger only the first time the condition is met
- **Every time**
  Trigger on every evaluation that meets the condition
- **At most every X hours**
  Throttle notifications to a maximum frequency
  Example: no more than once every 24 hours

This helps reduce alert noise while still keeping visibility.

### **Step 5: Choose Notification Channels**

Define how alerts are delivered.

**Email**

- Send notifications via email
- Can be used for individuals or workload owners

**Webhooks**

- Send alerts to an external system
- Useful for automation, such as:
  - Triggering workflows
  - Updating internal systems
  - Integrating with monitoring or incident tools

### **Save the Cost Guard**

Click **Save** to activate the Cost Guard.

It will immediately begin monitoring based on your configuration.

## **When to Use Cost Guards**

Cost Guards are most effective when applied to:

- High-cost workloads
- Critical production systems
- Newly launched features with uncertain usage patterns
- Systems where unexpected drops indicate issues

They are often used by engineering and operations teams to maintain control over system behavior.

## **Summary**

Cost Guards provide a simple way to monitor and control usage and spend.

They:

- Track cost or usage over time
- Trigger alerts based on thresholds
- Operate at the workload level
- Support flexible notification options

Used correctly, they help you detect problems early and maintain control over your system’s behavior.

[title] AI Governance
[path] /


[title] Discounts
[path] Billing & Monetization/Credits & Drawdowns/

## Flexible Discounting for All Scenarios

Discounts allow you to reduce charges on a customer’s bill. They can be applied as a percentage or a fixed amount and can be configured to last for a specific duration.

This page covers how discounts work and how to assign them to customers.

## **How Discounts Work**

A discount modifies the final billed amount for a customer.

There are two types of discounts:

### **Percentage Discount**

- Reduces the invoice by a percentage
- Applies across all eligible charges
- Can be applied for:
  - A single billing period
  - Multiple billing periods
  - Indefinitely (never expires)

**Example:**

- 20% discount on a $100 invoice → $80 total

### **Fixed Amount Discount**

- Reduces the invoice by a fixed dollar amount
- Always applies to **a single billing period only**

**Example:**

- $50 discount on a $200 invoice → $150 total

This is typically used for:

- One-time credits
- Promotions or goodwill adjustments

## **Key Differences**

| **Feature**      | **Percentage Discount**             | **Fixed Amount Discount**  |
| ---------------- | ----------------------------------- | -------------------------- |
| Applies as       | % of invoice                        | Flat dollar amount         |
| Duration options | Flexible (1+ periods or indefinite) | Single billing period only |
| Common use cases | Ongoing promotions, contracts       | One-time credits           |

## **Creating a Discount**

### **Navigate**

- Go to **Billing → Discounts**
- Click **Create Discount**

### **Step 1: Define Basic Information**

- **Name**
  Use a descriptive name
  Example: 20% Launch Promo, $100 Credit
- **Description (optional)**
  Add context for internal tracking

### **Step 2: Select Discount Type**

Choose one:

**Percentage**

- Enter a percentage value
- Example: 10 for 10%

**Fixed Amount**

- Enter a dollar value
- Example: 50 for $50 off

Note: Fixed amount discounts are limited to one billing period.

### **Step 3: Configure Expiration**

**For Percentage Discounts**

You can choose:

- **Never Expires**
  Discount applies indefinitely
- **Custom Duration**
  Set the number of billing periods
  Example: 3 billing periods

**For Fixed Amount Discounts**

- Automatically limited to **1 billing period**
- No additional duration configuration required

### **Save the Discount**

Click **Save** to create the discount.

The discount is now available to be assigned to customers.

## **Assigning Discounts to Customers**

Discounts are applied at the **customer level**.

### **Navigate**

- Go to **Billing → Customers**
- Select a customer

### **Apply a Discount**

- Locate the **Discounts** section on the customer page
- Click **Add Discount**

Configure:

- **Discount**
  Select from the list of available discounts
- **Start Timing**
  Choose when the discount should begin:
  - Immediately
  - At the start of the next billing period

Click **Save**.

## **How Discounts Are Applied**

- Discounts are applied during invoice calculation
- They reduce the total amount due based on their configuration
- If a discount has a duration, it automatically expires after the specified number of billing periods

## **Important Notes**

- Fixed amount discounts are **always one-time**
- Percentage discounts are more flexible and can be ongoing
- Discounts apply at the **customer level**, not per meter or per rate
- Multiple discounts may be supported depending on configuration, but should be used carefully to avoid unintended stacking effects

## **Summary**

Discounts provide a flexible way to adjust customer billing.

They allow you to:

- Offer promotions
- Apply credits
- Adjust pricing for specific customers

Use percentage discounts for ongoing adjustments and fixed amount discounts for one-time changes.

[title] Get Session Token
[path] Billing & Monetization/Customer Billing Portal/

To interact with Amberflo via the API, UI Kit, or AmberfloSDK, you first need to create a **session token**.

The recommended approach is to retrieve this token from an endpoint on your server. On the client side—especially in single-page applications built with frameworks like React—you can then use the browser’s fetch function to request the token.

This setup ensures security and smooth integration between your frontend and Amberflo services.

For more information check out the [API Reference](docId\:p_45dCxEKUOsbG4vBhz06)&#x20;

This example shows how to create the server endpoint that serves the client secret:

```javascript
const express = require('express');
const app = express();
const request = require('request');

app.get('/amberflo-session', async (req, res) => {
  request({
    method: 'POST',
    uri: 'https://app.amberflo.io/session',
    body: {
      "customerId": "{{DESIRED_CUSTOMER_ID}},
      "expirationEpochMilliSeconds": {{EXPIRATION_DATE_IN_MILLISECONDS}}
    },
    headers: {
      "X-API-KEY": "{{YOUR_API_KEY}}"
    }
  }, function (error, response, body) {
        if (!error && response.statusCode == 200) {
            res.json({ sessionToken: response.sessionToken })
        }
    }
  );
})
```

***📘 The above example is in Node.js***

You can get `X-API-KEY `from Amberflo site inside **Settings -> Account -> API keys**.

![](https://files.readme.io/87336b5-d631957-api-keys.png)

You can also get the `customerId` from Amberflo site inside **Customers → Click on a customer → Copy Customer ID**.

![](https://files.readme.io/50e0210-ec76595-customer-id.png)

This example demonstrate how to fetch the session token with JavaScript in the client side:

```javascript
const response = fetch('/amberflo-session').then(function(response) {
  return response.json();
}).then(function(responseJSON) {
  const sessionToken = responseJson.sessionToken
})
```


[title] Alert Types
[path] Metering/Usage Alerting /

The following alert (aka. notification) types can be created using the [Alerts UI Dashboard](https://ui.amberflo.io/alerts) or [Alerts API](docId\:TXaVag-nUfaH_djTdJXlS)&#x20;

## Usage

Usage alerts are **scheduled** checks that trigger based on meter consumption.&#x20;

1. Specify using a **cron&#x20;**&#x65;xpression ([some cron examples](https://crontab.guru/examples.html)).
2. Set the **range&#x20;**(`hour`, `day`, `week`, `month`).
3. Select the meter by setting the **meterId.&#x20;**&#x49;t can found by calling the \[Meter Definition API].([Get a list of meters](docId\:i66o22RFPYjUNKud2fZ6i)) or [UI Dashboard](https://ui.amberflo.io/meters)
4. Select the **thresholdCondition&#x20;**&#x61;nd set the **thresholdValue**

At the defined schedule, Amberflo calls the [Usage API](docId\:ARr7X-fDEquk7pUKn3VU_)  to evaluate the condition against the current usage. If the condition is met for a customer, the alert is triggered.

```curl
curl --location --request POST 'https://app.amberflo.io/notification' \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: xyz' \
--data-raw '{
    "name": "usage-alert",
    "meterId" : "4d1ca680-4974-11ec-ad9b-e1ce3f8b2ad3",
    "cron": "0 0 * * 0",
    "webhookUrl": "https://hooks.slack.com/services/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3",
    "email":"batman@amberflo.io",
    "thresholdCondition": "greater-than",
    "thresholdValue": "1000",
    "range":"day",
    "customerFilterMode": "per-customer",
    "enabled": false
}'
```


Prepaid Credits
---------------

This is a real-time alert that applies only to prepaid customers. It triggers when a customer's prepaid credits fall below a defined threshold, prompting them to top up.

- Set the `thresholdValue` for the prepaid balance.
- The `thresholdCondition` is always *less-than*.
- The evaluation is done for the **current billing cycle**.

If a customer's prepaid balance on the active invoice drops below the threshold, the alert will be fired.

```curl
curl --location --request POST 'https://app.amberflo.io/notification' \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: xyz' \
--data-raw '{
    "name": "alert-for-prepaid",
    "notificationType": "prepaid",
    "email": ["batman@gmail.com", "batman@wayne.io"],
    "webhookUrl": "https://hooks.slack.com/services/T01HZB7A0M6/B01P3J0LE0H/OBRoTt8TmrJ2Zbv3bQAqH88",
    "thresholdValue": "1000",
    "customerFilterMode": "per-customer",
    "enabled": true
}'
```

## Invoice

Invoice alerts are **real-time** and apply to customers with billing enabled through an assigned pricing plan.

- Set the `thresholdValue` for the total invoice amount.
- The `thresholdCondition` is always *greater-than*.
- The evaluation is based on the **current billing cycle** and applies to the active invoice.

If the total on a customer’s current invoice exceeds the set threshold, an alert is triggered.

```curl
curl --location --request POST 'https://app.amberflo.io/notification' \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: xyz' \
--data-raw '{
    "name": "invoice-tracker",
    "notificationType": "invoice",
    "email": ["batman@wayne.io"],
    "thresholdValue": "500",
    "customerFilterMode": "per-customer",
    "enabled": true
}'
```

## Product Item Usage Limit

This **real-time** alert is relevant only to invoices that include a breakdown by product items. Since product items are mapped to specific meters, the alert monitors meter unit consumption (or quantity) for a product item as displayed on the invoice.

![](https://files.readme.io/730d3a1-invoice_product_item_.jpg)

1. Select the meter by setting the `meterId`**.&#x20;**&#x49;t can found by calling the [List Meters API](docId\:i66o22RFPYjUNKud2fZ6i)  or [UI Dashboard](https://ui.amberflo.io/meters)
2. Select the pricing plan by setting the `productPlanId`. This can be obtained from the [Account Pricing API ](docId:-r4cpBaBC5wUBoiwpm0fb) or [UI Dashboard](https://ui.amberflo.io/billing-cloud/pricing-plans)
3. Set the meter units limit `thresholdValue`**.**
4. The `thresholdCondition` is always **greater-than**
5. The **range&#x20;**&#x6F;r time bucket is always the current billing cycle.

If the *meter units* or quantity on the current invoice for the selected meter and pricing plan exceeds the `thresholdValue`, then it will fire an alert.

```curl
curl --location --request POST 'https://app.amberflo.io/notification' \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: xyz' \
--data-raw '{
    "name": "product-item-units-tracker",
    "notificationType": "product-item-units",
    "productPlanId":"ff9c55a9-4796-480f-ab77-2ae943ae1700",
    "email": ["batman@wayne.io"],
    "thresholdValue": "100",
    "customerFilterMode": "per-customer",
    "meterId":"21b71110-ee51-11ec-ad99-5b1b04dba7a5",
    "enabled": true
}'
```

## Product Item Charge Amount

This is the same as **Product Item Usage Limit**, except it tracks the product item total charge on the current invoice instead of the meter units.

1. Select the meter by setting the `meterId`. It can found by calling the [List Meters API](docId\:i66o22RFPYjUNKud2fZ6i)  or [UI Dashboard](https://ui.amberflo.io/meters)
2. Select the pricing plan by setting the `productPlanId`. This can be obtained from the [Account Pricing API ](docId:-r4cpBaBC5wUBoiwpm0fb) or [UI Dashboard](https://ui.amberflo.io/billing-cloud/pricing-plans)
3. Set the product item charge limit `thresholdValue`
4. The `thresholdCondition` is always **greater-than**
5. The **range&#x20;**&#x6F;r time bucket is always the current billing cycle.

If the total product item total charge on the current invoice for the selected meter and pricing plan exceeds the `thresholdValue`, then it will fire an alert.

```curl
curl --location --request POST 'https://app.amberflo.io/notification' \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: xyz' \
--data-raw '{
    "name": "product-item-price-tracker",
    "notificationType": "product-item-price",
    "productPlanId":"ff9c55a9-4796-480f-ab77-2ae943ae1700",
    "email": ["batman@wayne.io"],
    "thresholdValue": "200",
    "customerFilterMode": "per-customer",
    "meterId":"21b71110-ee51-11ec-ad99-5b1b04dba7a5",
    "enabled": true
}'
```

## Undefined Customer

This scheduled alert is triggered when ingested records reference a customer that does not exist in Amberflo. To prevent alert fatigue, there is a one-hour cool-down period between alerts of this type.

```curl
curl --location --request POST 'https://app.amberflo.io/notification' \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: xyz' \
--data-raw '{
    "name": "Undefined Customer",
    "notificationType": "undefined-customer",
    "email": ["batman@wayne.io"]
}'
```

## Undefined Meter

This scheduled alert is triggered when ingested records reference a meter that has not been defined in Amberflo. Like the undefined customer alert, it includes a one-hour cool-down period between notifications.

```curl
curl --location --request POST 'https://app.amberflo.io/notification' \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: xyz' \
--data-raw '{
    "name": "Undefined Meter",
    "notificationType": "undefined-meter",
    "email": ["batman@wayne.io"]
}'
```


[title] Files
[path] Metering/Ingestion Options/

## Ingesting Meters from File Sources

Metering data is often generated and stored in **files** by other systems.

These files may be in formats such as:

- **CSV**
- **JSON**
- Other structured or semi-structured types

In many cases, the data needs to be **transformed** before it can be ingested into Amberflo’s metering system.


**Options for Ingesting File-Based Metering Data**

You have several methods available:

1. **Write code using our SDKs to:**
   Read data from files, Transform the data if needed and Send it to Amberflo for ingestion
2. **Use Logstash to:&#x20;**
   Parse and transform the file contents and Upload the transformed data to Amberflo's **S3** **bucket**.&#x20;
3. **Use an ETL Tool**
   Any **ETL tool** that can push data to **S3** is supported.
   Amberflo will automatically pick up the files and ingest them once they’re in your assigned bucket.

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/cFyFaRDkEBkgxXTvoX9Mi_image.png)


[title] SDK Sample Code
[path] Metering/Ingestion Options/

This sample demonstrates how to **ingest meter events** using the Amberflo SDK in a **Spring Boot** application.

**Steps:**

1. **Set Up the Amberflo Environment**
   Prepare your application environment with the required Amberflo client settings (e.g., API keys, region, etc.).
2. **Configure Service Discovery**
   Set up your application to discover and communicate with Amberflo’s ingestion service.
3. **Create a Helper Method**
   Implement a helper function to map your internal objects to the Amberflo ingestion payload format.

Maven dependencies (check for latest versions):

```xml
<dependencies>
  <dependency>
    <groupId>io.amberflo</groupId>
    <artifactId>metering-java-client</artifactId>
    <version>1.3.6</version>
  </dependency>
  <dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<version>1.18.20</version>
	</dependency>
</dependencies>
```

### **Environment Setup Details**

- Configure the necessary environment variables for the Amberflo SDK client.
- Add the required configuration entries to your application properties or environment.
- Ensure the service is **discoverable** by the Amberflo client at runtime.

:::CodeblockTabs
application.properties

```none
amberflo.apiKey=your-api-key-here
amberflo.region=US_West
amberflo.domain=Prod
amberflo.ingestion.frequency.seconds=1
amberflo.ingestion.batch.size=10
amberflo.isDebug=true
```
:::

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/9crRmRcbp7yAA4GQ43lt9_image.png)

```java
import com.amberflo.metering.ingest.meter_message.Domain;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import com.amberflo.metering.ingest.meter_message.Region;

@Getter
@Component
public class AmberfloEnvironment {

    @Value("${amberflo.apiKey}")
    private String apiKey;

    @Value("${amberflo.region}")
    private Region region;

    @Value("${amberflo.domain}")
    private Domain domain;

    @Value("${amberflo.ingestion.frequency.seconds}")
    private Double ingestionFrequencyInSeconds;

    @Value("${amberflo.ingestion.batch.size}")
    private Integer ingestionBatchSize;
}
```

## Setup Amberflo client bean for service discoverability

Amberflo provides a **singleton instance** of the client to ensure efficient ingestion of meter events.

To enable **dependency injection** and make the client discoverable as a service within your Spring Boot application:

- Create a configuration class
- Define a method that returns the Amberflo client as a **Spring bean**

This allows other components in your application to **inject the Amberflo client** where needed, promoting reusability and centralized configuration.

```java
import com.amberflo.metering.ingest.MeteringContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AmberfloClientConfig {

    @Autowired
    private AmberfloEnvironment amberfloEnvironment;

    @Bean
    public MeteringContext meteringContext() {
        MeteringContext meteringContext = null;
        try{
            meteringContext = MeteringContext.createOrReplaceContext(
                    this.amberfloEnvironment.getApiKey(),
              			//TODO: enter your service name here
              			//The service name will be added for all of your meters as an extra dimension.
                    "service-name",
                    this.amberfloEnvironment.getDomain(),
                    this.amberfloEnvironment.getRegion(),
                    this.amberfloEnvironment.getIngestionFrequencyInSeconds(),
                    this.amberfloEnvironment.getIngestionBatchSize());
            if(meteringContext.meteringInstance() != null) {
                LOGGER.info("Amberflo client initialized with region={}, domain={}, frequency={}, batchSize={}",
                        this.amberfloEnvironment.getRegion(),
                        this.amberfloEnvironment.getDomain(),
                        this.amberfloEnvironment.getIngestionFrequencyInSeconds(),
                        this.amberfloEnvironment.getIngestionBatchSize());
            }
        }
        catch(Exception ex){
            LOGGER.error("Amberflo client initialization failed: {}", ex.getMessage());
            ex.printStackTrace();
        }

        return meteringContext;
    }
}
```

## Entity to Amberflo payload mapper

In this approach, your internal entity is transformed into an **Amberflo ingestion payload** using a dedicated helper class.

For example:

- You have an entity class named `Event`
- This entity is mapped to the required SDK format in a helper class called `AmberfloMapper`

The transformation logic is encapsulated in a method like:

:::BlockQuote
AmberfloMapper.ingestMeterForEvent()
:::

This method can now be called **from anywhere in your code**, allowing you to easily meter events across your application in a centralized and reusable way.

Sample entity class:

```java
import java.io.Serializable;
import javax.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "event")
public class Event implements Serializable {
    private static final long serialVersionUID = 1L;
    
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private long id;

    @Column(name = "event_source_type")
    private String eventSourceType;

    @Column(name = "event_type")
    private String eventType;

    @Column(name = "customer_id")
    private String customerId;
}
```

Sample mapper helper:

```java
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.HashMap;
import java.util.Map;

import static com.amberflo.metering.ingest.MeteringContext.metering;

public class AmberfloMapper {
    private static final Logger LOGGER = LogManager.getLogger();

    public static void ingestMeterForEvent(final Event event, final String meterName){
        try {
            final String customerId = event.getCustomerId();
            final double meterValue = 1.0;
            final LocalDateTime eventTime = LocalDateTime.now(ZoneOffset.UTC);
            final Map<String, String> dimensions = new HashMap();
            dimensions.put("event_source_type", event.getEventSourceType());
            dimensions.put("event_type", event.getEventType());
            metering().meter(customerId, meterName, meterValue, eventTime, dimensions);
            LOGGER.info("Amberflo ingestion: event id {}, customerId {}, meter {}, event source type {}, event type {}",
                    event.getId(), customerId, meterName, dimensions.get("event_source_type"), dimensions.get("event_type"));
        } catch (Exception ex) {
            LOGGER.error("Amberflo ingestion failed: {}", ex.getMessage());
            ex.printStackTrace();
        }
    }
}
```


[title] What is Amberflo?
[path] /

Amberflo is a platform for **understanding AI costs and monetizing AI usage**.

As software shifts toward AI-powered features, agents, and autonomous workflows, two things become inseparable:

- **What your AI workloads cost**
- **What you charge customers for using them**

Traditional billing systems were not designed for this new model. AI usage is dynamic, request-based, and often priced differently across models and providers. At the same time, AI-native products frequently charge customers based on the same underlying usage signals.

Amberflo bridges this gap by bringing **AI cost visibility and customer billing into a single platform**.

With Amberflo, you can track how much your AI workloads cost, understand how those costs are distributed across teams or products, and generate accurate billing for customers based on that same usage data.



# **The Shift to AI Usage-Based Economics**

AI changes how software costs are incurred and how products are monetized.

In traditional SaaS systems:

- Infrastructure costs and customer billing were often separate concerns.
- Billing was typically subscription-based.


In AI-driven products:

- Costs are generated **per request**, **per token**, or **per operation**.
- AI providers charge dynamically across models and regions.
- Customer pricing is frequently **usage-based or hybrid usage + subscription**.


This creates a new operational challenge:

- You must continuously understand **cost, revenue, and margin** at the level of real AI usage.

Amberflo provides the system of record for this.



# **Amberflo Platform**

Amberflo brings together three core capabilities required to operate AI-powered products at scale.



## **1. AI Usage and Cost Intelligence**

Amberflo captures detailed AI usage data and converts it into accurate cost visibility.

Organizations can track usage across models, providers, teams, applications, or customers.

Capabilities include:

- Real-time AI usage tracking across models and providers
- Token-level usage and cost attribution
- Cost visibility by business unit, product, workload, or customer
- AI spend dashboards and analytics
- Cost guardrails, budgets, and alerts

This allows teams to understand **where AI costs originate and how they evolve over time**.



## **2. AI Monetization and Customer Billing**

Amberflo enables companies to monetize AI usage with precision.

Using the same usage data that drives cost visibility, Amberflo can generate customer-facing billing aligned with how AI is actually consumed.

Capabilities include:

- Meter AI usage at customer, tenant, or feature level
- Configure flexible usage-based pricing models
- Support tiers, bundles, overages, and prepaid usage
- Generate invoices and billing reports
- Provide usage transparency to customers

This ensures your revenue model stays aligned with the underlying usage that drives your infrastructure costs.



## **3. Margin and Unit Economics for AI**

AI-native products must constantly manage the relationship between **cost and revenue**.

Amberflo connects usage, cost, and billing so you can understand the financial performance of your AI features.

With Amberflo you can answer questions like:

- What does each AI feature actually cost to run?
- How much revenue does each customer generate relative to their usage?
- Are we making money on our AI-powered services?

By combining cost intelligence and billing into one system, Amberflo enables teams to manage **AI economics in real time**.



# **Built for AI-Native Products**

Amberflo is designed for companies building AI-powered products, platforms, and agents.

It integrates with modern AI stacks and supports rapidly evolving models, pricing structures, and workloads.

Whether you are:

- Tracking internal AI usage across teams
- Understanding the cost of AI features in your product
- Charging customers for AI-powered services

Amberflo provides the infrastructure needed to manage both **AI spend and AI revenue** in a single platform.



# Join our Discord Channel

For questions, best practices, and help getting the most out of Amberflo, join our community on **Discord**. It’s the fastest way to get answers, share feedback, and connect with other teams building AI-powered products.

Our team actively monitors the community and is happy to help with setup, usage questions, or ideas on how to structure AI cost tracking and billing for your product.

**Join the Amberflo Discord:** [https://discord.gg/femn96kV8z](https://discord.gg/femn96kV8z)

[title] AWS SQS
[path] Metering/Meter Integration Options/

# Write events directly to an Amberflo supplied and secured AWS SQS queue

Amberflo provides your account with a **FIFO (First-In-First-Out) AWS SQS queue** that includes the necessary **access rights and permissions** to submit meter records.

- Meter records are **automatically processed** as they arrive in the queue.
- No additional configuration is needed on your end for ingestion.

To get your **SQS queue and S3 bucket provisioned**, please **contact us**.

## Format

The **meter records sent to the SQS queue&#x20;**&#x6D;ust follow the same standardized format as used by the Amberflo Ingest API.&#x20;
Here are some examples:

```json
[{
     "customerId": "customer-123",
     "meterApiName": "ComputeHours",
     "meterValue": 5,
     "meterTimeInMillis": 1619445706909,
     "dimensions": {
        "region": "us-west-2",
        "az": "az1"
     }
}]
```

We also support [NDJSON](https://github.com/ndjson/ndjson-spec) format (JSON separated by a newline)

```json
{ "customerId": "customer-123", "meterApiName": "ComputeHours", "meterValue": 5, "meterTimeInMillis": 1619445706909 }
{ "customerId": "customer-321", "meterApiName": "ComputeHours", "meterValue": 4, "meterTimeInMillis": 1619445712341 }
{ "customerId": "customer-123", "meterApiName": "ComputeHours", "meterValue": 1, "meterTimeInMillis": 1619445783456 }
```

## Code Example

```python
import json
from uuid import uuid1
from datetime import date

import boto3

records_to_send = [{
     'customerId': 'customer-123',
     'meterApiName': 'ComputeHours',
     'meterValue': 5,
     'meterTimeInMillis': 1619445706909,
     'dimensions': {
        'region': 'us-west-2',
        'az': 'az1'
     }
}]

queue_url = 'https://sqs.us-west-2.amazonaws.com/624335419252/62-ingest.fifo'

sqs = boto3.client('sqs')

sqs.send_message(
    QueueUrl=queue_url,
    MessageBody=json.dumps(records_to_send),
    MessageGroupId=str(uuid1()),
    MessageDeduplicationId=str(uuid1()),
)
```

## Troubleshooting

Meter records sent to the **SQS queue&#x20;**&#x61;re first saved in an **S3 bucket** before being processed.

If there's a failure during processing:

- A **failure report file** will be created in the S3 bucket with the reason for the error.
- A **dead-letter queue** is also set up to capture messages that could not be written to S3.

This setup ensures reliabl&#x65;**&#x20;troubleshooting** and **traceability** for all ingestion issues.

[title] Cost Visibility Quick Start
[path] Quick Start/

This guide walks you through a complete flow for tracking and understanding cost in Amberflo.

By the end, you will have:

- A **meter** capturing usage
- A **cost rate** defining how usage translates into cost
- A **workload** for attribution
- **Spend data** visible in the dashboard

The goal is to get a working cost visibility loop. Keep it simple.

## **What You’ll Build**

Example use case:

You want to understand the cost of an internal system, such as a **text processing pipeline**.

You will:

1. Create a meter to track usage
2. Define cost rates for that usage
3. Create a workload
4. Send usage attributed to that workload
5. View resulting spend

## **Step 1: Create a Meter**

This is identical to the Customer Billing Quick Start.

### **Navigate**

- In the left navigation:
  - Go to **Meters**

### **Create the Meter**

- Click **Create Meter** (top right)

### **Configure**

- **Label**
  Example: Token Usage or API Requests
- **API Name**
  Auto-generated
  - No spaces
  - Allowed: letters, numbers, hyphens, underscores
  - Max 50 characters
- **Description (optional)**
- **Meter Type**
  Select **Count**

### **Dimensions (Recommended)**

Add dimensions that impact cost.

Example:

- model
- type (input, output)

Note:

- **Workload** is included by default
- You do not need to remove it

Click **Create**.

## **Step 2: Configure Cost Rates**

### **Navigate**

- In the left navigation:
  - Go to **Meters**
- Select your meter to open it
- Click the **Rate** tab
- Click **Create Meter Rate** (top right)

### **Configure the Rate**

- **Label**
  Example: Cost Rates v1
- **Description (optional)**

### **Choose Rate Model**

**Option 1: Per Unit**

- Same cost for every unit

Example:

- Every request costs $0.01

**Option 2: Per Unit with Dimensions (Recommended)**

Cost varies based on dimension values.

Example:

- Input vs output usage
- Different models with different costs

### **Build the Rate Table**

- Each row = a combination of dimension values
- Each row has its own cost per unit

Example:

| **model** | **type** | **cost** |
| --------- | -------- | -------- |
| A         | input    | 0.001    |
| A         | output   | 0.002    |
| B         | input    | 0.003    |

Notes:

- You **cannot remove workload** from this page
- Only include dimensions relevant to cost
- Use **Add Row** to define combinations

Click **Save**.

## **Step 3: Create a Workload**

### **Navigate**

- In the left navigation:
  - Go to **Workloads**

### **Create**

- Click **Create Workload** (top right)

Fill in:

- **Name**
  Example: Text Processing Pipeline
- **Workload ID**
  Unique identifier
  - Letters, numbers, hyphens, underscores only

Click **Save**.

## **Step 4: Send Usage (with Workload)**

### **Navigate**

- In the left navigation:
  - Go to **Meters**
- Open your meter

### **Upload an Event**

- Click **Event Upload**

Fill in:

- **Customer**
  Leave blank
- **Workload**
  Select your workload
- **Value**
  Example: 1000
- **Dimensions**
  Must match values defined in your rate table
  Example:
  - model = A
  - type = input

Click **Ingest Meter**.

## **Step 5: View Spend**

### **Navigate**

- In the left navigation:
  - Go to **Spend**

### **What to Expect**

You should see:

- **Spend Month-to-Date**
- **Spend Quarter-to-Date**
- **Spend Year-to-Date**

Since this is your first data, these may be the same.

Under **Overview**:

- **Top Spend by Workload**
  Your workload should appear
- **Top Trending Workloads**
  Your workload should appear

### **If You Do Not See Data**

Amberflo does not automatically refresh dashboards.

- **Refresh the page**

If data still does not appear:

- Confirm usage was sent with a **workload selected**
- Confirm your dimension values match the rate table
- Confirm a **rate is configured** on the meter

## **What You Just Did**

You completed the full cost visibility loop:

- Metered usage
- Defined cost logic
- Attributed usage to a workload
- Generated spend data

## **What’s Next**

From here, you can expand into:

- More detailed dimension-based cost models
- Multiple workloads
- Cost guards and alerts
- Connecting cost with customer billing

This is the foundation for understanding how your system consumes resources and where your costs come from.

[title] Price Machine Overview
[path] Billing & Monetization/

## Build any Pricing and Billing Model

At the core of our Billing Cloud, built from the ground up to tackle the demands of real-time usage-based pricing and billing, is a highly flexible and scalable Pricing Machine State Engine (a.k.a rating engine).

We describe below some of the artifacts and how the pricing machine works.
This background will help you understand the unique and powerful capabilities and flexibility of the pricing engine and, we hope, will unleash your imagination in building and deploying the pricing models that best suit your products and customer base.

It provides the tools needed to create any pricing plan that you have in mind.

# How the price machine works

Before delving into the different types of price machine nodes, we will provide a high-level description for how the price calculation works.

Amberflo invoice calculator is the logic which converts a specific meter's hourly usage aggregation for a given customer to an invoice.

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/rQC8F_DAk923o9AHGN5fU_image.png)

**The exact input** which the invoice calculator consumes is the meter hourly usage aggregation of a given customer **partitioned by all of the official dimensions of the invoice**.

Then the price calculator executes the following steps:
**Step 1**: Fetch the relevant price machine for the customer and the meter.
**Step 2**: Reduce the input to a smaller set of partitions as defined by the specific price machine.
**Step 3**: Execute the price machine on the reduce input.

**The exact output** of the price machine is set of what call an 'Item-Variant-Invoice'. An 'Item-Variant-Invoice' is a partition of the invoice by a sub-set of the meter official dimensions.

Given these we will define each price machine using two properties:

1. **Required dimensions**
2. **Function**

### **Example**

Let's assume you have the following meter:

```json
{
    "meterApiName": "api-calls",
    "dimensions": [ "region", "is-urgent-request" ],
    "meterType": "sum"
}
```

Then the price machine will create the following **'Item-Variant-Invoice':**

```json
[
    { "variant": { "region": "US" }, "price": 33.5 }, // (10 + 67)/2
    { "variant": { "region": "CA" }, "price": 8.5 } // (14 + 3)/2
]
```

# The different types of price machines

Now that we know something about how Amberflo's price machine works, we will introduce the different types of machines we offer.

## Price Leaf Node

The leaf price machine is the simplest price machine one can think of, and yet as we will see next it by itself quite an advance price machine.

The properties of this machine are:

1. **Required dimensions : none**
2. **Function :&#x20;**&#x74;ier based price step function.

Bellow is the 'leaf price machine' schema:

```yaml
type: "object"
description: "A simple leaf model to calc the price of a single meter usage."
properties:
  allowPartialBatch:
    type: "boolean"
    description: "Default to false. If true the LeafNode machine won't round up the usage to the next whole unit of
    batch, but instead calc the price per the exact usage consumed."
  tiers:
    type: "array"
    description: "This array represents the price step function of the given meter. The price is calculated using the
    price tiers as described below, where each tier starts at startAfterUnit (included) and ends at the
    startAfterUnit of the next tier (excluded)."
    items:
      type: "object"
      properties:
        batchSize:
          type: "integer"
          description: "Defines how much usage is included in a single batch. When calculating the price the Leaf-Node
          will round up the usage to next whole unit of batch and calc the price for the total amount of batches."
        pricePerBatch:
          type: "number"
          description: "This is your price for a single batch of usage in terms of the base currency unit of the
          account (usd in most cases)."
        startAfterUnit:
          type: "integer"
      required:
        - startAfterUnit
        - batchSize
        - pricePerBatch
  type:
    type: "string"
    default: "LeafNode"
required:
  - type
  - tiers
```

Unless you want to partition the invoice by a subset of the meter dimensions then this machine is likely all you need.

Let's look at a few examples of possible configuration for this machine.

### **Example 1.1:**

A machine that calculates a price of $0.10 per 1 unit of usage.

For example the price of 12 units of usage is $1.20.

```json
{
    "type": "LeafNode",
    "tiers": [
        {
            "startAfterUnit": 0,
            "batchSize": 1,
            "pricePerBatch": 0.1
        }
    ],
    "allowPartialBatch": true
}
```

### **Example 1.2:**

A machine that calculates a price of $0.50 per 5 units of usage with no partial batching.

Unlike the previous example this machine doesn't allow partial batches, which means, the price usage will be rounded up to whole unit of the batch size, and the price will be determined based on the amount of batches.

For example, the price of 12 units of usage is $1.50 (3 batches).

```json
{
    "type": "LeafNode",
    "tiers": [
        {
            "startAfterUnit": 0,
            "batchSize": 5,
            "pricePerBatch": 0.5
        }
    ],
    "allowPartialBatch": false
}
```

### **Example 1.3:**

A machine that calculates a price of $0.10 per each 1 unit of usage for the first 10 units of usage, and then drop the price to $0.05 for each unit afterwards.

For example, the price of 12 units of usage is $1.10 (0.1 *10 + 0.05&#x20;*&#x32;).

```json
{
    "type": "LeafNode",
    "tiers": [
        {
            "startAfterUnit": 0,
            "batchSize": 1,
            "pricePerBatch": 0.1
        },
        {
            "startAfterUnit": 10,
            "batchSize": 1,
            "pricePerBatch": 0.05
        }
    ],
    "allowPartialBatch": false
}
```

### **Example 1.4:**

A machine that calculates a price of $0 per each 1 unit of usage for the first 10 units of usage, and then increases the price to $0.05 for each unit thereafter.

For example, the price of 12 units of usage is $0.1 (0 *10 + 0.05&#x20;*&#x32;).

```json
{
    "type": "LeafNode",
    "tiers": [
        {
            "startAfterUnit": 10,
            "batchSize": 1,
            "pricePerBatch": 0.05
        }
    ],
    "allowPartialBatch": false
}
```

## Discrete Price Leaf Node

Discrete-price-leaf-node is another type of a leaf node. It is different than the Price-leaf-node in a way that it calculates the price of each usage time slot independently of other hours.

For example:
Let's assume we have the following tiers:

- 0-100: no charge
- 100 or more: $1 per 1 unit of usage.

Now let's assume we had the following usage:

- day 1: 95
- day 2: 75

With the original Price-leaf-node we will charge that customer for $70 (95 + 75 - 100).
This is because the that model applies the tiers for the usage over the entire period.

With this Discrete-price-leaf-node we will charge the customer for $0 because on each day we used less than 100 units.

The properties of this machine are:

1. **Required dimensions:** none
2. **Function**: tier based price step function.

Bellow is the 'discrete leaf price machine' schema:

```yaml
---
type: "object"
properties:
  allowPartialBatch:
    type: "boolean"
  tiers:
    type: "array"
    description: "This array represents the price step function of the given meter. The price is calculated using the
    price tiers as described below, where each tier starts at startAfterUnit (included) and ends at the
    startAfterUnit of the next tier (excluded)."
    items:
			type: "object"
      properties:
        batchSize:
          type: "integer"
          description: "Defines how much usage is included in a single batch. When calculating the price the Leaf-Node
          will round up the usage to next whole unit of batch and calc the price for the total amount of batches."
        pricePerBatch:
          type: "number"
          description: "This is your price for a single batch of usage in terms of the base currency unit of the
          account (usd in most cases)."
        startAfterUnit:
          type: "integer"
      required:
        - startAfterUnit
        - batchSize
        - pricePerBatch
  type:
    type: "string"
    default: "DiscreteLeafNode"
required:
  - type
  - tiers
```

## Volume Based Leaf Node

A leaf node can calc the price of a given usage (of a given invoice-variant) without deferring any pricing decisions to subsequent nodes. In other words a leaf node can't have subsequent nodes which it depends on.

For this leaf-node the only thing that dictates the price of a meter is the total amount of usage over time.

This price node is based on the total usage volume.
For example, Let's assume we have the following tiers:

1. from 0 -> $1 per unit of usage
2. from 10 -> $3 per unit of usage

Example:
let's assume that during an invoice cycle we have a total of 15 units used. Then the price should be:
15 \* 3 = $45

The properties of this machine are:

1. **Required dimensions**: none
2. **Function**: tier based price function (but not a step function).

Bellow is the 'Volume based leaf leaf ' schema:

```yaml
type: "object"
properties:
  type:
    type: "string"
    default: "volume_based_leaf_node"
  volumeToUnitPriceMap:
    type: "object"
    description: "A map of tier to price"
    additionalProperties:
      type: "number"
required:
  - type
  - volumeToUnitPriceMap
```

Example:

```json
{
    "type": "volume_based_leaf_node",
    "volumeToUnitPriceMap": {
        "0.0": 0,
        "11.0": 10
    }
}
```

### Limitations of the volume base price node

In the example above we describe the following tiers table:

1. from 0 -> $1 per unit of usage
2. from 10 -> $3 per unit of usage

And to calculate the price of 15 units of usage we just multiple 15 by 3 = $45.

But what if the values were decreasing as the usage goes ?

For example let's assume we have the following table:

1. from 0 -> $3 per unit of usage
2. from 10 -> $1 per unit of usage

Now, a customer with 15 unit of usage will pay $15, while a customer with 9 unit of usage will pay 9 \* 3 = $27.
That stands against the core ideas of "usage-based-pricing", that guide us to create fair price machines (where if you use more you pay more, and there is no reason to use the system more just to pay less).

So, one by design limitation of this price-machine is that the price per unit can only increase as we use the system more. If you wish to let it decrease then please refer to the 'price-leaf-node' and 'discrete-price-lead-node' mentioned above.

## Dimension Matrix Node

This type of machine allows you to create a different price logic for different combinations of dimensions values.

Basically you define a **Price-Leaf-Node** for each combination of dimensions values.
Notice that this price machine will drop any usage for which the dimension values are not defined in the price machine (we will share an example for this below).

The properties of this machine are:

1. **Required dimensions**: as defined in the 'dimensionKeys' property.
2. **Function: A sum&#x20;**&#x6F;f a tier based price step functions.

```json
---
type: "object"
description: "The DimensionMatrixNode allows us to define different meters for different combinations of dimensions
values. For example let's assume you want to bill your customers by the amount storage used over time. Also let's assume
you have two types of storage: hard-disk and in-memory, and you want to have a different price for each type of storage.
Then add the storage-type as a dimension of your meters, and have a different Price-LeafNode for each sort of storage."
properties:
  dimensionKeys:
    type: "array"
    description: "A list of dimensions which affect the price."
    items:
      type: "object"
  dimensionsPrices:
    type: "array"
    items:
      type: "object"
      properties:
        dimensionValues:
          type: "array"
          description: "A values - one per each of the dimension keys. The DimensionMatrixNode will partition the given
          usage by this dimensions (and drop any usage which doesn't have a corresponding dimensions values defined in
          this model). Then the DimensionMatrixNode will calculate the price of each partitioned usage "
          items:
            type: "object"
        leafNode:
          type: "object"
          description: "A simple leaf model which calc the price of a single meter usage."
          properties:
            allowPartialBatch:
              type: "boolean"
            tiers:
              type: "array"
              items:
                type: "object"
                properties:
                  batchSize:
                    type: "integer"
                  pricePerBatch:
                    type: "number"
                  startAfterUnit:
                    type: "integer"
                required:
                  - startAfterUnit
                  - batchSize
                  - pricePerBatch
            type:
              type: "string"
              default: "LeafNode"
          required:
            - type
            - tiers
  type:
    type: "string"
    default: "DimensionMatrixNode"
required:
  - type
  - dimensionsPrices
  - dimensionKeys
```

### **Example 2:**

The machine below defines a price according to the following pricing plan:

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/CxtcGxh5hDa1BAhwDEoyP_image.png)

Equivalent price machine:

```json
{
    "type": "DimensionMatrixNode",
    "dimensionKeys": [
        "Region",
        "Memory"
    ],
    "dimensionsPrices": [
        {
            "dimensionValues": [
                "us-west-1",
                "1Gb"
            ],
            "leafNode": {
                "type": "PricePerUnitLeafNode",
                "tiers": [
                    {
                        "startAfterUnit": 0,
                        "batchSize": 1,
                        "pricePerBatch": 0.001000000
                    }
                ],
                "allowPartialBatch": false
            }
        },
        {
            "dimensionValues": [
                "us-west-1",
                "2Gb"
            ],
            "leafNode": {
                "type": "PricePerUnitLeafNode",
                "tiers": [
                    {
                        "startAfterUnit": 0,
                        "batchSize": 1,
                        "pricePerBatch": 0.002000000
                    }
                ],
                "allowPartialBatch": false
            }
        },
        {
            "dimensionValues": [
                "us-west-1",
                "4Gb"
            ],
            "leafNode": {
                "type": "PricePerUnitLeafNode",
                "tiers": [
                    {
                        "startAfterUnit": 0,
                        "batchSize": 1,
                        "pricePerBatch": 0.002000000
                    }
                ],
                "allowPartialBatch": false
            }
        },
        {
            "dimensionValues": [
                "us-east-2",
                "1Gb"
            ],
            "leafNode": {
                "type": "PricePerUnitLeafNode",
                "tiers": [
                    {
                        "startAfterUnit": 0,
                        "batchSize": 1,
                        "pricePerBatch": 0.001500000
                    }
                ],
                "allowPartialBatch": false
            }
        },
        {
            "dimensionValues": [
                "us-east-2",
                "2Gb"
            ],
            "leafNode": {
                "type": "PricePerUnitLeafNode",
                "tiers": [
                    {
                        "startAfterUnit": 0,
                        "batchSize": 1,
                        "pricePerBatch": 0.003000000
                    }
                ],
                "allowPartialBatch": false
            }
        },
        {
            "dimensionValues": [
                "us-east-2",
                "4Gb"
            ],
            "leafNode": {
                "type": "PricePerUnitLeafNode",
                "tiers": [
                    {
                        "startAfterUnit": 0,
                        "batchSize": 1,
                        "pricePerBatch": 0.004500000
                    }
                ],
                "allowPartialBatch": false
            }
        }
    ]
}
```

## Distinct Resource Reducer

This machine counts the distinct values for a subset of dimensions and then calculates a price using a Price-Leaf-Node for the count.

The distinct values count function can be applied on:

1. Each individual hour.
2. Each day.
3. For the entire invoice period.

The properties of this machine are:

1. **Required dimensions:&#x20;**&#x61;s defined in the 'resourceDefiningDimensions' property.
2. **Function: A sum&#x20;**&#x6F;f a tier based price step functions which is applied on the result of the distinct count.

```yaml
---
type: "object"
description: "Use this price model if you want to to charge you customer based on a distinct count of some set of
dimensions count. For example, let's assume you have a dimension called job-id, and you want to invoice your customers
based on the amount of jobs they ran, then you can use this price model to accomplish that."
properties:
  granularity:
    type: "string"
    description: "HOURLY, DAILY, ENTIRE_INVOICE_PERIOD"
  nextNode:
    type: "object"
    description: "A simple leaf model which calc the price of a single meter usage."
    properties:
      allowPartialBatch:
        type: "boolean"
      tiers:
        type: "array"
        items:
          type: "object"
          properties:
            batchSize:
              type: "integer"
            pricePerBatch:
              type: "number"
            startAfterUnit:
              type: "integer"
          required:
            - startAfterUnit
            - batchSize
            - pricePerBatch
      type:
        type: "string"
        default: "LeafNode"
    required:
      - type
      - tiers
  resourceDefiningDimensions:
    type: "array"
    description: "The dimension names to use for the distinct count."
    items:
      type: "object"
  type:
    type: "string"
    default: "distinct_resource_reducer"
required:
  - type
  - granularity
  - nextNode
  - resourceDefiningDimensions
```

### **Example 3:**

Let's assume your company has a pool of machines and provides a service which allows your customers to use your machines to run all kind of tasks on-demand.

Let's also assume that you measure how long each task runs, but for simplicity you want to invoice customers by the distinct number of tasks that they run during the invoice period. In such a case you can define the following price machine:

```json
{
    "type": "distinct_resource_reducer",
    "resourceDefiningDimensions": [
        "Country"
    ],
    "granularity": "ENTIRE_INVOICE_PERIOD",
    "nextNode": {
        "usageVariationsByTimeMap": null,
        "dimensions": ["job-id"],
        "type": "LeafNode",
        "tiers": [
            {
                "startAfterUnit": 0,
                "batchSize": 5,
                "pricePerBatch": 2
            }
        ],
        "allowPartialBatch": false
    }
}
```

This machine will have a distinct count for all of the jobs that ran during the invoice-period (regardless of how long they ran or if the runtime was spread over multiple hours or days), and charge the customer $2 per each batch of 5 jobs.

## Max Reducer

Similarly to the 'Distinct-Resource-Reducer' this price machine node can also reduce the time granularity of your hourly usage. This price machine finds the max value to pick the value of each usage partition.

This model allows you to charge your customers based on the peak hourly usage over:

1. Each day.
2. The entire invoice period.

The properties of this machine are:

1. **Required dimensions:&#x20;**&#x49;nherit the 'Required dimensions' of the next node.
2. **Function:&#x20;**&#x61;pply the next node function over the hourly, daily or entire\_invoice max usage.

**NOTICE:&#x20;**&#x79;ou can plug this price-machine-node to any of the price machines described above.

```yaml
---
type: "object"
description: "Hours is the most granular time unit in Amberflo. Amberflo calculates hourly aggregation of each of your
meter, where the aggregations are partitioned according to the meter's official dimensions values. In other words
all of the price machines takes hourly meter aggregation partitioned by the dimension values.
You can use this node if you want to have your price model based on time unit which is less granular than a single hour.
For example if you want to have your price model based on the daily max value of any hourly aggregation of the same day,
you can use this model to reduce the time granularity of the aggregations."
properties:
  granularity:
    type: "string"
    description: "HOURLY, DAILY, ENTIRE_INVOICE_PERIOD"
  nextNode:
    type: "object"
    description: "Next node can be any sort price node."
  type:
    type: "string"
    default: "max_reducer"
required:
  - granularity
  - nextNode
  - type
```

### **Example 4.1:**

Let's use the previously mentioned example and assume your company has a pool of machines and provides a service that allows your customers to use your machines to run all kind of tasks.

But this time, let's assume you want to charge your customers according to the max hourly usage rate they incurred during the invoice period.

You can create a map\_reducer similar to that described below.

```json
{
    "type": "max_reducer",
    "granularity": "entire_invoice_period",
    "nextNode": {
        "type": "LeafNode",
        "tiers": [
            {
                "startAfterUnit": 0,
                "batchSize": 5,
                "pricePerBatch": 40
            }
        ],
        "allowPartialBatch": false
    }
}
```

### **Example 4.2:**

Let's assume we take Example 2, mentioned above (for the dimensions matrix), but this time we want to calculate based on the max daily value.

As mentioned, we can have any node as the next node of the max\_reducer price machine. Therefore we can just wrap the aforementioned 'DimensionMatrixNode' inside of a max\_reducer.

```json
{
    "type": "max_reducer",
    "granularity": "daily",
    "nextNode": {
        "type": "DimensionMatrixNode",
        "dimensionKeys": [
            "Region",
            "Memory"
        ],
        "dimensionsPrices": [
            {
                "dimensionValues": [
                    "us-west-1",
                    "1Gb"
                ],
                "leafNode": {
                    "type": "PricePerUnitLeafNode",
                    "tiers": [
                        {
                            "startAfterUnit": 0,
                            "batchSize": 1,
                            "pricePerBatch": 0.001000000
                        }
                    ],
                    "allowPartialBatch": false
                }
            },
            {
                "dimensionValues": [
                    "us-west-1",
                    "2Gb"
                ],
                "leafNode": {
                    "type": "PricePerUnitLeafNode",
                    "tiers": [
                        {
                            "startAfterUnit": 0,
                            "batchSize": 1,
                            "pricePerBatch": 0.002000000
                        }
                    ],
                    "allowPartialBatch": false
                }
            },
            {
                "dimensionValues": [
                    "us-west-1",
                    "4Gb"
                ],
                "leafNode": {
                    "type": "PricePerUnitLeafNode",
                    "tiers": [
                        {
                            "startAfterUnit": 0,
                            "batchSize": 1,
                            "pricePerBatch": 0.002000000
                        }
                    ],
                    "allowPartialBatch": false
                }
            },
            {
                "dimensionValues": [
                    "us-east-2",
                    "1Gb"
                ],
                "leafNode": {
                    "type": "PricePerUnitLeafNode",
                    "tiers": [
                        {
                            "startAfterUnit": 0,
                            "batchSize": 1,
                            "pricePerBatch": 0.001500000
                        }
                    ],
                    "allowPartialBatch": false
                }
            },
            {
                "dimensionValues": [
                    "us-east-2",
                    "2Gb"
                ],
                "leafNode": {
                    "type": "PricePerUnitLeafNode",
                    "tiers": [
                        {
                            "startAfterUnit": 0,
                            "batchSize": 1,
                            "pricePerBatch": 0.003000000
                        }
                    ],
                    "allowPartialBatch": false
                }
            },
            {
                "dimensionValues": [
                    "us-east-2",
                    "4Gb"
                ],
                "leafNode": {
                    "type": "PricePerUnitLeafNode",
                    "tiers": [
                        {
                            "startAfterUnit": 0,
                            "batchSize": 1,
                            "pricePerBatch": 0.004500000
                        }
                    ],
                    "allowPartialBatch": false
                }
            }
        ]
    }
}
```

## Avg Usage Reducer

This node has a similar logic to the "Max Reducer" described above with two main differences:

1. It calculated the avg usage instead of a max - although notice that similarly to the "Max Reducer" we can split the usage into hours (hourly avg), days (an avg value for each day of the invoice), or calc one avg value for the entire invoice period.
2. The avg is being calculated over the **ORIGINAL time period of invoice.**

Regarding #2:

Avg is calculated using the following formula:

:::BlockQuote
Avg of usage from time t1 to time t2 =  \[Usage during t1 to t2]  / \[t2 - t1]
:::

Now, let's look at the following example and ask ourselves what is the denominator, meaning what are the t2, and t1 values.

1. Let’s assume today is 7/22/2022.
2. We started an invoice at 7/15/2022.
3. The end time of the invoice is 8/15/2022

To make things simple let's assume our avg price state machine granularity is 'entire\_invoice\_period':

So, at 7/22 we have two options for how to calc the denominator:
Option 1 - from 7/15/2022 to 7/22/2022, using current usage
Option 2 - from 7/15/2022 to 8/15/2022, using current usage

When we said that the avg is being calculated over the **ORIGINAL time period of invoice&#x20;**&#x77;e meant is that we follow **option 2**. Option 2, has some big advantages over option 1 among them are:

1. Using option 2 the price only goes up and can't really fluctuate.
2. Option 2 is a more fair pricing mechanism for scenarios where a customer changes it plan or offboards from a service.

```yaml
---
type: "object"
description: "Hours is the most granular time unit in Amberflo. Amberflo calculates hourly aggregation of each of your
meter, where the aggregations are partitioned according to the meters official dimensions values. In other words
all of the price machines takes hourly meter aggregation partitioned by the dimension values.
You can use this node if you want to have your price model based on time unit which is less granular than a single hour.
For example if you want to have your price model based on the daily avg value of any hourly aggregation of the same day,
you can use this model to reduce the time granularity of the aggregations."
properties:
  granularity:
    type: "string"
    description: "HOURLY, DAILY, ENTIRE_INVOICE_PERIOD"
  nextNode:
    type: "object"
    description: "Next node can be any sort price node."
  type:
    type: "string"
    default: "average_reducer"
required:
  - granularity
  - nextNode
  - type
```

## Resource Groups Reducer

The main reason to use this node is if you want to partition your usage by a certain set of dimension
values which is not included in the next nodes, without specifying a unique leaf-price machine as done in the dimensions-matrix-node.

For example, let's assume you operate on many regions but you have the same pricing logic for all regions. Having said that, let's assume that when generating the invoice you want to calculate the price-per-region using the same leaf-node. You can use this method to have such logic.

The properties of this machine are:

1. **Required dimensions:&#x20;**&#x41; unified set as defined in 'resourceDefiningDimensions' + the 'Required dimensions' of the next node.
2. **Function:&#x20;**&#x61;pply the next node function over the partitioned usage.

```yaml
---
type: "object"
description: "The main reason to use this node is if you want to partition your usage by a certain set of dimension
values which isn't included in the next nodes. For example, let's assume you operate on many regions but you have the
same pricing logic for all regions. Having said that let's assume when coming up with the invoice you want to
calculate the price per region using the same leaf-node (let's say). You can use this method to have such logic.
"
properties:
  aggregationType:
    type: "string"
    description: "SUM, MAX"
  nextNode:
    type: "object"
    description: "Use any price machine us next node."
  resourceDefiningDimensions:
    type: "array"
    description: "The partitioning dimensions."
    items:
      type: "object"
  type:
    type: "string"
    default: "resource_groups_reducer"
required:
  - aggregationType
  - nextNode
  - resourceDefiningDimensions
  - type
```

### Example 5:

We can implement the example mentioned above (partition by region but keep the same price logic for all regions) using a configuration similar to the one below:

```json
{
    "type": "resource_groups_reducer",
    "resourceDefiningDimensions": [
        "Region"
    ],
    "nextNode": {
        "type": "LeafNode",
        "tiers": [
            {
                "startAfterUnit": 0,
                "batchSize": 5,
                "pricePerBatch": 0.1
            }
        ],
        "allowPartialBatch": false
    },
    "aggregationType": "sum"
}
```

# Endpoints

Two ways to define a price machine:

1. Via Amberflo's console - Amberflo's console contains a wizard that allows you to define most of the models mentioned above. It's an easy and safe way to do it.
2. If the UI doesn't support the exact configuration you want to have then you can always use our APIs. The URL is:
   `http://app.amberflo.io/payments/pricing/amberflo/account-pricing/product-item-price`(just notice that the price-machine is one attribute of the entire item-price object - [example](https://docs.amberflo.io/reference/create-a-product-item-price-using-a-product-item-price-template))


[title] Usage & Amount Alerting
[path] Billing & Monetization/


[title] UI SDK - Vanilla Javascript integration
[path] Billing & Monetization/Customer Billing Portal/

**SDK library integration**

The **Amberflo UI Kit SDK** is a development kit built with Vanilla JavaScript, designed to let you embed core Amberflo views for a specific customer directly on your own website.

While the SDK uses **React** behind the scenes, it does not require your project to be built with React. It wraps the React UI Kit and handles everything internally.

The SDK is responsible for making the necessary API calls and rendering the components into a specified `<div>` element in your HTML.

📘 ***Requisites***

***These are some of the languages where the SDK can be used: in React, Javascript, Angular, Vue.***

## Getting Started

1.Include the script in the `<head>` of your site:

```html
<script
  type="module"
  src="https://js.amberflo.io/index.1.0.30.js">
</script>
```

***📘 Make sure to use to the latest available version in the script (e.g.: 1.0.30). You can check versions under Releases section.***

2\. Retrieve your `session token` to access Amberflo API endpoints. With that session id, you will need to make a new instance of the AmberfloSDK included on the script above. To do that, you will have to add this code snippet under the first script tag you added:

```html
<script>
  window.onload = () => {
    amberflo = new AmberfloSDK("{{YOUR_SESSION_TOKEN}}");
  };
</script>
```

***📘Replace&#x20;***`{{YOUR_SESSION_TOKEN}} `***with the token you got. For example, if you received a session token that was&#x20;***`1234abcd`***, you would instantiate the Amberflo SDK like so:&#x20;***`amberflo = new AmberfloSDK("1234abcd").`***&#x20;To know more about how to get your session token read&#x20;***[Get Session Token](docId\:I2mADjtkAuAs4YNxv2uNs)***.***

3\. Since sessions can expire, on your new instance of `AmberfloSDK` there is a method to set the new sessionId. You can call it anytime to `amberflo.setSessionId("{{YOUR_NEW_SESSION_TOKEN}}");`.
The app will continue to work the same with the same ID.

4\. To add a new Amberflo view, you will need to place a `<div>` on your code, with the class amberflo and a `data-amberflo-type` attribute to setup the element. This `data-amberflo-type` attribute is responsible for rendering the different views that we support.

You can further personalize each component by adding attributes to the `<div>`:

- `data-amberflo-colors` – Set a HEX or color name to define the primary brand color. Defaults to Amberflo's color palette.
- `data-amberflo-font-size` – Set a base font size (e.g., 16). Component fonts will scale proportionally.
- `data-amberflo-font-family` – Provide a comma-separated list of fonts. The browser will use the first available font.
- `data-amberflo-font-custom` – Import a Google Font if it’s not already supported. This must be used together with data-amberflo-font-family.
- `data-amberflo-with-container` – "true" or "false". If "true", a container box is displayed around the widget.
- `data-amberflo-with-title` – "true" or "false". If "true", the widget title is shown (container must also be enabled).
- `data-amberflo-title` – Set a custom title for the widget. If omitted, the default title is used.

All customization attributes are optional. If not specified, Amberflo’s default design system will be applied.

```html
<div
  class="amberflo"
  data-amberflo-type="usage-table"
  data-amberflo-colors="#1565c0"
  data-amberflo-font-size="16px"
  data-amberflo-font-family="Roboto, Helvetica, Arial, serif"
  data-amberflo-font-custom="https://fonts.googleapis.com/css2?family=DynaPuff:wght@400;500&family=Great+Vibes&family=Rubik&display=swap"
  data-amberflo-with-container="true"
  data-amberflo-with-title="true"
  data-amberflo-title="Custom Title"
></div>
```

Some views may require additional attributes depending on the data they display. These attributes can be updated programmatically at any time to render a different view or change the current state.
&#x20;For example, you can initially display a **Usage Table** for one meter and then update the view dynamically—such as when a user clicks a button—to show data for a different meter without reloading the page.

## Get Session Token

To interact with Amberflo—whether through the API or the AmberfloSDK—you must first create a session token.

The recommended approach is to retrieve this token from a backend endpoint on your server using the browser’s `fetch` function on the client side. This method works particularly well for single-page applications, especially those built with modern frontend frameworks like React.

For more information check out our [Create a session API](docId\:p_45dCxEKUOsbG4vBhz06)&#x20;

This example shows how to create the **server endpoint** that serves the client secret:

```javascript
const express = require('express');
const app = express();
const request = require('request');

app.get('/amberflo-session', async (req, res) => {
  request({
    method: 'POST',
    uri: 'http://app.amberflo.io/session',
    body: {
      "customerId": "{{DESIRED_CUSTOMER_ID}},
      "expirationEpochMilliSeconds": {{EXPIRATION_DATE_IN_MILLISECONDS}}
    },
    headers: {
      "X-API-KEY": "{{YOUR_API_KEY}}"
    }
  }, function (error, response, body) {
        if (!error && response.statusCode == 200) {
            res.json({ sessionToken: response.sessionToken })
        }
    }
  );
})
```

📘 The above example is in Node.js.

You can get `X-API-KEY` from the Amberflo site inside **Settings -> Account  Configurations -> API keys.**

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/rm-xYN1uDJd28ogS3t9n8_image.png)

You can also get the `customerId` from Amberflo site inside **Customers → Click on one customer → Copy Customer ID.**

![](https://files.readme.io/ec76595-customer-id.png)

This example demonstrate how to **fetch the session token** with JavaScript in the client side:

```javascript
const response = fetch('/amberflo-session').then(function(response) {
  return response.json();
}).then(function(responseJSON) {
  const sessionToken = responseJson.sessionToken
})
```

## Using an Event

As you explore the [Available Views](docId\:kIPKru3tyXh4axofLKCFG) , you’ll notice that some of them support events. For example, the Usage Table view includes a `datechange` event that fires when the date is changed using the date picker.

These events give you the flexibility to trigger additional actions in your app whenever a supported interaction occurs within an Amberflo view.

Here is an example of how to use an event using the `datechange` event with the Usage Table view:

```html
<div
  id="usage-table"
  class="amberflo"
  data-amberflo-type="usage-table"
  data-amberflo-meters="SampleMeter"
></div>

<script>
  window.onload = () => {
    after setting up your session token...
    document
      .getElementById("usage-table")
      .addEventListener("datechange", (event) => {
        console.log(event.details);
        // { startDate: 1670112000000, endDate: 1672779539731 }
      });
  };
</script>
```

## Invoice-related Options

Several views in Amberflo—such as the Invoices List and Invoice Details—display invoice-related data. You can customize how this data appears by adjusting settings under [Amberflo → Settings → Invoice](https://ui.amberflo.io/settings/invoice).

There are currently two configurable options:

**Displaying cached invoices**
&#x20;You can choose whether the data shown in invoice views is retrieved in real-time or from a cached version. Using cached data helps improve performance and speeds up invoice rendering.

**Grouping product items on invoices**
&#x20;Amberflo allows you to organize product items into categories. These categories appear on the invoice to make it easier to understand grouped usage and charges.

## SDK Versions



***📘&#x20;***&#x52;eleases

***01/19/2024 - Version 1.0.30***
***01/16/2024 - Version 1.0.29***
***01/10/2024 - Version 1.0.28***
***08/04/2023 - Version 1.0.27***
***08/04/2023 - Version 1.0.26***
***07/12/2023 - Version 1.0.25***
***02/06/2023 - Version 1.0.24***
***01/09/2023 - Version 1.0.23***
***12/27/2022 - Version 1.0.22***
***12/20/2022 - Version 1.0.21***
***12/15/2022 - Version 1.0.20***
***12/08/2022 - Version 1.0.19***
***11/24/2022 - Version 1.0.18***
***10/20/2022 - Version 1.0.17***
***10/12/2022 - Version 1.0.16***
***10/04/2022 - Version 1.0.15***
***09/14/2022 - Version 1.0.14***
***09/12/2022 - Version 1.0.13***
***09/01/2022 - Version 1.0.12***
***08/29/2022 - Version 1.0.11***
***07/29/2022 - Version 1.0.10***
***07/12/2022 - Version 1.0.9***
***07/07/2022 - Version 1.0.8***
***07/06/2022 - Version 1.0.7***
***06/07/2022 - Version 1.0.6***
***05/09/2022 - Version 1.0.5***
***05/07/2022 - Version 1.0.4***
***04/26/2022 - Version 1.0.3***
***04/21/2022 - Version 1.0.2***
***04/20/2022 - Version 1.0.1***
***04/13/2022 - Version 1.0.0***

## Available Views

### Usage Table



![](https://files.readme.io/e6b46f9-usage-table.png)

| **Attribute**        | **Value**                                                                                                                                                                                                                                 |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| data-amberflo-type   | usage-table                                                                                                                                                                                                                               |
| data-amberflo-meters | Comma separated meters apiNames you want to see on the table.<br />e.g.:<br />ApiCalls,cluster\_count,cluster\_instance\_hours,cluster\_partitions,cluster\_reads,cluster\_storage,cluster\_writes,CpuHours,DataVolumeGb,number\_of\_runs |
|                      |                                                                                                                                                                                                                                           |

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/fHT2oSUhBLM2l8_Phnkwp_image.png)

Example:

```html
<div
  class="amberflo"
  data-amberflo-type="usage-table"
  data-amberflo-meters="ApiCalls,cluster_count,cluster_instance_hours,cluster_partitions,cluster_reads,cluster_storage,cluster_writes,CpuHours,DataVolumeGb,number_of_runs"
></div>
```

### Usage Graph



![](https://files.readme.io/60337b2-usage-graph.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/OeES2aKn6aa0oX355DxgS_image.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/KTnjGpOt5Jy3R49dj4wOm_image.png)

Example

```html
<div
  class="amberflo"
  data-amberflo-type="usage-graph"
  data-amberflo-meter="ApiCalls"
></div>
```

### Cost Table

![](https://files.readme.io/63235d9-cost-table.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/z9_oZj-Ju7Vft8hZj2MsX_image.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/hluQclUJ0HspjR2Ykbfow_image.png)

Example

```html
<div
  class="amberflo"
  data-amberflo-type="cost-table"
></div>
```

### Invoice Details

![](https://files.readme.io/bbc7154-invoice-details.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/GIcUPRjoEZJeqPzGrc4fT_image.png)

Example

```html
<div
  class="amberflo"
  data-amberflo-type="invoice-details"
  data-amberflo-invoice-date="1/9/2022"
></div>
```

### Invoices List

![](https://files.readme.io/29169e9-invoices-list.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/xVzCLYc_9qM3KDO2_Z5Xk_image.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/Ww2POYyU9WxYzL0cDwe3j_image.png)

Example

```html
<div
  class="amberflo"
  data-amberflo-type="invoices-list"
></div>
```

### Orders Usage Graph and Table

![](https://files.readme.io/2c9c52c-Screenshot_2024-01-10_at_1.05.51_PM.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/4nafn-tv4u-cKCl8NUGV2_image.png)

Example

```html
<div
  class="amberflo"
  data-amberflo-type="order-usage-graph-table"
></div>
```

### Prepaid Summary

![](https://files.readme.io/160bc79-image.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/SN61eYlmgr4A-c0DKxd_I_image.png)

Example

```html
<div
  class="amberflo"
  data-amberflo-type="prepaid-summary"
></div>
```

### Prepaid Wallet

![](https://files.readme.io/dad7af7-prepaid-wallet.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/2eBn5gzfOy2JRpdEgs0wa_image.png)

Example

```html
<div
  class="amberflo"
  data-amberflo-type="prepaid-table"
></div>
```

### Stripe Payment Method

![](https://files.readme.io/d9ca44d-Screen_Shot_2023-01-06_at_4.50.00_PM.png)



![](https://files.readme.io/40d4a6c-Screen_Shot_2023-01-06_at_4.50.09_PM.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/U4Vmk5nTxMreGoKzCGUVi_image.png)

```html
<div
  class="amberflo"
  data-amberflo-type="stripe-payment-method"
  data-amberflo-publishable-key="pk_test_62MIq3eFMMJsb2ZF6ivl10ycy6kVNhPau9QnrwJE649HGEEz3vZNDo5XA5I6ZdHRosbjGq8eAGPBrE0wrl7Q0Fxn1i00CaNu0Ar"
  data-amberflo-button-text="Submit"
  data-amberflo-return-url="https://mysite.com/stripe"
  data-amberflo-colors="#1565c0"
></div>
```

### Notes

Ensure that the page specified in `data-amberflo-return-url` includes the Amberflo script and properly initializes the SDK, just like the page where the component is originally hosted. This ensures continuity in rendering and functionality when the user is redirected.

Add the script:

```html
<script
  type="module"
  src="https://js.amberflo.io/index.1.0.24.js"
></script>
```

And make sure you instantiate it:

```javascript
<script>
  window.onload = () => {
    amberflo = new AmberfloSDK("{{YOUR_SESSION_TOKEN}}");
  };
</script>
```

### Pricing Plan

![](https://files.readme.io/cd62666-pricing.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/4c9JCf-V1Igy-u4yT0ZOP_image.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/SARgihTaRTXNw-FXzBXn5_image.png)

```html
<div
  class="amberflo"
  data-amberflo-type="pricing-plan"
  data-amberflo-pricing-plan="65ad692f-b7ae-4886-ab8a-3d0146d0817d"
  data-amberflo-title="Plan Name"
  data-amberflo-product-item-lines="30$ / GB,Custom  Text"
  data-amberflo-fixed-price="true"
  data-amberflo-price="$40 Fixed Price"
  data-amberflo-colors="#0078D4"
></div>
```

### Included Units

![](https://files.readme.io/cdad9cb-screenshot-localhost_3000-2022.12.27-12_37_16.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/xdN_pC86GceRQshpeeNr5_image.png)

### Order List

![](https://files.readme.io/1da89dd-image.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/rG7Snu2WlulTcJEB2U2rs_image.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/PoRAx-64JxxvikCvhWsOQ_image.png)

Example:

```html
<div
  id="orderList"
  class="amberflo"
  data-amberflo-type="order-list"
></div>

<script>
  document.getElementById("orderList").addEventListener("addorder", (event => {
    // Do something when add order button is pressed. For instance:
    window.location.href = `/add-order/${event.detail}`
  });
                                                        
  document.getElementById("orderList").addEventListener("rowclick", (event => {
    // Do something when an order row is clicked. For instance:
    window.location.href = `/order-details/${event.detail.id}`
  });
</script>
```

### Order Details

![](https://files.readme.io/4840691-image.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/W8ljm3NPjzvlmp1QQGw7F_image.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/7tLQMOzwvg4765hPCDeSM_image.png)

```html
<div
  id="orderDetails"
  class="amberflo"
  data-amberflo-type="order-details"
  data-amberflo-order-id="65ad692f-b7ae-4886-ab8a-3d0146d0817d"
></div>

<script>
  document.getElementById("orderDetails").addEventListener("back", (event => {
    // Do something when back button is pressed. For instance:
    window.location.href = "/order-list"
  });
</script>
```

### Order Create

![](https://files.readme.io/c2e3e5b-image.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/FYMUMWKqPuBN4y7QRqzdk_image.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/tIRuCtdpbn_sfQSmLfwqe_image.png)

```html
<div
  id="orderCreate"
  class="amberflo"
  data-amberflo-type="order-create"
  data-amberflo-product-item-id="65ad692f-b7ae-4886-ab8a-3d0146d0817d"
></div>

<script>
  document.getElementById("orderCreate").addEventListener("back", (event => {
    // Do something when back button is pressed. For instance:
    window.location.href = "/order-list"
  });
                                                          
   document.getElementById("orderCreate").addEventListener("cancel", (event => {
    // Do something when cancel button is pressed. For instance:
    window.location.href = "/order-list"
  });
</script>
```

### Order Usage Graph and Table

![](https://files.readme.io/a6fa09f-image.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/CSHs3C5xK3RS9uE5XDHkt_image.png)

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/hUFJzFpqYTYLWzyQyp-3-_image.png)

```html
<div
  class="amberflo"
  data-amberflo-type="order-usage-graph-table"
></div>
```

## How to get the Stripe publishable key

- Go to to [stripe.com ](https://stripe.com/)and login with your username and password
- Once inside Stripe, go to [Dashboard](https://dashboard.stripe.com/)
- Inside the dashboard copy the publishable key. Check the image below for reference:

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/iWqWbUJcO1zMXugEnGB7W_image.png)

**Appendix 1: Full Code Example**

```html
<html>
    <head>
        <script id="amberflo-script" type="module" src="https://js.amberflo.io/index.1.0.24.js"></script>
        <script>
            window.onload = () => {
                // YOU SHOULD NOT RETRIEVE A SESSION TOKEN LIKE THIS IN YOUR CLIENT-SIDE CODE.
                // THIS IS ONLY FOR DEMONSTRATION PURPOSES TO TRY OUT A QUICK WORKING EXAMPLE.
                // Please see this section (https://docs.amberflo.io/docs/uikit-sdk#get-session-token)
                // for more information on how to provide a session token to your client.
                fetch("https://app.amberflo.io/session", {
                    method: "POST",
                    body: JSON.stringify({
                        // replace customer123 with an actual customer id
                        customerId: "customer123",
                    }),
                    headers: {
                        // YOU SHOULD NOT HAVE YOUR API KEY IN YOUR CLIENT SIDE CODE.
                        // THIS IS ONLY FOR DEMONSTRATION PURPOSES TO TRY OUT A QUICK WORKING EXAMPLE.
                        // replace your_api_key with your account's API key.
                        // Please see this section for more info.
                        // (https://docs.amberflo.io/docs/uikit-sdk#get-session-token)
                        "x-api-key": "your_api_key"
                    },
                })
                .then((response) => response.json())
                .then((responseJson) => {
                    amberflo = new AmberfloSDK(responseJson.sessionToken);

                    // This is an example of using an event.
                    // This will log the date info when changing the date on the usage graph using the date picker.
                    document
                    .getElementById("usage-graph")
                    .addEventListener("datechange", (event) => {
                        console.log(event.detail);
                        // { startDate: 1674000000000, endDate: 1676603751569 }
                    });
                });
            };
        </script>
    </head>

    <body>
        <!-- replace meter-api-name with actual meter API Name -->
        <div
            id="usage-graph"
            class="amberflo meter"
            data-amberflo-type="usage-graph"
            data-amberflo-meter="meter-api-name"
        ></div>
        <div
            id="usage-table"
            class="amberflo"
            data-amberflo-type="usage-table"
            data-amberflo-with-container="true"
            data-amberflo-with-title="true"
            data-amberflo-title="Usage Table"
        ></div>
    </body>
</html>
```


[title] AWS S3
[path] Metering/Ingestion Options/

# Write events directly to an Amberflo supplied and secured AWS S3 bucket

Amberflo provisions an **AWS S3 bucket** for your account with the necessary **access rights and permissions** to write meter data.

- **Meter files are automatically picked up** for processing as soon as they arrive in the bucket.
- If processing fails, **error reports are written back** into the same bucket for review.

There are several ways to write data to S3, including:

- **AWS S3 SDK**
- **AWS Glue**
- **Logstash**
- **Fluentd**
- And other ingestion tools

To get your S3 bucket provisioned, please contact us.

## Format

The **meter records** you send to the S3 bucket should be of the same standardized format as accepted by the ingest meter API  Here are some examples:

```json
[{
     "customerId": "customer-123",
     "meterApiName": "ComputeHours",
     "meterValue": 5,
     "meterTimeInMillis": 1619445706909,
     "dimensions": {
        "region": "us-west-2",
        "az": "az1"
     }
}]
```

We also support [NDJSON](https://github.com/ndjson/ndjson-spec) format (JSON separated by a newline)

```json
{ "customerId": "customer-123", "meterApiName": "ComputeHours", "meterValue": 5, "meterTimeInMillis": 1619445706909 }
{ "customerId": "customer-321", "meterApiName": "ComputeHours", "meterValue": 4, "meterTimeInMillis": 1619445712341 }
{ "customerId": "customer-123", "meterApiName": "ComputeHours", "meterValue": 1, "meterTimeInMillis": 1619445783456 }
```

## Code Example

The S3 object key should have the date to allow easier troubleshooting (e.g.: `/ingest/amberdata/06-07-2022/0000a2e4-e6ad-11ec-8293-6a8da1c4f9f0`);

```python
import json
from uuid import uuid1
from datetime import date

import boto3

records_to_send = [{
     'customerId': 'customer-123',
     'meterApiName': 'ComputeHours',
     'meterValue': 5,
     'meterTimeInMillis': 1619445706909,
     'dimensions': {
        'region': 'us-west-2',
        'az': 'az1'
     }
}]

bucket_name = '183-amberflo'

date = datetime.now().strftime(r'%m-%d-%Y')

object_key = 'ingest/amberdata/' + date + '/' + str(uuid1())

s3 = boto3.resource('s3')

s3.Object(bucket_name, object_key).put(Body=json.dumps(records_to_send), ACL='bucket-owner-full-control')
```

## Troubleshooting

If you encounter issues while ingesting data through S3, Amberflo will generate a **failure report&#x20;**&#x69;n the following S3 path:

`s3://<bucket_name>/failed-requests/<date>/<original uri>.reason.txt`

This file contains the reason for the failure and can help you diagnose and resolve the issue.

## Compression (gzip support)

Amberflo supports ingesting **gzip-compressed files**.

- Simply upload files with the **.gz extension**
- Amberflo will **automatically decompress** these files during ingestion


[title] Meter Rates (Cost Calculation)
[path] Cost Tracking/

Meter rates define how usage is converted into **cost**.

This page explains how to configure cost rates on a meter so that all incoming usage can be translated into accurate spend.

This assumes you have already created one or more meters.

## **How Meter Rates Work**

When usage is ingested into a meter, Amberflo:

1. Matches the usage to a **meter**
2. Applies a **rate** defined on that meter
3. Calculates the resulting **cost**

The rate is the rule that determines how each unit of usage is valued.

Rates can change over time. When they do, you create a new rate configuration and apply it going forward.

## **Navigate to Meter Rates**

1. Go to **Cost → Configure → Meters**
2. Select the meter you want to configure
3. On the meter details page, click the **Rate** tab (middle tab)
4. Click **Create Meter Rate** (top right)

## **Create a Meter Rate**

### **Basic Fields**

- **Label**
  A name for the rate configurationSince rates may change over time, use a label that reflects the version or context.
  Examples:
  - OpenAI Pricing - Jan 2026
  - v1 Cost Rates
  - GPT Model Pricing Q1
- **Description (optional)**
  Add context about what this rate represents

## **Choose a Rate Model**

Amberflo supports two primary models for cost calculation.

### **1. Per Unit**

Each unit of usage has the same cost.

**Example:**

- Every API call costs $0.005
- Every job execution costs $0.10

This model is appropriate when cost does not vary based on context.

### **2. Per Unit with Dimensions (Recommended)**

Cost varies based on attributes of the usage.

This is the standard approach for most real-world systems.

**Why Use Dimensions**

In many systems, cost is not uniform. It depends on factors such as:

- Type of operation
- Resource used
- Configuration of the request

For example, with token-based usage:

- **Input tokens** and **output tokens** have different costs
- Different **models** have different pricing

These differences are captured using dimensions.

## **Build the Rate Table**

When using **Per Unit with Dimensions**, you define a table of rates.

- Each **column** represents a dimension
- Each **row** represents a unique combination of dimension values
- Each row has a **per-unit cost**

### **Example Structure**

If your meter includes:

- model
- token\_type

Your table might include rows like:

- model = A, token\_type = input → cost X
- model = A, token\_type = output → cost Y
- model = B, token\_type = input → cost Z

Each combination can have a different cost.

## **Configuring the Table**

- Remove any dimensions that are not relevant to cost calculation
  (for example, workload is typically used for attribution, not pricing)
- Use **Add Row** to define new combinations
- Use **Add Column** if you need to include additional dimensions

For each row:

1. Set the dimension values
2. Enter the corresponding **cost per unit**

## **Automation Options**

You do not need to configure rates manually in the UI.

Amberflo supports:

- **API-based configuration**
- **CSV import**

This allows you to:

- Sync rates from external pricing sources
- Update rates programmatically
- Manage large pricing tables efficiently

## **Saving and Applying Rates**

Once your rate configuration is complete:

- Click **Save**

From that point forward:

- All new usage events for that meter will use this rate configuration
- Cost will be calculated automatically based on the defined rules

## **Updating Rates Over Time**

Rates change. When they do:

1. Create a **new meter rate**
2. Use a clear label to indicate the new version
3. Apply it going forward

Do not overwrite previous configurations if you need historical accuracy.

## **Summary**

Meter rates are the foundation of cost calculation in Amberflo.

They:

- Define how usage translates into cost
- Support both simple and complex pricing logic
- Allow variation based on dimensions
- Can be managed manually or programmatically

Accurate rates ensure that your cost data reflects the real behavior of your systems.

[title] Billing for LLM tokens
[path] Common Use Cases/

This guide shows how to bill customers for an AI chatbot product using:

- **API calls (per request)**
- **LLM tokens (input + output, priced differently by model)**

This is a real-world billing pattern used by most AI products.

By the end, you will have:

- Multiple meters (API calls + tokens)
- A pricing plan with **multiple rate types**
- Dimensional pricing (model + token type)
- A customer receiving a fully broken-down invoice

## **The Scenario**

You are building an **AI chatbot SaaS**.

Your pricing model:

- $0.01 per API request
- Token usage billed based on:
  - **Model** (Model A vs Model B)
  - **Token type** (input vs output)

This reflects how real AI systems are priced.

## **What You’ll Build**

You will implement:

:::BlockQuote
API Request → Flat charge &#x20;
LLM Tokens → Dimensional pricing (model + type) &#x20;
→ Combined into a single invoice
:::

## **Step 1: Create Meters**

You need **two meters**.

### **1A. API Calls Meter (Simple)**

### **Navigate**

- Go to **Meters**
- Click **Create Meter**

### **Configure**

- **Label**: API Calls
- **API Name**: auto-generated
- **Meter Type**: Count

No dimensions required.

Note:
You *can* add dimensions (endpoint, region, etc.) for analytics or cost tracking, but they are not needed for billing here.

Click **Create**.

### **1B. LLM Tokens Meter (Dimensional)**

### **Navigate**

- Go to **Meters**
- Click **Create Meter**

### **Configure**

- **Label**: LLM Tokens
- **Meter Type**: Count

### **Add Dimensions**

Add:

- model
- type (input, output)

These are **required** for realistic AI pricing.

Click **Create**.

## **Step 2: Create a Pricing Plan**

### **Navigate**

- Go to **Pricing**
- Click **Create Pricing Plan**

### **Configure**

- **Name**: Chatbot Usage Plan
- **Billing Period**: Monthly

Click **Continue**.

## **Step 3: Add Rates**

You will add **two rates** to the same plan.

### **3A. API Calls Rate (Per Unit)**

- Click **Add Rate**
- Select **Usage-Based**

Configure:

- **Meter**: API Calls
- **Rate Model**: Per Unit
- **Price**: 0.01

This means:

- Every API request = $0.01

### **3B. LLM Tokens Rate (Dimensional)**

- Click **Add Rate** again
- Select **Usage-Based**

Configure:

- **Meter**: LLM Tokens
- **Rate Model**: Dimensions
- **Tier Model**: Per Unit

### **Define Pricing Matrix**

You will define rates based on:

- model
- type

Example:

| **model** | **type** | **price per token** |
| --------- | -------- | ------------------- |
| model\_a  | input    | 0.00001             |
| model\_a  | output   | 0.00002             |
| model\_b  | input    | 0.000005            |
| model\_b  | output   | 0.00001             |

Each row is a separate pricing rule.

### **Important**

- You are pricing **each combination independently**
- This is how real AI pricing works

Click **Save**.

## **Step 4: Create a Customer**

### **Navigate**

- Go to **Customers**
- Click **Create Customer**

### **Configure**

- **Customer Name**: Test Customer
- **Customer ID**: test\_customer

Click **Save**.

## **Step 5: Assign Pricing Plan**

- Open the customer
- In **Pricing Plans**, click **Assign Plan**

Configure:

- **Plan**: Chatbot Usage Plan
- **Start Date**: Now

Click **Assign Plan**.

## **Step 6: Send Usage**

Now you simulate real usage.

### **Navigate**

- Go to **Meters**
- Open **LLM Tokens**
- Click **Event Upload**

### **Example Event (Tokens)**

:::BlockQuote
\[
&#x20; \{
&#x20;   "meterApiName": "llm\_tokens",
&#x20;   "meterValue": 1000,
&#x20;   "dimensions": \{
&#x20;     "model": "model\_a",
&#x20;     "type": "input"
&#x20;   }
&#x20; }
]
:::

Send multiple events for different combinations:

- model\_a + input
- model\_a + output
- model\_b + input

### **API Calls Event**

Send another event using the API Calls meter:

:::BlockQuote
\[
&#x20; \{
&#x20;   "meterApiName": "api\_calls",
&#x20;   "meterValue": 10
&#x20; }
]
:::

## **Step 7: View the Invoice**

### **Navigate**

- Go to **Customers**
- Open your customer

### **What You’ll See**

Your invoice will include:

- API call charges
- Token usage charges

By default, token charges are broken down by:

- Model
- Token type

Example:

:::BlockQuote
Model A - Input Tokens → $X &#x20;
Model A - Output Tokens → $Y &#x20;
Model B - Input Tokens → $Z &#x20;
API Calls → $W &#x20;
Total → $T
:::

### **Important Note**

You **do not have to expose this level of detail** to your customers.

- This breakdown is the default
- You can simplify how invoices are presented

### **If You Don’t See Data**

- Refresh the page
- Amberflo does not auto-refresh

If still empty:

- Confirm events were sent
- Confirm dimensions match pricing
- Confirm plan is assigned

## **What You Just Built**

You implemented a real AI billing system:

- Multiple meters
- Mixed pricing models
- Dimensional pricing
- Unified invoice

## **Why This Matters**

This pattern lets you:

- Price different models differently
- Charge input vs output tokens separately
- Combine request-based and usage-based billing

This is how modern AI products monetize.

## **Next Steps**

- Add **tiered pricing** for tokens
- Introduce **discounts or credits**
- Connect to **cost tracking** for margin visibility

For internal cost attribution, see:
[Workloads](docId\:XToRHVsoI2AOK1BZPwfDU)


[title] Billing Overview
[path] Billing & Monetization/

Amberflow gives you a single platform to observe AI usage, understand true costs, and monetize AI-powered features. Know exactly what your AI usage costs you, what you charge customers, and how margins change as usage scales.

## Why It Matters

AI usage has real costs, and if you are offering AI-powered features to customers, those costs need to be recovered and monetized. Without a usage-aware billing system, teams either undercharge and absorb unpredictable AI costs, or overcharge with blunt pricing that discourages adoption.

Amberflow’s built-in rating and billing engine allows you to turn AI usage into revenue. By combining precise usage data, accurate cost information, and flexible pricing models, you can bill customers fairly, track margins in real time, and confidently scale AI-driven products.

This is how AI moves from a cost center to a revenue engine.

## How to Use It

Use Monetization when you want to charge customers based on how they actually consume AI-powered features.

### Define usage-based pricing plans

Amberflow allows you to create pricing plans that reflect how your product is used.

You can define plans based on:

- AI usage metrics such as requests or tokens
- Tiered or volume-based pricing
- Multiple dimensions, such as model, provider, or feature

This flexibility allows you to align pricing with customer value instead of relying on static seat-based models.

### Rate usage automatically

All incoming usage is passed through Amberflow’s rating engine.

The rating engine:

- Applies your defined pricing rules
- Calculates what each customer owes based on actual usage
- Accounts for discounts, promotions, or negotiated terms

This ensures that billing is consistent, auditable, and directly tied to real consumption.

### Integrate with Stripe for customer payments

With this integration, you can:

- Automatically generate usage-based charges
- Bill customers on a calendar-month basis
- Bill customers on a fixed monthly anniversary date

This supports both traditional SaaS billing models and modern usage-based billing approaches.

### Track revenue, cost, and margin

Because Amberflow already tracks the underlying cost of AI usage, monetization goes beyond invoicing.

You can:

- See how much revenue is generated from AI usage
- Compare revenue to underlying AI costs
- Understand profit margins at a granular level

This visibility allows you to price confidently and adjust plans based on real economics, not assumptions.

### Apply discounts and promotions

Pricing plans can include:

- Discounts for specific customers
- Promotional pricing
- Volume-based incentives

These adjustments flow through the same rating and billing pipeline, ensuring that promotions do not break reporting or margin analysis.

### Scale without manual reconciliation

All monetization flows from the same usage data used for observability, budgets, and cost controls.

This means:

- No duplicate tracking systems
- No manual reconciliation between usage, cost, and billing
- A single source of truth for AI consumption and revenue
- As usage grows, billing scales automatically with it.

## When to Use This Page

Use Monetization when:

- You are offering AI-powered features to external customers
- You want to charge based on real usage instead of fixed pricing
- You need visibility into revenue and profit margins for AI
- You want billing to scale with usage without operational overhead

This page shows how Amberflow helps you turn AI usage into sustainable revenue.

[title] Prometheus
[path] Metering/Ingestion Options/

# Ingesting Meters with Prometheus Remote Write

Amberflo supports ingesting metrics directly from Prometheus using its native remote write functionality. This enables real-time ingestion of usage data as meter events, allowing you to monitor and allocate infrastructure costs with precision.

This approach works particularly well for environments where Prometheus is already deployed, such as in Kubernetes clusters, but it is not limited to Kubernetes. You can send any relevant Prometheus metrics from your infrastructure or applications.

Why Use Prometheus for Meter Ingestion?

- **Low-lift integration:** No custom agent or Amberflo library required. Simply configure remote\_write in Prometheus.
- **Real-time metering:** Prometheus pushes events directly to Amberflo as they are generated.
- **Flexible attribution:** Use dimensions like namespace, node, tenant, or application to enable fine-grained usage tracking and cost allocation.
- **Custom metrics:** Supports ingestion of both built-in system metrics and any custom metrics sent to Prometheus from your IT environment.

### Example Use Case: Kubernetes

Prometheus is often used to monitor Kubernetes clusters. With Amberflo, you can ingest metrics such as:

- **Kube-state metrics:** For example, pod states, resource requests, and limits.
- **Node metrics:** CPU, memory, disk, and I/O usage from underlying nodes.
- **Kubelet metrics:** Container-level metrics like actual resource consumption.
- **Custom application metrics:** Data sent to Prometheus by in-cluster apps.

Amberflo uses the namespace label as the customerId by default, which allows for accurate cost allocation by namespace (or by other dimensions if preferred).

## To use Prometheus remote write with Amberflo, you need the following:

**You provide:**

- Any Prometheus-monitored system (e.g. a Kubernetes cluster)
- Prometheus deployed and collecting metrics
- Outbound internet access from Prometheus


**Amberflo provides:**

- An API to accept the remote write
- Ability to set custom ingestion schema for metric data

## Prometheus Remote Write Configuration

Below is an example configuration snippet to enable remote\_write in your Prometheus setup:

```yaml
remote_write:
- url: https://app.amberflo.io/ingest/prometheus
  name: amberflo
  write_relabel_configs:
  - source_labels: [__name__, pod]
    separator: ":"
    regex: 'kube_pod_info:coredns(.+)'
    action: keep
  - action: replace
    source_labels: [namespace]
    target_label: customerId
  - regex: 'customerId|node|pod|helm_sh_chart|__name__'
    action: labelkeep
  headers:
    x-api-key: XXX-XXX-XXX
```

This configuration:

- Keeps only relevant metrics based on name and pod.
- Maps the Prometheus namespace label to Amberflo’s customerId.
- Limits which labels are sent to Amberflo for ingestion.
- Authenticates using your Amberflo API key.


[title] Databases and JDBC
[path] Metering/Ingestion Options/

## Ingest Meters from a JDBC Enabled Source

Using metering data from an existing **JDBC-compatible source** is a common practice.
&#x20;Many systems support JDBC, including:

- **MongoDB**
- **Elasticsearch**
- **MySQL**
- **PostgreSQL**
- And many more

### **Options for Ingesting JDBC Data into Amberflo**

You can choose one of the following methods to ingest meter records from your JDBC source:

**1. Use Amberflo SDKs**

Write custom code using one of our **official SDKs** to extract data from your JDBC source and ingest it into Amberflo.

- SDKs are available in multiple languages (e.g., **Python**, **JavaScript**)
- Code samples are available to get you started quickly

[https://github.com/amberflo/metering-python-sample](https://github.com/amberflo/metering-python-sample)

**2. Use Logstash with the JDBC Plugin**

You can use **Logstash** and its **JDBC input plugin** to extract data from your JDBC source and upload it to **Amberflo's S3 bucket**.



**3. Use an ETL Tool**

Any **ETL tool&#x20;**&#x63;apable of pushing data into **S3** can be used.
Once data lands in your Amberflo S3 bucket, **Amberflo will automatically ingest it**.

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/qS6KlzOXZ5hYJz7OUD8p__image.png)


[title] Ingestion Options
[path] Metering/

A successful usage-based business model with metered billing is built on the foundation of an **accurate usage data collection strategy**.

Amberflo provides multiple easy ways to begin collecting data without the need to re-tool your systems.

## SDK

Using the Amberflo SDK gives you:

- More control
- Immediate feedback via response codes
- Support for batching records
- Flush-on-demand functionality

**Batching Records**
Amberflo SDKs are designed to support **high-throughput environments**.

You can confidently:

- Send **hundreds of meter records per second**
- Deploy the SDK on web servers handling **high volumes of traffic**
- Ensure accuracy and availability **from day one**

**How it works:**

Each call is **queued in memory**, not sent immediately over HTTP. Messages are **batched and flushed in the background**, allowing for much faster performance.

You can configure the **batch size&#x20;**&#x61;nd th&#x65;**&#x20;flush rate.**



**Flush on demand (Blocking call)**
You can **flush the queue manually**—for example, at the end of your program—to ensure no data is left unsent. Just call the flush method:

`metering.flush()`

**Important:**
This is a **blocking call**—it will pause the calling thread until the queue is empty.
We recommend using it only in **cleanup scripts**, not during request processing.

## Non-SDK

We **strongly recommend using our SDK for AWS S3**, as it offers the best of both worlds:

1. Access to **rich SDK features** (see above)
2. Guaranteed **AWS S3 uptime SLA** for meter ingestion

However, we fully support cases where using a third-party SDK isn’t possible or preferred. In such scenarios, Amberflo offers multiple alternatives:

**Amberflo Application**
The Amberflo web application provides a **rich set of real-time dashboards** for visualizing:

- **Usage**
- **Revenue**

Dashboards can be filtered by:

- **Customer**
- **Meter name**
- **Custom dimensions**

**Amberflo API**
Amberflo is an **API-first platform**. Every feature and object can be accessed programmatically via our APIs and SDKs.

**AWS S3**
Amberflo provides access to an **Amazon S3 bucket** with predefined **permissions and controls&#x20;**&#x74;o write meter data directly.

**Logging Systems**
Many companies already use **centralized logging infrastructure** (e.g., **AWS CloudWatch**) for operational insights.

While logging systems aren't built specifically for metering, they can serve as a **"catch-all"** for usage data streams because they’re deeply integrated into your systems.

Amberflo supports this approach by:

- Allowing you to **write meter events to your logging system**
- **Extracting** meter events from it using supported connectors

We currently provide a connector for **AWS CloudWatch** . We’re continuously adding **new out-of-the-box integrations**—check the **Integrations&#x20;**&#x73;ection for updates.&#x20;

If you have a specific request, please contact us.




[title] Files
[path] Metering/Meter Integration Options/

## Ingesting Meters from File Sources

Metering data is often generated and stored in **files** by other systems.

These files may be in formats such as:

- **CSV**
- **JSON**
- Other structured or semi-structured types

In many cases, the data needs to be **transformed** before it can be ingested into Amberflo’s metering system.


**Options for Ingesting File-Based Metering Data**

You have several methods available:

1. **Write code using our SDKs to:**
   Read data from files, Transform the data if needed and Send it to Amberflo for ingestion
2. **Use Logstash to:&#x20;**
   Parse and transform the file contents and Upload the transformed data to Amberflo's **S3** **bucket**.&#x20;
3. **Use an ETL Tool**
   Any **ETL tool** that can push data to **S3** is supported.
   Amberflo will automatically pick up the files and ingest them once they’re in your assigned bucket.


![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/cFyFaRDkEBkgxXTvoX9Mi_image.png)


[title] Meter Dimensions
[path] Metering/

# Dimensions

Amberflo’s Metering Service enables you to answer key questions in real time with precision: **What was used, by whom, when, where, and how much?**

Amberflo Meter Event data structure encapsulates this in the following way -

| What                                    | Meter name and API name                                                         |
| --------------------------------------- | ------------------------------------------------------------------------------- |
| Whom<br />When<br />How much<br />Where | Customer ID or Business Unit ID<br />Timestamp<br />Meter value<br />Dimensions |

To support this, you can add **dimensions** to your meter definitions. Dimensions are **custom key-value pairs** included with each meter event, allowing you to **enrich the event data with contextual metadata.**

## **What Are Dimensions?**

**Dimensions are the schemaless portion of a meter definition.** They provide a flexible way to customize the payload of a meter event without predefined constraints.

This allows you to capture and associate metadata such as:

- Where the usage originated (e.g., cloud provider, region, zone)
- Additional attribution beyond customer ID (e.g., cluster, project, department)
- Event state or status
- Any other business-specific markers

You are free to define any number of dimensions that reflect the context of your usage data.

**Important note**: If Dimensions are not defined ahead of time, new ones are automatically created upon ingestion.



## **How Dimensions Are Used**

**Grouping and Filtering**

Amberflo automatically groups and filters meter events by their dimension values. For example, if you define a dimension called cloud\_provider with values such as AWS, Azure, or Google, Amberflo will automatically sort, group, and make those values filterable in the UI and APIs.

**Pricing and D****iscount****s**

Dimensions are not only useful for analytics but also play a key role in constructing pricing plans and discount rules. You can use them to create pricing tiers, apply discounts, or define rate overrides based on specific dimension values.

**Usage Explorer**

Amberflo’s Usage Explorer provides real-time visibility into aggregated meter events, grouped and pivoted by dimension. This helps you analyze trends, drill into usage patterns, and segment your customers more effectively.

**Query API**

The Query API allows you to extract aggregated usage data by dimension, enabling integration with external tools or downstream billing systems.

Below are some common dimension types, but you are free to **create any dimensions specific to your use case** and domain:

1. Additional resource or application IDs
2. Department name
3. Cluster name or Project name
4. Cloud Region, Zone, etc.
5. Deployment environment
6. Customer type
7. Service version
   more...



### For Public Cloud and Department Chargebacks

```json
{
      "meter_name":"cloud_bedrock_input_tokens",
      "meter_value":1725,
      "time":1619445706909,
      "dimensions":{
         "business_unit_id":"us-east-2",
         "region":"us-east-2",
         "model":"anthropic_claude_3.7_sonnet"
     }
```

### For Customer Billing:

```json
{
      "customerId":"Wayne Enterprises, Inc.",
      "meter_name":"ApiCalls",
      "meter_value":575,
      "time":1619445706909,
      "dimensions":{
         "region":"us-east-2",
         "tenant_type":"Tech"
     }
```


[title] Untitled
[path] /


[title] AI Usage
[path] Cost Tracking/

## Why It Matters

AI costs do not grow randomly. They grow because usage changes. More requests, different models, new teams, inefficient integrations, or silent failures all show up in usage data before they show up on a bill.

AI Usage gives you direct visibility into the activity behind your AI spend. Instead of guessing why costs increased, you can see exactly what changed, when it changed, and who or what is responsible.

This is the layer that connects financial reporting to real operational behavior. Without it, cost control is reactive. With it, cost control becomes predictable.

## How to Use It

Use this page when you want to understand what is actually happening with AI usage across your organization.

### Start with the overview

At the top of the page, you see:

- Total spend on AI activity for the selected time range
- The top resources driving spend
- The resources with the fastest-changing usage

This gives you immediate context. You should be able to tell quickly whether usage looks normal or if something needs investigation.

### Review active usage metrics

Below the overview, you see time-series data for AI usage metrics such as tokens, API calls, and errors.

By default:

- Only metrics with usage are shown
- The time range is set to the last seven days

This provides a fast operational snapshot without noise. You can optionally show metrics with no usage if you want to see everything that is available.

### Drill into a specific meter

When something stands out, open the detailed view for that meter. Click on the button in the upper right of the graph that has two arrows. This can be used to expand the graph into the full page and provide many ways to dig into the data.

From there, you can:

- Change the time range to focus on when usage changed
- Adjust how data is grouped over time (hourly, daily, weekly, monthly)
- Choose whether values are shown as totals, peaks, minimums, or averages

These controls help you distinguish between steady growth, short-lived spikes, and bursty behavior.

### Break usage down by Workload

Usage can be viewed:

- Combined across the organization
- Broken out by Workload

Workloads are flexible and can represent teams, applications, projects, pipelines, etc. You can also select only specific Workloads to compare, keeping the view focused.

This makes it easy to identify ownership and isolate where changes are coming from.

### Filter to isolate the cause

You can filter usage by dimensions such as model or provider.

For example:

- View usage for a single model only
- Compare behavior across providers
- Isolate production traffic from everything else

Filters apply consistently to both charts and tables, so the data always matches what you see.

### Identify top consumers

Alongside the charts, you can see which Business Units are responsible for the most usage or spend over the selected period.

This helps you quickly answer who is driving usage, not just how much is being used.

### Inspect and export the data

Every chart is backed by a table showing the exact data points used in the visualization.

When needed, you can:

- Export the data as CSV
- Save the current configuration as a reusable view

Saved views are useful for recurring reviews or investigations and they can be found under the "Saved Views" navigation link.

## When to Use This Page

Use AI Usage when:

- Spend increased and you need to explain why
- You want to catch abnormal usage early
- You need to understand which teams, models, or providers are driving activity
- You want operational answers before financial escalation

This is how you move from reacting to AI costs to understanding them.

[title] Outcome Based
[path] Common Use Cases/

This guide explains how to implement **outcome-based pricing**.

Instead of charging for underlying activity (API calls, tokens, etc.), you charge for a **business outcome**.

## **The Scenario**

You are building an **AI-powered customer support platform**.

Instead of billing for:

- API calls
- LLM tokens

You charge customers based on:

**Support tickets successfully resolved**

### **Pricing Model**

- $2.00 per support ticket resolved

Customers are charged based on results, not underlying usage.

## **Core Concept**

:::BlockQuote
System Activity (calls, tokens, etc.) &#x20;
→ Produces an Outcome &#x20;
→ Meter the Outcome &#x20;
→ Bill the Customer
:::

The key shift:

You are metering the **result**, not the individual steps that produce it.

## **Step 1: Define the Outcome**

Start by identifying the business event you want to charge for.

In this example:

- **Support Tickets Resolved**

This becomes your **meter**.

## **Step 2: Create the Meter**

### **Where to Configure**

- Go to **Meters**
- Click **Create Meter**

### **Configure**

- **Label**: Tickets Resolved
- **Meter Type**: Count

No dimensions required.

Each time a ticket is resolved, you will send:

- Meter value = 1

## **Step 3: Create a Pricing Plan**

### **Where to Configure**

- Go to **Pricing**
- Create a new pricing plan

### **Configure**

- **Name**: Support Outcome Plan
- **Billing Period**: Monthly

## **Step 4: Add a Usage-Based Rate**

### **Where to Configure**

- Add a **Usage-Based** rate

### **Configure**

- **Meter**: Tickets Resolved
- **Rate Model**: Per Unit
- **Rate**: $2.00

## **Step 5: Assign the Plan to a Customer**

### **Where to Configure**

- Go to **Customers**
- Assign the pricing plan to a customer

Once assigned, all metered outcomes will be billed.

## **Step 6: Sending Usage (Conceptual)**

Each time your system resolves a support ticket:

- Send a meter event with value = 1

If 50 tickets are resolved:

- Total usage = 50

## **Example Outcome**

If a customer resolves:

- 120 tickets in a month

### **Billing Calculation**

:::BlockQuote
120 tickets × $2.00 = $240
:::

## **Why Outcome-Based Pricing**

This model aligns pricing directly with **value delivered**.

Instead of charging for system activity:

- You charge for **what the customer actually cares about**

## **Important Distinction**

Outcome-based billing does **not replace** tracking underlying usage.

You should still track:

- API calls
- LLM tokens
- Other system activity

These are useful for:

- Cost tracking
- Optimization
- Internal visibility

For internal cost tracking and attribution, see:
[Workloads](docId\:XToRHVsoI2AOK1BZPwfDU)

[title] Prepaid Credits
[path] Billing & Monetization/Credits & Drawdowns/

Amberflo supports modern usage-based billing models such as **bulk-buy** and **wallet-based billing**, helping deliver predictability for both customers and internal accounting teams.

Customers can **pre-purchase Credits** by paying upfront for a specific amount of usage.
Each purchase adds a **Prepaid Credit Card** to the customer's **Wallet**, reflecting the available balance.

As usage occurs, charges are drawn down from the Wallet in real-time.

Invoices will still display the usage details and line item costs. However, no payment will be due on the invoice until the Wallet balance is fully depleted.

This approach allows for flexible, transparent billing while supporting prepayment and commitment-based pricing strategies.



![](https://files.readme.io/255cf92-prepaid-credits.png)



The screenshot below shows how Prepaid Credits are applied to the subtotal on an **Invoice.**



![](https://files.readme.io/9b9394f-Screen_Shot_2022-10-20_at_5.33.36_PM.png)

## Adding Prepaid Credits

To add prepaid credits to a customer’s account:

1. Navigate to the **Customers** tab.
2. Select the customer by clicking on their **name**.
3. Go to the **Credits** tab within the customer profile.
4. Click **Add Prepaid Credits**.

You’ll be presented with a form to define:

- The **amount of prepaid credits**
- Any **conditions** or **expiration rules** (if applicable)

Once saved, the prepaid credit amount will be added to the customer’s **Wallet**, and usage will begin drawing down from this balance in real time.
A screenshot of the screen to define the Prepaid Credit amount with any conditions is shown below:

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/4a1pVBIV-riulR8CrTUX0_screenshot-2024-11-05-at-25216-pm.png)

When adding prepaid credits, you'll need to provide the following details:

- **Transaction Name** – A descriptive name for easy reference
- **Number of Credits** – The total amount of prepaid credits to add
- **Charge Amount** (optional) – If left blank, a 1:1 credit-to-charge conversion will be applied by default

You can also choose the **transaction type**:

- **One-Time** – A single, non-recurring credit addition
- **Recurring** – Credits will be added on a regular schedule, based on the configured time frame

By selecting the checkbox **"Apply a Condition to Prepaid Credits"**, you can restrict the usage of these credits to:

- A specific meter
- A particular dimension combination

This is useful for limiting prepaid credits to certain features, products, or customer behaviors.

### **Charging the Customer**

You must also select how the customer will be billed for this transaction:

- **Now** – If your Stripe account is connected and the customer's payment information is available, the charge will be processed immediately.
- **Reconcile Later** – If Stripe is not configured or you prefer to charge manually at a later time

If Reconcile Later is selected, the transaction will be saved as **pending**.

### **Post-Save Behavior**

Once saved, return to the **Customer Profile** view.
You’ll see the pending prepaid credit transaction marked with a **"Requires Action"** tag, indicating that follow-up is needed to complete the charge.

See the screenshot below:

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/jrCKPrge70BbWjjwFlVwx_screenshot-2024-11-05-at-25501-pm.png)

Once payment is received in your third-party payment system, go back to the pending prepaid credit transaction and select **Activate**. You will be prompted to provide the **Transaction ID** as confirmation of the payment. This step is important, as Amberflo uses the provided information to reconcile the prepaid credit purchase.

You can also activate the transaction programmatically using the APIs. See [Update an external prepaid card status](docId:9KYpmkXiAcSXvOXE7gkzx)  for details.
After the activation is saved, the prepaid credits become available and will be automatically drawn down as usage is recorded.

## Credits Expiration

Amberflo automatically manages the balance of prepaid credits, deducting from it in real time based on billable usage. Each prepaid credit card includes an expiration date. Once that date is reached, any remaining credits on the card are no longer valid and will be removed from the available balance.

To configure or extend the expiration date for a specific prepaid credit card, go to the **Credits tab** within the customer’s profile. From there, open the details of the card in question and adjust the expiration settings as needed.

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/1e0Jov0IfdPeN5dVynqOk_screenshot-2024-11-05-at-25703-pm.png)

You can also configure the default expiration date for all purchased prepaid credit cards in the system. To do this, simply visit the **Credits&#x20;**&#x70;age under the **Settings** tab and configure the default duration of the prepaid credit cards.

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/6kCPOIzctmUYXQ7rXWRjG_screenshot-2024-11-05-at-25916-pm.png)


[title] Margin Dashboard
[path] Billing & Monetization/

The Margin Dashboard gives you a clear, real-time view of your business profitability by combining revenue and cost from the same underlying usage data. Because Amberflo meters usage once and applies both pricing and cost rates to those events, you can accurately track how much you earn, how much you spend, and how your margins evolve over time.

This eliminates the disconnect between billing systems and cost tracking tools, and provides a single source of truth for understanding unit economics across your AI-powered services.

## Why This Matters

For AI-driven products, costs are variable and tightly coupled to usage. Without visibility into both sides of the equation, it is difficult to answer critical questions:

- Are we profitable at the workload or product level?
- How are AI costs impacting our margins?
- Are costs growing faster than revenue?
- Where should we optimize pricing or usage?

The Margin Dashboard answers these questions by showing revenue, cost, and margin together in a unified view.

## Overview Cards

At the top of the page, you will see four summary cards based on the last 3 months of activity:

- **Revenue**: Total billed revenue across all customers.
- **Metered Cost**: Total cost calculated from metered usage. This includes AI usage such as tokens, as well as any other metered infrastructure or services.
- **Margin**: Revenue minus metered cost.
- **Margin %**: Margin expressed as a percentage of revenue.

These metrics provide a quick snapshot of overall business performance and profitability.

## Rolling 12-Month Trend

Below the summary cards, the dashboard displays a rolling 12-month trend that combines:

- Revenue
- Metered Cost
- Margin %

This view allows you to track how your business is evolving over time:

- Identify whether revenue growth is outpacing cost growth
- Monitor margin compression or expansion
- Spot trends driven by changes in usage, pricing, or infrastructure

By visualizing these metrics together, you can quickly understand the relationship between usage-driven costs and your billing model.

## How It Works

The Margin Dashboard is powered by Amberflo’s metering and rating engine:

1. Usage is captured through meter events (e.g., API calls, tokens, compute time)
2. Costs are calculated by applying cost rates to those events
3. Revenue is calculated by applying pricing plans to the same events
4. Margins are derived directly from these aligned datasets

Because cost and revenue are based on the same underlying usage, the resulting metrics are consistent, accurate, and real-time.

## Benefits

- Unified visibility into revenue, cost, and margin
- Real-time profitability tracking for AI and usage-based services
- Accurate cost attribution tied directly to usage
- Faster identification of margin risks and optimization opportunities
- A foundation for pricing strategy and financial planning

[title] Segment.io
[path] Metering/Meter Integration Options/

You can ingest events into Amberflo using **Segment.io**.
&#x20;Integration details can be found [here.](https://segment.com/catalog/integrations/amberflo/)

**What You Can Do**

With Segment's **Amberflo Destination**, you can:

- **Ingest meter events** using:
  - track
  - page
  - screen
- **Create customers** in Amberflo using:
  - identify

This integration provides a seamless way to bring your customer and usage data into Amberflo using Segment’s event tracking.


![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/IBShgycDwccAHK0VkBJVX_image.png)

## Getting Started

1. From the
2. Search for
3. Choose which
4. Go to the
5. Enter the “API Key” in the

## Supported methods

## Page

Create a meter called **page** in Amberflo.

&#x20;The ***page*** is ingested as meter with value 1.

```javascript
analytics.page({
  userId: "some_user_id", 
  name: "Home",
  properties: {
   	"title": "Welcome | Initech",
    "keywords": "paper,comedy",
    "search": "schrute farms",
    "referrer": "https://google.com",
    "path": "/home",
    "browser": "Chrome",
    "url": "https://segment.com"
  }
})
```

**Mappings:**

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/8XWBnMbkoRdY0Od_Px9NR_image.png)

**Amberflo webhook endpoint:**

```curl
curl --request POST \
     --url https: //app.amberflo.io/ingest \
     --header 'Accept: application/json' \
     --header 'Content-Type: application/json' \
     --header 'x-api-key: YOUR_API_KEY' \
     --data '
{
    "uniqueId": "messageId",
    "customerId": "some_user_id",
    "meterApiName": "page",
    "meterValue": 1.0,
    "meterTimeInMillis": 1619445706909,
    "dimensions": {
        "name": "Home",
        "title": "Welcome _ Initech",
        "keywords": "paper,comedy",
        "search": "schrute farms",
        "referrer": "https:__google.com",
        "path": "_home",
        "browser": "Chrome",
        "url": "https:__segment.com"
    }
}
```

## Screen

Create a meter called **screen** in Amberflo. The *screen* event is ingested as meter with value 1.

```javascript
analytics.screen({
  userId: "some_user_id", 
  name: "Home",
  properties: {
   	"title": "Welcome | Initech"
  }
})
```

**Mappings:**

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/vV5UwNZq2sWt63PNfXiqe_image.png)

**Amberflo webhook endpoint:**

```curl
curl --request POST \
     --url https://app.amberflo.io/ingest \
     --header 'Accept: application/json' \
     --header 'Content-Type: application/json' \
     --header 'x-api-key: YOUR_API_KEY' \
     --data '
{
     "uniqueId": "messageId",
     "customerId": "segmentUserId",
     "meterApiName": "screen",
     "meterValue": 1.0,
     "meterTimeInMillis": 1619445706909,
     "dimensions": {
       "name": "Home",
       "title": "Welcome _ Initech"
     }
}
'
```

## Identify

Identify creates a customer in Amberflo or updates if customer already exists. Segment's **userId** is required for a customer in Amberflo. If **userId** is not set, then the customer will not be created in Amberflo and the *identify* event will be discarded.

```javascript
analytics.identify({
  userId: "some_user_id", 
  traits: {
    "name": "John Doe",
    "email": "john.doe@email.com",
    "plan": "premium",
    "logins": 5,
    "phone": "650-769-3240",
    "username": "marakasina",
    "website": "example.com",
    "avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/scottkclark/128.jpg"
  }
})
```

**Mappings:**

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/jRaPQeUNyeakdxHFuNwav_image.png)

**Amberflo webhook endpoint:**

```curl
curl --request PUT \
     --url https://app.amberflo.io/customers \
     --header 'Accept: application/json' \
     --header 'Content-Type: application/json' \
     --header 'x-api-key: YOUR_API_KEY' \
     --data '
{
     "customerId": "XXXX-ZZZZZ",
     "customerName": "myCustomerName",
     "customerEmail" "john.doe@email.com",
     "traits": {
        "plan": "premium",
        "logins": 5,
        "phone": "650-769-3240",
        "username": "marakasina",
        "website": "example.com",
        "avatar": "https:__s3.amazonaws.com_uifaces_faces_twitter_scottkclark_128.jpg"
    }
}
'
```

## Track

Create a meter in Amberflo for the track *event* in Segment. The Segment track event is ingested as a meter with value of properties.value, or 1 if the value is not set.

```javascript
analytics.track({
  userId: "some_user_id", 
  event: "ApiCalls",
  properties: {
    "value" : 2.0
   	"region": "us-west-2"
  }
})
```

**Mappings:**

![](https://archbee-image-uploads.s3.amazonaws.com/GQ6FhONXoR2DQjp03hOcB/3ljwptfYaolRKPA-EbgEH_image.png)

**Amberflo webhook endpoint:**

```curl
curl --request POST \
     --url https://app.amberflo.io/ingest \
     --header 'Accept: application/json' \
     --header 'Content-Type: application/json' \
     --header 'x-api-key: YOUR_API_KEY' \
     --data '
{
     "uniqueId": "messageId",
     "customerId": "some_user_id",
     "meterApiName": "ApiCalls",
     "meterValue": 2.0,
     "meterTimeInMillis": 1619445706909,
     "dimensions": {
      	"region": "us-west-2"
     }
}
'
```

## Links

[Segment Catalog -> Amberflo](https://app.segment.com/amberflo-lior/destinations/catalog/amberflo)
[Segment Destination Documentation -> Amberflo](https://segment.com/docs/connections/destinations/catalog/amberflo/)
