# Event Ingestion API
Source: https://docs.runonatlas.com/advanced-atlas/event-ingestion-api
Start adding event ingestion to your application
## Endpoint Details
**URL**: [https://platform.runonatlas.com/external/event](https://platform.runonatlas.com/external/event)
**Method**: POST
**Authentication**: The API requires authentication using an API key.
* **Header**: x-api-key (Required)
* **Value**: Provided by Atlas OR one can be created in Settings > API Keys (Required)
## Request Parameters
* **eventName** (string): Name of the event being tracked
* **generatedAt** (string): ISO 8861 timestamp of event creation
* **customerId** (string): Unique identifier for the customer
## Optional Parameters
* **idempotencyKey** (string): Globally unique UUID to prevent duplicate event processing. Recommended for ensuring exactly-once event delivery. If not provided, Atlas will generate one based on event payload.
* **userId** (string): Unique identifier for the end user (the customer's user)
* **data** (object): Key-value pair for additional event metadata. Used to provide context for event identification and usage calculation. Can include any relevant information about the event.
## Example Request
```json theme={null}
curl --location --globoff '' \\
--header 'x-api-key: {{API_KEY}}' \\
--data '{
"eventName": "aiFeatureUsed",
"data": {"featureName": "dataExport"},
"generatedAt": "2024-10-25T00:00:00.000Z",
"customerId": "1e74c86e-62ce-4896-b466-e1dbae768e0b",
"userId": "7e63ad67-bae9-4c18-9ec7-33440f39afbf",
"idempotencyKey": "test-key"
}'
```
## Best Practices
* Always include an idempotencyKey
* Use meaningful event names
* Provide comprehensive data for accurate usage tracking
# Introduction
Source: https://docs.runonatlas.com/advanced-atlas/index
Heads up — this is our advanced setup.
Most teams won’t need this. But if you’re working with usage- or credit-based billing, this is where the magic happens.
## Overview
Atlas combines billing, usage data, and provisioning to provide a modern cockpit for product monetization. Our documentation is intended to be approachable and comprehensive, if something is not clear, or documentation is missing, please contact us.
Atlas accepts first and third party usage data to create a baseline of understanding of what users are doing in your application. Once eventing is configured, non technical resources can configure billable metrics entitlements and subscription plans for customers.
Entitlements can act as feature flags or directly integrate with existing feature flagging systems to allow basic provisioning and access gating.
Billable metrics can be turned into invoices based on configuration and delivered directly to your payment gateway for seamless collection.
Finally, our configuration CRM gateway allows you to bi-drectionally sync data to and from systems of record to ensure all data is in sync.
## Key Terms
Here are some important terms to know as you navigate Atlas.
* **Merchant**: Atlas customer - the entity that's using Atlas to bill their customers
* **Customer**: The customer of the merchant
* **User**: The individual who works at the customer
* **Atlas User**: The merchant user who is using the atlas app
* **Plan**: A bucket containing entitlements and/or metrics - most represent what the user is buying
* **Entitlement**: Static capability or feature, can be granular or high level. Can also include non software specific items that are on a contract, e.g. support hours.
* **Metric**: A set of events, filtered, and counted, summed, or averaged, etc. to be billed
* **Price**: Price set on an entitlement or billable metric
* **Event**: An event defined by our merchants that can be tied to a metric or feature
* **Price Type**: Defines price structure (one-time, tiered, capped, etc.)
* **Credits**: Pre-paid units that can be used to pay down invoices or unlock functionality
* **Subscription**: Ties a plan to a specific user/customer and invoice
* **Invoice**: What is billed to the customer. Contains terms and detailed line items.
* **Jobs Engine**: Nightly procedure of reconciling usage with subscriptions, visitors, and accounts. Generates invoices, retries, etc.
* **SDK**: Both client and server-side packages for calling Atlas API endpoints
## Entitlements
Atlas Entitlements are static capabilities or features that help define what the customer receives by purchasing a plan.
Entitlements can also be used for non-software-specific contractual items, e.g. professional support hours or consulting.
To create an entitlement, go to the entitlements page, and press the "Create entitlement" button.
Like billable metrics, entitlements can be tied to specific events. However, this is optional and is only used to represent overall usage. If you want to bill based on usage, use a billable metric.
If you choose to tie an entitlement to an event, you’ll be able to view low, medium, and high usage statistics.
Usage is helpful at determining which capabilities may be in the wrong plan, are under or over monetized, or should be deprecated.
Every entitlement auto-generates an `entitlement_key` that is returned via API and can be used in provisioning or feature flagging.
## Billable Metrics
Billable Metrics are designed for usage, action, and outcome-based pricing scenarios.
To create a new billable metric, go to the billable metric page and 'Create metric'.
To create a billable metric, you must have matching events flowing into Atlas.
In the "Create metric" modal, you’ll populate a Name and Description, choose if you want to show this metric on invoice, and build the query that matches this billable metric to a specific event type.
This UI allows non-technical resources to build and change billable metrics without reliance on engineering resources.
You can either use the UI rule builder or, for power users, you can write direct SQL using the Advanced SQL editor.
In either case, you can use the Run button to quickly check if events exist for your given query.
## Plans
Atlas Plans represent a collection of entitlements and billable metrics that a customer can purchase or subscribe to.
Entitlements and Billable Metrics must be created before you can add them to a Plan.
To create a Plan, go to plans page, and press the Create plan button.
Give the plan a Name, Description, Currency and Payment Terms, then press Save. You now have an empty plan ready to be populated.
The newly-created Plan will go into a 'Draft' state. This is to ensure changes can be made to the Plan without fear of the Plan being subscribed to by a customer.
Your newly created plan can be populated with entitlements and metrics by clicking the Add button on either.
When adding an entitlement or billable metric you’ll be asked to populate Billing Cadence, Price Type, and Price.
## Customers
There are three main ways to create customers in Atlas:
1. Upload a .csv file
2. Use our CRM integrations
3. Manually
To manually add a new customer, go to the customer page and select Add customer. Only the Name and Email fields are required.
Once a customer is created they can be linked to any available plan manually or via our SDK.
Clicking on a customer brings you to the customer detail page, which allows you to view and customize any active plan, and view and edit invoice details.
## Price Details
Atlas supports multiple price types in order to support complex billing scenarios.
* **Unit Pricing** - Charges are based on a fixed price per unit (e.g., \$10,000 platform fee / yr). This model is straightforward and can be used for many common basic billing scenarios.
* **Tiered Pricing** - Prices change based on quantity thresholds. For example, you might charge $.55 per user for the first 1000 AI tokens, and $.50 for the next 1000, and so on. This encourages higher volume usage with price breaks.
* **Package Pricing** - Allows bundling of multiple units into a package at a single price. For example, you may offer user accounts in packs of 10. If a customer needs 11 users, they will need to purchase an additional pack of 10, as billing is always done in increments of 10.
* **Bulk Pricing** - Also known as volume pricing, bulk pricing refers to a pricing strategy used to offer reduced prices or discounts to customers who purchase quantities in bulk. For example, you may charge 100$ for 50 units ($2/unit), but if you buy 200 units you charge $300 ($1.5/unit).
* **Matrix Pricing** - Matrix pricing uses multiple variables to calculate prices in a grid-like structure, offering flexibility for complex, customized pricing. For example, if you’re charging for API usage, you can set different prices based on the type of API response, allowing for more nuanced pricing.
Each of these models ensures you can align pricing with the value your customers receive, offering both flexibility and precision in billing.
# Payment Gateway Integration
Source: https://docs.runonatlas.com/advanced-atlas/payment-gateway-integration
Integrate Atlas with your payment gateway
Atlas Billing manages all plans, entitlements, billable metrics, and pricing, and is responsible for the automatic generation of invoices based on each customer’s current subscription. Once invoices are created, they must be sent for payment collection, which requires integration with a payment gateway.
Atlas currently supports two payment gateways: Stripe and QuickBooks. You can connect to either by navigating to Settings > Integrations.
The integration is bi-directional for payment statuses. Once a payment is received or if an invoice becomes overdue in your connected gateway, the corresponding status is automatically synced back to Atlas. This ensures that billing data and downstream workflows (for example, dunning or access controls) stay aligned with the actual payment status.
While payment status is synchronized, edits made directly to invoice line items or structure inside the payment gateway are not reflected back in Atlas. Atlas remains the system of record for invoice generation logic.
* QuickBooks uses OAuth for authentication. You’ll need administrator access to QuickBooks to complete the integration.
* Once connected, the integration will remain active unless manually disconnected.
* As invoices are created in Atlas, you can manually sync and review them in QuickBooks before sending to your customer.
* Atlas supports automatic email delivery of invoices if you enable the corresponding toggle in the integration settings screen.
## Stripe Integration
Atlas integrates with Stripe to help you manage customer invoicing and payments. To connect your Stripe account to Atlas, you’ll need to provide an API key—either a secret key or a restricted key.We recommend using a restricted key whenever possible. This approach aligns with our commitment to the principle of least privilege—a best practice in security that ensures systems only have access to the resources they absolutely need. Stripe API keys can be found in your [Stripe API Dashboard](https://dashboard.stripe.com/apikeys).
* Atlas only accepts test keys in sandbox mode and production keys in production mode.
* As invoices are created in Atlas, you can manually sync and review them in Stripe before sending to your customer.
* Atlas supports automatic email delivery of invoices if you enable the corresponding toggle in the integration settings screen.
### API Key Options
1. Restricted Key (Recommended)
* Access: Only to specific resources you select.
* Pros: More secure. Limits Atlas access to only the necessary parts of your Stripe account.
* Cons: Requires a few more steps to configure. You may need to update permissions in the future as Atlas adds new features.
2. Secret Key (Full Access)
* Access: Full access to your Stripe account.
* Pros: Easiest to set up - no configuration required, and no updates required if Atlas needs access to a new resource type.
* Cons: Gives Atlas access to all your Stripe resources, including ones we don’t use. Not ideal from a security standpoint.
### What Permissions Does Atlas Need?
When using a restricted key, Atlas currently needs write access to the following Stripe resources:
* Customers
* Invoices
* Webhook Endpoints
### How to Connect with a Restricted API Key in Stripe?
1. Go to your [Stripe API Keys](https://dashboard.stripe.com/apikeys) page.
2. Click **Create restricted key**.
3. Select **Providing this key to another website**.
4. Name the key (e.g., Atlas Integration).
5. Set the **URL** to [https://app.runonatlas.com/](https://app.runonatlas.com/)
6. Check the box for **Customize permissions for this key**.
7. In the permissions section, set the following to **Write** (All other permissions should be set to **None**):
* Customers
* Invoices
* Webhook Endpoints
8. Save the key and copy it.
9. Navigate back to the [Atlas application](https://app.runonatlas.com).
10. Select **Settings**.
11. Select the tab for **Integrations**.
12. Click the button for connecting to Stripe paste your key into the modal that opens, and click **Activate**.
Atlas will verify that the provided key has the correct permissions and save it securely for use when syncing invoice data to Stripe.
Be sure to store your key securely. Treat it like a password.
# Bulk upsert customers
Source: https://docs.runonatlas.com/api-reference/customers/bulk-upsert-customers
https://platform.runonatlas.com/external/schema post /external/customers/upsert/bulk
Bulk upsert customers into Atlas by unique external ID (e.g. the customer ID in your system). Does not automatically subscribe the customers to the default plan if eligible.
# Create customer with default plan subscription
Source: https://docs.runonatlas.com/api-reference/customers/create-customer-with-default-plan-subscription
https://platform.runonatlas.com/external/schema post /external/customers
Create a new customer with a unique external ID (e.g. the customer ID in your system). Also automatically subscribes the customer to the default plan if eligible.
# Create Stripe setup checkout session for payment method update
Source: https://docs.runonatlas.com/api-reference/customers/create-stripe-setup-checkout-session-for-payment-method-update
https://platform.runonatlas.com/external/schema post /external/customers/external-id/{externalId}/stripe/checkout-sessions/setup
Creates a new Stripe setup checkout session to update the payment method for a customer by external ID (e.g. the customer ID in your system). You must have a valid Stripe account connected to your Atlas merchant to use this endpoint. Redirect the customer to the URL returned in the response to update their payment method.
# Get custom pricing unit usage
Source: https://docs.runonatlas.com/api-reference/customers/get-custom-pricing-unit-usage
https://platform.runonatlas.com/external/schema get /external/customers/external-id/{externalId}/customPricingUnits/{customPricingUnitId}/usage
Get custom pricing unit usage for a customer by external ID (e.g. the customer ID in your system).
# Get customer by ID
Source: https://docs.runonatlas.com/api-reference/customers/get-customer-by-id
https://platform.runonatlas.com/external/schema get /external/customers/{id}
Get a customer by ID.
# Get customer transactions
Source: https://docs.runonatlas.com/api-reference/customers/get-customer-transactions
https://platform.runonatlas.com/external/schema get /external/customers/external-id/{externalId}/transactions
Get a paginated list of transactions for a customer by external ID (e.g. the customer ID in your system).
# Get customer with expanded data
Source: https://docs.runonatlas.com/api-reference/customers/get-customer-with-expanded-data
https://platform.runonatlas.com/external/schema get /external/customers/external-id/{externalId}/expanded
Get a customer by external ID with their active and upcoming subscriptions, custom pricing unit balances, and private plans.
# Get customers
Source: https://docs.runonatlas.com/api-reference/customers/get-customers
https://platform.runonatlas.com/external/schema get /external/customers
Get a paginated list of customers.
# Upsert customer
Source: https://docs.runonatlas.com/api-reference/customers/upsert-customer
https://platform.runonatlas.com/external/schema post /external/customers/upsert
Upsert customer by unique external ID (e.g. the customer ID in your system). Does not automatically subscribe the customer to the default plan if eligible.
# Ingest events
Source: https://docs.runonatlas.com/api-reference/events/ingest-events
https://platform.runonatlas.com/external/schema post /external/event/
Ingest events into Atlas. Events are processed asynchronously, so the response does not indicate when the events have been processed or that the data is correct. For example, if you send a customer ID that does not exist, this endpoint will accept it anyway.
# Get invoice with line items
Source: https://docs.runonatlas.com/api-reference/invoices/get-invoice-with-line-items
https://platform.runonatlas.com/external/schema get /external/invoices/{invoiceId}/with-line-items
Get an invoice with its line items attached, including expanded price and billable information.
# Cancel subscription
Source: https://docs.runonatlas.com/api-reference/subscriptions/cancel-subscription
https://platform.runonatlas.com/external/schema post /external/subscriptions/{id}/cancel
Cancel a subscription. For currently active subscriptions, subscription will be scheduled to end at end of current billing period. For upcoming subscriptions, subscription will be deleted.
# Create multiple subscriptions
Source: https://docs.runonatlas.com/api-reference/subscriptions/create-multiple-subscriptions
https://platform.runonatlas.com/external/schema post /external/subscriptions/bulk
Create multiple subscriptions.
# Create subscription
Source: https://docs.runonatlas.com/api-reference/subscriptions/create-subscription
https://platform.runonatlas.com/external/schema post /external/subscriptions
Create a subscription.
# Delete subscription
Source: https://docs.runonatlas.com/api-reference/subscriptions/delete-subscription
https://platform.runonatlas.com/external/schema delete /external/subscriptions/{id}
Delete a subscription. Currently active subscriptions cannot be deleted, they must be cancelled first.
# Get subscription by ID
Source: https://docs.runonatlas.com/api-reference/subscriptions/get-subscription-by-id
https://platform.runonatlas.com/external/schema get /external/subscriptions/{id}
Get a subscription by ID.
# Get subscription by ID with expanded data
Source: https://docs.runonatlas.com/api-reference/subscriptions/get-subscription-by-id-with-expanded-data
https://platform.runonatlas.com/external/schema get /external/subscriptions/{id}/expanded
Get a subscription by ID with expanded plan data including prices and billables.
# Get subscriptions
Source: https://docs.runonatlas.com/api-reference/subscriptions/get-subscriptions
https://platform.runonatlas.com/external/schema get /external/subscriptions
Get a paginated list of subscriptions.
# Renew cancelled subscription
Source: https://docs.runonatlas.com/api-reference/subscriptions/renew-cancelled-subscription
https://platform.runonatlas.com/external/schema post /external/subscriptions/{id}/renew
Renew a cancelled subscription by removing its end date. Requires externalCustomerId to verify the customer matches the subscription.
# Create a Pricing Model
Source: https://docs.runonatlas.com/create-a-pricing-model
Creating a pricing model with Atlas is fast, flexible, and backed by real market data.
### 1. Start a New Pricing Model
Click the “Create a new pricing model” button. Atlas will spin up our AI engine to:
* Analyze your product
* Research your competitors
* Surface relevant industry benchmarks
From there, you’ll get a set of pricing models to explore.
### 2. Describe Your Product
Help us understand what you’re building by choosing at least one of these options:
* Paste your marketing website URL
* Upload docs (like a pitch deck or product spec)
* Simply type out a description of your app
The more context you give, the better the recommendations.
### 3. Review the Analysis
Atlas will generate a breakdown of competitor pricing strategies and industry patterns. Use this to:
* Understand common models in your space
* Compare price points
* Get ideas for positioning your offering
### 4. Pick a Model to Try
Select one of the recommended pricing models to customize and launch. You’ll be able to tweak it further before going live.
# How does Atlas work?
Source: https://docs.runonatlas.com/developer-portal/how-does-atlas-work
Understand how to use Atlas to manage your billing and packaging.
## What problem does Atlas solve?
When you are building a SaaS application, one of the key problems you need to solve is **how to manage your billing and packaging**.
Not only you need to decide the right strategy, you also need to implement it in a way that allows you to scale and adapt to your
users' needs. You will not create the correct pricing and packaging strategy in your first iteration. Many pricing and packaging
experts would actually say that you will **never stop iterating** over it.
As a developer, this is a challenge. We need to somehow charge our clients and keep all the systems in sync.
But, how do we create a system that allows us to manage our billing and packaging in a way that enables us to iterate as much as we need?
How can we easily migrate from a flat features model to one that is more complex and usage-based?
**Atlas solves this problem.**
## How does Atlas solve it?
### Payment integrations
There are multiple solutions that you can use to charge your customers. However, all these providers offer a lot of complexity.
And it's not their fault, they serve an extremely big variety of customers, and each one of them needs something slightly different.
So, what do we do? **We integrate on top of them and make your journey painless**. Right now, we integrate with Stripe and we offer a
simple way to charge your customers. You don't need to worry about webhooks, payment processing failing later, or syncing everything.
**Our system works as a single source of truth.** Just using our frontend widgets and our clients in the backend,
the immediate and recurrent payments are automatically handled and in sync in all your systems.
### Single Source of truth
We have many widgets and utilities that will help you to manage your billing and packaging. However, if there is some case that we
don't cover or you need the pricing and packaging information for a specific use case, you can use our API to get the information.
**We have a single source of truth** for all your pricing and packaging information and you can use it to get the information for any
use case.
### Authentication
We are not reinventing the wheel. We use your authentication system to verify the requests: **you can use any authentication system you want**
and just tell us how to use it.
### Features at the core
Everything comes down to a single concept: **features**. Features are what you are selling to your users. They are the functionalities
that your users can access. Everything in your company relates to that. What are the best features? How do we offer them? How do we
price them? How do we limit them?
Atlas recognizes the importance of features and **puts them at the center**. It's simple: every time you develop a new feature, you
only need to register it. Atlas and the product team will take care of the rest.
How do we do this? For example, let's say that you have a bunch of endpoints in your application that are related to the "data exploration"
feature. Using Atlas, you simply need to register the feature in the endpoint and forget about it.
How is this done? For example, in Express.js, you can do the following:
```typescript {3} theme={null}
app.get(
"/data-exploration",
atlasExpress.createFeaturesMiddleware(["data-explorer"]),
(req, res) => {
res.send("Hello, world!");
}
);
```
If your product team wants to change who can access this feature, engineers don't care. Atlas will automatically start rejecting
(or accepting) access to users who don't have the feature enabled. **Simple.**
### Performant
All our libraries have **robust caching mechanisms** that will make your application performant. We also have caching invalidation
mechanisms that will make sure that your application is always up to date and in sync, even if you have horizontally scaled your application.
### Easy to use
All our libraries are designed to be easy to use. We have a lot of documentation and examples that will help you to get started.
Is something confusing? Reach out on our [Discord](https://discord.gg/runonatlas) and we will help you out.
# Express
Source: https://docs.runonatlas.com/developer-portal/sdk/express
`@runonatlas/express` is our Express.js library for Atlas, which provides all the middleware and backend resources you need.
## Installation
```bash Terminal theme={null}
npm install @runonatlas/express
```
```bash Terminal theme={null}
pnpm add @runonatlas/express
```
```bash Terminal theme={null}
yarn add @runonatlas/express
```
## Initialization
This only covers the backend initialization with Express. You will also need
to initialize your frontend. For example, [React](/developer-portal/sdk/react)
or [Next.js](/developer-portal/sdk/next).
Initializing the Atlas SDK is very easy. You just need to follow these steps:
You can get it from the [Atlas Dashboard](https://app.runonatlas.com).
```bash .env theme={null}
ATLAS_API_KEY="INSERT_YOUR_ATLAS_API_KEY"
```
To initialize the Atlas SDK, you need to instantiate the Atlas Client with the following parameters:
1. Your Atlas API key.
2. A function that returns the user id given a request object.
When using Clerk, you just need to pass the getAuth function to get the userId.
```typescript src/index.ts [expandable] {6-8,12} theme={null}
import { AtlasExpressClient } from "@runonatlas/express";
import { clerkMiddleware, getAuth } from "@clerk/express";
import express from "express";
const app = express();
const atlas = new AtlasExpressClient(
(req) => getAuth(req).userId
);
app.use(clerkMiddleware());
app.use("/", atlas.router);
```
You just need to pass a function that returns the userId from the request.
```typescript src/index.ts [expandable] {6-11} theme={null}
import { AtlasExpressClient } from "@runonatlas/express";
import express from "express";
import { getAuth } from "@clerk/express";
const app = express();
const atlas = new AtlasExpressClient(
(req) => getAuth(req).userId
);
// ... Remaining code
```
If you are having CORS issues only with Atlas, it probably means that your CORS middleware is running after the Atlas server. Make sure to place the CORS middleware before the Atlas server. For example:
```typescript src/index.ts [expandable] {11} theme={null}
import { AtlasExpressClient } from "@runonatlas/express";
import express from "express";
import { clerkMiddleware, getAuth } from "@clerk/express";
import cors from "cors";
import corsOptions from "./corsOptions";
const app = express();
const atlas = new AtlasExpressClient((req) => getAuth(req).userId);
app.use(cors(corsOptions));
app.use(clerkMiddleware());
app.use("/", atlas.router);
```
## Restrict user access based on their subscriptions
You can prevent users from accessing restricted parts of your application based on their subscription. To do so, you can use our backend protection features.
### Backend Protection
In order to prevent users from using certain endpoints unless they have a plan with the required features, we only need to add a middleware.
```typescript {3} theme={null}
app.post(
"/api/generate-caption",
atlas.createFeaturesMiddleware(["data-explorer"]), // Add this line
(req, res) => {
// Your endpoint logic here
}
);
```
This will automatically prevent users who don't have the data-explorer feature in their active plan from accessing the endpoint.
Remember that you need to have followed the [installation
guide](#installation) before you can use any of these features.
## Limit-based features
Sometimes, just having a feature as enabled or disabled is not enough, and our pricing models require limits to be set. For example, 5 users per account, or 20GB of storage.
Setting this up with Atlas is very easy. And, if at some point the limits change, you won't need to change the code again!
### Configuring the backend
The backend needs to understand what the limits are and how to check if the limit has been reached. To do so, when initializing the Atlas Client, you can provide a `limits` object with callbacks per each limit that you might want to use.
For example, given a feature whose id is `data-explorer`, you can provide a callback to check if the limit has been reached:
```typescript src/index.ts [expandable] {10-16} theme={null}
import { AtlasExpressClient } from "@runonatlas/express";
import express from "express";
import { getAuth } from "@clerk/express";
import { prisma } from "./db";
const app = express();
const atlas = new AtlasExpressClient((req) => getAuth(req).userId, {
limits: {
"data-explorer": (userId: string) =>
prisma.explorations.count({
where: {
userId,
},
}),
},
});
// ... Remaining code
```
Now, every time you the application needs to check if the `data-explorer` feature is available, Atlas will use the callback to compute it.
By default, if you haven't configured a limit callback, Atlas will deny access to the feature if it has a limit. You can easily override this behavior. For example:
```typescript src/index.ts [expandable] {10-12} theme={null}
import { AtlasExpressClient } from "@runonatlas/express";
import express from "express";
import { getAuth } from "@clerk/express";
import { prisma } from "./db";
const app = express();
const atlas = new AtlasExpressClient((req) => getAuth(req).userId, {
baseClientOptions: {
allowUnknownLimits: true,
},
});
```
## Usage-based and Credit-based features
Usage-based and credit-based features allow you to bill customers based on their consumption.
These features track usage events and apply billing according to your pricing model configuration.
### Understanding billing types
Atlas supports two types of billing for consumption-based features:
**Usage-based billing**: Events are rolled up for the entire billing period, the price is applied, and the user
is billed in arrears according to what they used. This is traditional usage-based billing where customers pay
for what they consume after the fact.
**Credit-based billing**: Users are allocated a specific amount of a custom pricing unit (e.g., credits) each month.
When an event is received in Atlas that matches a credit-based price, their balance of custom pricing units is
instantly deducted. This provides near real-time consumption tracking with immediate balance updates.
Atlas offers the ability to block access to a usage-based and credit-based features
via the **Max usage** and **Block overage** options, respectively.
**Max usage**: (Number) The maximum amount of a usage-based feature a customer is allowed to consume within a single billing
period. If specified, Atlas will block access after usage reaches the amount specified and then unblock access at the start
of the next billing period. If not specified, Atlas will allow unlimited usage in any given billing period.
**Block overage**: (Boolean) Whether or not to prevent a customer from drawing custom pricing unit balance negative.
If enabled, Atlas will block access to a feature when a balance of 0 has been detected. Otherwise, Atlas will continue
accepting events and further reduce the customers credit balance negative. The next allocation of custom pricing units
will be applied as normal, increasing the customers balance from the current negative balance by the amount of the
allocation.
Because Atlas's event ingestion system is designed to be eventually
consistent, it is possible for a small number of events to be processed before
Atlas feature flagging has recognized that the customer has hit a balance of 0.
This means that small overages are possible even if `blockOverage` is enabled.
For more real time access control, please reach out to customer support at
[support@runonatlas.com](mailto:support@runonatlas.com).
### Options to report events
To bill your customers correctly, your application must send an event to Atlas each time a usage-based or credit-based feature is consumed. There are three ways to report these usage events:
#### Option A: Automatic Reporting (Recommended)
**Enjoy Atlas' magic!** By default, the Atlas Express Client automatically sends a single usage event to Atlas every time an endpoint protected by the `createFeaturesMiddleware` is called.
```typescript src/index.ts [expandable] {3} theme={null}
app.post(
"/api/generate-caption",
atlas.createFeaturesMiddleware(["usage-based-feature"]), // Same middleware as before. It's smart enough to know it now needs to send events
(req, res) => {
// Your endpoint logic here
}
);
```
#### Option B: Report a Custom Quantity
If an endpoint's usage should count as more than one event, you can specify a fixed quantity directly in the middleware options.
```typescript src/index.ts [expandable] {3} theme={null}
app.post(
"/api/generate-caption",
atlas.createFeaturesMiddleware(["usage-based-feature"], {
events: { quantity: 2 },
}),
(req, res) => {
// Your endpoint logic here
}
);
```
#### Option C: Manual Event Reporting
For more complex scenarios, such as when the event quantity depends on request-specific logic or needs to be sent conditionally, you can take full manual control. First, disable automatic sending for the middleware, then use the `atlas.client.enqueueEvents` method within your endpoint logic to report the usage.
```typescript src/index.ts [expandable] {3, 6-10} theme={null}
app.post(
"/api/generate-caption",
atlas.createFeaturesMiddleware(["usage-based-feature"], {
events: { sendAutomatically: false },
}),
async (req, res) => {
// Your endpoint logic here
await atlas.client.enqueueEvents({
customerId: getAuth(req).userId,
featureIds: ["usage-based-feature"],
quantity: Math.floor(Math.random() * 10),
});
}
);
```
To disable automatic event reporting for all endpoints by default, set `events: { sendAutomatically: false }` in the `defaultMiddlewareOptions` when initializing the Atlas Express Client. This is useful if you plan to report most events manually.
```typescript src/index.ts [expandable] {8-12} theme={null}
import { AtlasExpressClient } from "@runonatlas/express";
import express from "express";
import { getAuth } from "@clerk/express";
const app = express();
const atlas = new AtlasExpressClient((req) => getAuth(req).userId, {
defaultMiddlewareOptions: {
events: { sendAutomatically: false },
},
});
```
### Batching events
To optimize performance and avoid rate limits, the Atlas client automatically batches usage events before sending them. You can customize this batching behavior by configuring the `eventsFlushAt` (number of events) and `eventsFlushInterval` (time in milliseconds) options during client initialization.
```typescript src/index.ts [expandable] {9-11} theme={null}
import { AtlasExpressClient } from "@runonatlas/express";
import express from "express";
import { getAuth } from "@clerk/express";
const app = express();
const atlas = new AtlasExpressClient((req) => getAuth(req).userId, {
// Events will be sent every 10 events or every 1 second, whichever comes first.
eventsFlushAt: 10,
eventsFlushInterval: 1000,
});
```
You can also trigger a manual flush of all enqueued events at any time using the `flushEvents` method. This is particularly useful for ensuring no events are lost during a graceful shutdown of your application.
```typescript src/index.ts [expandable] {11} theme={null}
async function onProcessShutdown() {
await atlas.client.flushEvents();
}
```
## Organizations
This only covers the backend configuration. You will also need to configure
your frontend. For example,
[React](/developer-portal/sdk/react?#organizations) or
[Next.js](/developer-portal/sdk/next?#organizations).
In many applications, it's common to allow multiple users to belong to the same organization and share a single subscription. For example: Alice creates an organization called **Standard Corp**, purchases a plan, and invites Bob and Carol to join. Since all three are members of the same organization, they should share access to the same plan and limits.
To enable this, you should use the **organization ID** as the user identifier instead of the individual user ID. This tells Atlas that access and usage should be scoped to the organization — not the individual — ensuring that Bob and Carol both have access under Standard Corp’s plan.
```typescript src/index.ts [expandable] {7} theme={null}
import { AtlasExpressClient } from "@runonatlas/express";
import { getAuth } from "@clerk/express";
import express from "express";
const app = express();
const atlas = new AtlasExpressClient(
(req) => getAuth(req).orgId
);
app.use("/", atlas.router);
```
```
```
# JavaScript
Source: https://docs.runonatlas.com/developer-portal/sdk/javascript
`@runonatlas/core` is our JavaScript/TypeScript library for Atlas, which provides the base client for direct integration with Atlas services.
This guide is for direct JavaScript/TypeScript integration. If you're using one of our supported frameworks, we recommend using their dedicated SDKs instead:
* [Next.js SDK](/developer-portal/sdk/next) - For Next.js applications
* [React SDK](/developer-portal/sdk/react) - For React applications
* [Express SDK](/developer-portal/sdk/express) - For Express.js applications
These framework-specific SDKs provide better integration and additional features tailored to each framework.
## Installation
```bash Terminal theme={null}
npm install @runonatlas/core
```
```bash Terminal theme={null}
pnpm add @runonatlas/core
```
```bash Terminal theme={null}
yarn add @runonatlas/core
```
## Initialization
You can get it from the [Atlas Dashboard](https://app.runonatlas.com).
```bash .env theme={null}
ATLAS_API_KEY="INSERT_YOUR_ATLAS_API_KEY"
```
To initialize the Atlas SDK, you need to instantiate the Atlas Client with your API key:
```typescript src/atlas/client.ts expandable theme={null}
import { AtlasClient } from "@runonatlas/core";
const atlas = new AtlasClient();
```
## Core Features
The Atlas JavaScript client provides several methods to interact with Atlas services:
### Feature Management
#### Register Features
Register features that your application uses:
```typescript theme={null}
await atlas.registerFeatureUsage("data-explorer");
```
The feature registration is used to ensure that the feature is registered in Atlas. If the feature is not registered, it will be registered automatically and an alert will be sent to the administrators.
#### Check Feature Access
Check if a customer has access to specific features:
```typescript theme={null}
const { ok, features } = await atlas.areFeaturesAllowed("customer-123", [
"data-explorer",
"ai-assistant",
]);
if (ok) {
// Customer has access to all required features
} else {
// Check individual feature access
console.log(features); // { "data-explorer": true, "ai-assistant": false }
}
```
### Customer Management
#### Get Customer Features
Retrieve all features a customer may have access to:
```typescript theme={null}
const features = await atlas.getCustomerFeatures("customer-123");
console.log(features); // { "data-explorer": { allowed: true, included: true, limit: 10, currentUsage: 5 }, "ai-assistant": { allowed: false, included: false } }
```
#### Get Customer Details
Get detailed information about a customer:
```typescript theme={null}
const customer = await atlas.getCustomerDetails("customer-123");
console.log(customer); // { id: "customer-123", name: "John Doe", ... }
```
### Pricing Model
Get information about your pricing model:
```typescript theme={null}
const pricingModel = await atlas.getPricingModel();
console.log(pricingModel); // { plans: [...], features: [...] }
```
## Limit-based features
Sometimes, just having a feature as enabled or disabled is not enough, and our pricing models require limits to be set. For example, 5 users per account, or 20GB of storage.
Setting this up with Atlas is very easy. And, if at some point the limits change, you won't need to change the code again!
When initializing the Atlas client, you can configure a limit callback for a feature. This callback will be called when a limit-based feature is checked. The callback should return the number of usages for the feature.
For example:
```typescript src/atlas/client.ts expandable theme={null}
import { AtlasClient } from "@runonatlas/core";
const atlas = new AtlasClient({
limits: {
"data-explorer": async () => {
// Return the number of usages for the data-explorer feature
return 5;
},
},
});
```
## Usage-based features
Usage-based and credit-based features allow you to bill customers based on their consumption.
These features track usage events and apply billing according to your pricing model configuration.
### Reporting events
To bill your customers correctly, your application must send an event to Atlas each time a usage-based feature is consumed.
To do this, you can use the `enqueueEvents` method of the Atlas client.
```typescript theme={null}
await atlas.enqueueEvents({
customerId: "customer-123",
featureIds: ["usage-based-feature"],
quantity: Math.floor(Math.random() * 10),
});
```
### Batching events
To optimize performance and avoid rate limits, the Atlas client automatically batches usage events before sending them. You can customize this batching behavior by configuring the `eventsFlushAt` (number of events) and `eventsFlushInterval` (time in milliseconds) options during client initialization.
```typescript src/index.ts theme={null}
import { AtlasClient } from "@runonatlas/core";
const atlas = new AtlasClient({
eventsFlushAt: 10,
eventsFlushInterval: 1000,
});
```
You can also trigger a manual flush of all enqueued events at any time using the `flushEvents` method. This is particularly useful for ensuring no events are lost during a graceful shutdown of your application.
```typescript theme={null}
async function onProcessShutdown() {
await atlas.flushEvents();
}
```
# Next.js
Source: https://docs.runonatlas.com/developer-portal/sdk/next
`@runonatlas/next` is our Next.js library for Atlas, which provides all the components, hooks and backend resources you need.
## Installation
```bash Terminal theme={null}
npm install @runonatlas/next
```
```bash Terminal theme={null}
pnpm add @runonatlas/next
```
```bash Terminal theme={null}
yarn add @runonatlas/next
```
## Initialization
To initialize the application, you need to create a client provider component and wrap your application with it. Since Atlas needs to authenticate with your backend and verify that users are only accessing what they can, you will need to place it under your authentication provider.
It's very important that you provide all the required props:
* `getAuth`: A function that returns the authentication token. *This is only necessary if the authentication with the backend is done with tokens and headers (for example, Clerk)*.
* `loginCallback`: A function that is called whenever the user tries to do an action that requires authentication.
* `userId`: The ID of the user.
* `userEmail`: The email of the user. *Optional.*
* `userName`: The name of the user. *Optional.*
* `isUserLoading`: A boolean that indicates whether the user is still loading. *Optional.* Helps Atlas to avoid flickering when the user status changes.
You then need to wrap your application with the `AtlasProvider`, wherever you have the rest of your providers and under your authentication provider.
For example, with Clerk:
```tsx src/atlas/client.tsx expandable theme={null}
"use client";
import { useAuth, useUser } from "@clerk/nextjs";
import { AtlasProvider } from "@runonatlas/next/client";
import { redirect } from "next/navigation";
const loginCallback = () => {
redirect("/login");
};
export function AtlasClientProvider({
children,
}: {
children: React.ReactNode;
}) {
const { userId, getToken, isLoaded } = useAuth();
const { user } = useUser();
return (
{children}
);
}
```
```tsx src/app/layout.tsx expandable theme={null}
import { ClerkProvider } from "@clerk/nextjs";
import { AtlasClientProvider } from "./atlas/client";
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
{/** Notice how we don't have any html or body tags after the AtlasClientProvider */}
{/* Don't forget the rest of your providers and your application! */}
{children}
);
}
```
You will need to initialize the client in the backend. To do so, you will need to:
1. Set the Atlas API key in the environment variables.
2. Create the Atlas Next Server Client, providing a callback that returns the user that performed the request. This callback will depend on the authentication provider you are using.
3. Use the Atlas Next Server Client to create the API routes that will be used to access the Atlas API. They should be in the `src/app/api/atlas-api/[slug]/route.ts` file.
The result should be like this:
```bash .env theme={null}
ATLAS_API_KEY="INSERT_YOUR_ATLAS_API_KEY"
```
```typescript src/atlas/server.ts expandable theme={null}
import { AtlasNextServerClient } from "@runonatlas/next/server";
import { auth } from "@clerk/nextjs/server";
export const atlasServerClient = new AtlasNextServerClient(
async () => {
const { userId } = await auth();
return userId;
}
);
```
```typescript src/app/api/atlas-api/[[...slug]]/route.ts expandable theme={null}
import { atlasServerClient } from "@/atlas/server";
import { NextRequest } from "next/server";
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ slug: string[] }> }
) {
const { slug } = await params;
return atlasServerClient.handleRequest(request, slug);
}
export async function POST(
request: NextRequest,
{ params }: { params: Promise<{ slug: string[] }> }
) {
const { slug } = await params;
return atlasServerClient.handleRequest(request, slug);
}
```
## Use our Atlas widgets
Remember that you need to have followed the [installation
guide](#installation) before you can use any of these widgets.
You can use any of our Atlas widgets in your Next application by importing them from `@runonatlas/next/client`.
### PricingComponent Widget
The `PricingComponent` widget shows the plans in your Atlas pricing model and allows users to purhcase a subscription to a plan via Atlas' in-line checkout.
**IMPORTANT:** The `successUrl` must be absolute. For example, `/success` is not valid, but `https://your-app.com/success` is valid.
```tsx theme={null}
"use client";
import { PricingComponent } from "@runonatlas/next/client";
/**
* IMPORTANT:
* Replace the `successUrl` prop with values specific to your application.
*/
export default function PricingPage() {
return (
);
}
```
If you want to display your pricing model on a **public facing marketing site**, you can embed a hosted pricing page via `iFrame`. When a visitor selects a plan, they'll be redirected to your core application where the SDK is installed to sign in and complete the purchase.
To embed pricing on a website outside your app, use the `/pricing-embed` endpoint and pass the following URL query parameters:
* `publishableKey` - public key with read access to your pricing model (accessible in [settings/api-keys](https://app.runonatlas.com/settings/api-keys))
* `redirectTo` - absolute URL to redirect users to your app when they select a plan
* `successUrl` - absolute URL to return users to after they succesfully purchase a plan
**IMPORTANT:** The `redirectTo` and`successUrl` must be absolute. For example, `/success` is not valid, but `https://your-app.com/success` is valid.
```html theme={null}
```
### CustomerPortalComponent Widget
The CustomerPortalComponent widget shows the current user what plan they are subscribed to, a history of their payments, and allows them to cancel or change their plan at any time.
**IMPORTANT:** The `successUrl` must be absolute. For example, `/success` is not valid, but `https://your-app.com/success` is valid.
```tsx theme={null}
"use client";
import { CustomerPortalComponent } from "@runonatlas/next/client";
/**
* IMPORTANT:
* Replace the `successUrl` prop with values specific to your application.
*/
export default function CustomerPortalPage() {
return (
);
}
```
## Restrict user access based on their subscriptions
You can prevent users from accessing restricted parts of your application based on their subscription. To do so, it is important that you do it both in the UI and in the backend:
Protecting the UI is the easiest way to prevent users from accessing
restricted parts of your application.
Protecting the UI is cool, but not enough! You need to protect the backend
as well. This way, you can ensure that users cannot access restricted parts
of your application even if they bypass the UI.
### UI Protection
#### Using hooks
You can use the `useFeaturesAllowed` hook to check if a user has access to a feature.
```tsx theme={null}
import { useFeaturesAllowed } from "@runonatlas/next/client";
export default function Home() {
const { isAllowed } = useFeaturesAllowed(["feature-1", "feature-2"]);
return (
Our system has determined that...
{isAllowed
? "You have access to these features!"
: "You don't have access to these features. Please upgrade your subscription!"}
);
}
```
Or you can use the `useCustomerFeatures` hook to get a users access status for all features in your pricing model:
```tsx theme={null}
import { useFeaturesAllowed } from "@runonatlas/next/client";
...
const { features } = useCustomerFeatures();
console.log(features); // { "data-explorer": { allowed: true, included: true, limit: 10, currentUsage: 5 }, "ai-assistant": { allowed: false, included: false } }
```
#### Using components
You can use the `FeatureProtect` component to check if a user has access to a feature.
```tsx theme={null}
import { FeatureProtect } from "@runonatlas/next/client";
export default function Home() {
return (
Oh, you don't have access to these features. Upgrade now!
}
features={["feature-1", "feature-2"]}
>
Cool features
);
}
```
### Backend Protection
Using Atlas in the backend is **very easy**. You just need to use the `atlasServerClient` that we created before to check if a user has access to a feature.
For example, if you are using Clerk:
```typescript src/app/api/example/route.ts expandable theme={null}
import { atlasServerClient } from "@/atlas/server";
import { auth } from "@clerk/nextjs/server";
export async function GET() {
const { userId } = await auth();
if (!userId) {
return new Response("Unauthorized", { status: 401 });
}
const { ok } = await atlasServerClient.areFeaturesAllowed(userId, [
"data-explorer",
]);
if (!ok) {
return new Response("You don't have this feature enabled", { status: 401 });
}
return new Response("Hello from the backend!!");
}
```
## Limit-based features
Sometimes, just having a feature as enabled or disabled is not enough, and our pricing models require limits to be set. For example, 5 users per account, or 20GB of storage.
Setting this up with Atlas is very easy. And, if at some point the limits change, you won't need to change the code again!
### Configuring the backend
The backend needs to understand what the limits are and how to check if the limit has been reached. To do so, you need to use the `atlasServerClient` and provide callbacks per each limit that you might want to use.
For example, given a feature whose id is `data-explorer`, you can provide a callback to check if the limit has been reached:
```typescript src/atlas/server.ts expandable {11,12,13,14,15,16,17} theme={null}
import { AtlasNextServerClient } from "@runonatlas/next/server";
import { auth } from "@clerk/nextjs/server";
import { prisma } from "./db";
export const atlasServerClient = new AtlasNextServerClient(
async () => {
const { userId } = await auth();
return userId;
},
{
limits: {
"data-explorer": (userId: string) =>
prisma.explorations.count({
where: {
userId,
},
}),
},
}
);
```
Now, every time you the application needs to check if the `data-explorer` feature is available, Atlas will use the callback to compute it.
By default, if you haven't configured a limit callback, Atlas will deny access to the feature if it has a limit. You can easily override this behavior. For example:
```typescript src/atlas/server.ts expandable {9,10,11} theme={null}
import { AtlasNextServerClient } from "@runonatlas/next/server";
import { auth } from "@clerk/nextjs/server";
export const atlasServerClient = new AtlasNextServerClient(
async () => {
const { userId } = await auth();
return userId;
},
{
baseClientOptions: {
allowUnknownLimits: true,
},
}
);
```
### Explanation in the UI
When using both the `` component and the `useFeaturesAllowed()` hook, it will automatically check if the user has access to the features you are protecting AND if the limit has not been reached.
However, it is possible that you want to show your user why the access was denied! To to do this, we give you the reasons why the access was denied. For example, with the hook `useFeaturesAllowed`:
```tsx src/app/home/page.tsx expandable theme={null}
import { useFeaturesAllowed } from "@runonatlas/next/client";
export default function Home() {
const { isAllowed, reasons } = useFeaturesAllowed(["feature-1", "feature-2"]);
if (!isAllowed) {
return (
Our system has determined that...
{reasons.map((reason) => {
if (reason.reason === "notIncluded") {
return (
You need to upgrade your plan to access {reason.slug}
);
}
if (reason.reason === "limitReached") {
return (
You have reached the limit for {reason.slug}. You currently have{" "}
{reason.currentUsage}/{reason.limit}.
);
}
})}
);
}
return Cool feature
;
}
```
In the `` component, instead of directly providing the FallbackComponent, you can actually provide a function that returns the component to be rendered when the access is denied. For example:
```tsx src/app/home/page.tsx expandable theme={null}
import { FeatureProtect } from "@runonatlas/next/client";
export default function Home() {
return (
{
return (
Our system has determined that...
{reasons.map((reason) => {
if (reason.reason === "notIncluded") {
return (
You need to upgrade your plan to access {reason.slug}
);
}
if (reason.reason === "limitReached") {
return (
You have reached the limit for {reason.slug}. You currently
have {reason.currentUsage}/{reason.limit}.
);
}
})}
);
}}
features={["feature-1", "feature-2"]}
>
Cool features
);
}
```
## Usage-based and credit-based features
When using Next.js in a serverless environment (e.g., Vercel, Netlify), it is
crucial to properly manage event batching to prevent data loss. See the
[Batching events](#batching-events) section for required configurations.
Usage-based and credit-based features allow you to bill customers based on their consumption.
These features track usage events and apply billing according to your pricing model configuration.
### Understanding billing types
Atlas supports two types of billing for consumption-based features:
**Usage-based billing**: Events are rolled up for the entire billing period, the price is applied, and the user
is billed in arrears according to what they used. This is traditional usage-based billing where customers pay
for what they consume after the fact.
**Credit-based billing**: Users are allocated a specific amount of a custom pricing unit (e.g., credits) each month.
When an event is received in Atlas that matches a credit-based price, their balance of custom pricing units is
instantly deducted. This provides near real-time consumption tracking with immediate balance updates.
Atlas offers the ability to block access to a usage-based and credit-based features
via the **Max usage** and **Block overage** options, respectively.
**Max usage**: (Number) The maximum amount of a usage-based feature a customer is allowed to consume within a single billing
period. If specified, Atlas will block access after usage reaches the amount specified and then unblock access at the start
of the next billing period. If not specified, Atlas will allow unlimited usage in any given billing period.
**Block overage**: (Boolean) Whether or not to prevent a customer from drawing custom pricing unit balance negative.
If enabled, Atlas will block access to a feature when a balance of 0 has been detected. Otherwise, Atlas will continue
accepting events and further reduce the customers credit balance negative. The next allocation of custom pricing units
will be applied as normal, increasing the customers balance from the current negative balance by the amount of the
allocation.
Because Atlas's event ingestion system is designed to be eventually
consistent, it is possible for a small number of events to be processed before
Atlas feature flagging has recognized that the customer has hit a balance of 0.
This means that small overages are possible even if `blockOverage` is enabled.
For more real time access control, please reach out to customer support at
[support@runonatlas.com](mailto:support@runonatlas.com).
### Configuring the backend
To bill your customers correctly, your application must send an event to Atlas each time a usage-based or credit-based feature is consumed.
This is done using the `atlasServerClient.enqueueFeatureEvents` method, followed by `atlasServerClient.flushEvents` to ensure the event is sent immediately. In a serverless environment, this two-step process is essential.
```tsx src/app/api/example/route.ts expandable {18-27} theme={null}
import { atlasServerClient } from "@/atlas/server";
import { auth } from "@clerk/nextjs/server";
export async function GET() {
const { userId } = await auth();
if (!userId) {
return new Response("Unauthorized", { status: 401 });
}
const { ok } = await atlasServerClient.areFeaturesAllowed(userId, [
"usage-based-feature",
]);
if (!ok) {
return new Response("You don't have this feature enabled", { status: 403 });
}
try {
await atlasServerClient.enqueueFeatureEvents({
featureIds: ["usage-based-feature"],
customerId: userId,
});
await atlasServerClient.flushEvents(); // This will send the events immediately
} catch (err) {
console.error("Failed to enqueue feature events", err);
}
return new Response("Hello from the backend!!");
}
```
### Batching events
To optimize performance and avoid rate limits, the Atlas client automatically batches usage events before sending them.
You can customize this batching behavior by configuring the `eventsFlushAt` (number of events) and `eventsFlushInterval` (time in milliseconds) options during client initialization.
This will be very important if you are using a serverless backend, such as Vercel or Netlify, to ensure that events are sent before the serverless function is terminated.
```typescript src/atlas/server.ts [expandable] {9,10,11} theme={null}
import { AtlasNextServerClient } from "@runonatlas/next/server";
import { auth } from "@clerk/nextjs/server";
export const atlasServerClient = new AtlasNextServerClient(
async () => {
const { userId } = await auth();
return userId;
},
{
eventsFlushAt: 0,
eventsFlushInterval: 0,
}
);
```
You can also trigger a manual flush of all enqueued events at any time using the `flushEvents` method.
```typescript {11} theme={null}
import { atlasServerClient } from "@/atlas/server";
async function onProcessShutdown() {
await atlasServerClient.flushEvents();
}
```
## Organizations
In many applications, it's common to allow multiple users to belong to the same organization and share a single subscription. For example: Alice creates an organization called **Standard Corp**, purchases a plan, and invites Bob and Carol to join. Since all three are members of the same organization, they should share access to the same plan and limits.
To enable this, you should use the **organization ID** as the user identifier instead of the individual user ID. This tells Atlas that access and usage should be scoped to the organization — not the individual — ensuring that Bob and Carol both have access under Standard Corp’s plan.
```tsx src/atlas/client.tsx expandable {2,17,24,25} theme={null}
"use client";
import { useAuth, useUser, useOrganization } from "@clerk/nextjs";
import { AtlasProvider } from "@runonatlas/next/client";
import { redirect } from "next/navigation";
const loginCallback = () => {
redirect("/login");
};
export function AtlasClientProvider({
children,
}: {
children: React.ReactNode;
}) {
const { userId, getToken, isLoaded } = useAuth();
const { user } = useUser();
const { organization } = useOrganization();
return (
{children}
);
}
```
```typescript src/atlas/server.ts expandable {6,7} theme={null}
import { AtlasNextServerClient } from "@runonatlas/next/server";
import { auth } from "@clerk/nextjs/server";
export const atlasServerClient = new AtlasNextServerClient(async () => {
const { orgId } = await auth();
return orgId;
});
```
# React
Source: https://docs.runonatlas.com/developer-portal/sdk/react
`@runonatlas/react` is our React library for Atlas, which provides all the components and hooks you need.
## Installation
```bash Terminal theme={null}
npm install @runonatlas/react
```
```bash Terminal theme={null}
pnpm add @runonatlas/react
```
```bash Terminal theme={null}
yarn add @runonatlas/react
```
## Initialization
This only covers the frontend initialization with React. You will also need to initialize your backend. For example, [Express](/developer-portal/sdk/express).
To initialize your frontend application, you need to create a client provider component and wrap your application with it. Since Atlas needs to authenticate with your backend and verify that users are only accessing what they can, you will need to place it under your authentication provider.
It's very important that you provide all the required props:
* `getAuth`: A function that returns the authentication token. This is only necessary if the authentication with the backend is done with tokens and headers (for example, Clerk).
* `loginCallback`: A function that is called whenever the user tries to do an action that requires authentication.
* `userId`: The ID of the user.
* `userEmail`: The email of the user. Optional.
* `userName`: The name of the user. Optional.
* `isUserLoading`: A boolean that indicates whether the user is still loading. *Optional.* Helps Atlas to avoid flickering when the user status changes.
You then need to wrap your application with the `AtlasProvider`, wherever you have the rest of your providers and under your authentication provider.
For example, with Clerk:
```tsx src/atlas/client.tsx expandable theme={null}
import { useAuth, useClerk, useUser } from "@clerk/clerk-react";
import { AtlasProvider } from "@runonatlas/react";
import { useCallback } from "react";
export function AtlasClientProvider({
children,
}: {
children: React.ReactNode;
}) {
const { getToken, userId, isLoaded } = useAuth();
const { user } = useUser();
const { openSignIn } = useClerk();
const loginCallback = useCallback(() => {
openSignIn();
}, [openSignIn]);
return (
{children}
);
}
```
```tsx src/App.tsx expandable theme={null}
import { ClerkProvider } from "@clerk/clerk-react";
export default function App() {
return (
);
}
```
You can specify the host of your backend when instantiating the provider.
```tsx src/atlas/client.tsx expandable {22} theme={null}
import { useAuth, useClerk, useUser } from "@clerk/clerk-react";
import { AtlasProvider } from "@runonatlas/react";
import { useCallback } from "react";
export function AtlasClientProvider({
children,
}: {
children: React.ReactNode;
}) {
const { getToken, userId } = useAuth();
const { user } = useUser();
const { openSignIn } = useClerk();
const loginCallback = useCallback(() => {
openSignIn();
}, [openSignIn]);
return (
{children}
);
}
```
## Use our Atlas widgets
Remember that you need to have followed the [installation guide](#installation) before you can use any of these widgets.
You can use any of our Atlas widgets in your React application by importing them from `@runonatlas/react`.
### PricingComponent Widget
The PricingComponent widget shows the plans in your Atlas pricing model and allows users to purhcase a subscription to a plan via Atlas' in-line checkout.
**IMPORTANT:** The `successUrl` must be absolute. For example, `/success` is not valid, but `https://your-app.com/success` is valid.
```tsx theme={null}
import { PricingComponent } from "@runonatlas/react";
/**
* IMPORTANT:
* Replace the `successUrl` prop with values specific to your application.
*/
export default function PricingPage() {
return (
);
}
```
If you want to display your pricing model on a **public facing marketing site**, you can embed a hosted pricing page via `iFrame`. When a visitor selects a plan, they'll be redirected to your core application where the SDK is installed to sign in and complete the purchase.
To embed pricing on a website outside your app, use the `/pricing-embed` endpoint and pass the following URL query parameters:
* `publishableKey` - public key with read access to your pricing model (accessible in [settings/api-keys](https://app.runonatlas.com/settings/api-keys))
* `redirectTo` - absolute URL to redirect users to your app when they select a plan
* `successUrl` - absolute URL to return users to after they succesfully purchase a plan
**IMPORTANT:** The `redirectTo` and`successUrl` must be absolute. For example, `/success` is not valid, but `https://your-app.com/success` is valid.
```html theme={null}
```
### CustomerPortalComponent Widget
The CustomerPortalComponent widget shows the current user what plan they are subscribed to, a history of their payments, and allows them to cancel or change their plan at any time.
**IMPORTANT:** The `successUrl` must be absolute. For example, `/success` is not valid, but `https://your-app.com/success` is valid.
```tsx theme={null}
import { CustomerPortalComponent } from "@runonatlas/react";
/**
* IMPORTANT:
* Replace the `successUrl` prop with values specific to your application.
*/
export default function CustomerPortalPage() {
return (
);
}
```
## Limit user access based on their subscriptions
You can prevent users from accessing restricted parts of your application based on their subscription. To do so, you can use our UI protection features.
### UI Protection
#### Using hooks
You can use the `useFeaturesAllowed` hook to check if a user has access to a feature.
```tsx theme={null}
import { useFeaturesAllowed } from "@runonatlas/react";
export default function Home() {
const { isAllowed } = useFeaturesAllowed(["feature-1", "feature-2"]);
return (
Our system has determined that...
{isAllowed ? "You have access to this feature!" : "You don't have access to this feature. Please upgrade your subscription!"}
);
}
```
Or you can use the `useCustomerFeatures` hook to get a users access status for all features in your pricing model:
```tsx theme={null}
import { useFeaturesAllowed } from "@runonatlas/react";
...
const { features } = useCustomerFeatures();
console.log(features); // { "data-explorer": { allowed: true, included: true, limit: 10, currentUsage: 5 }, "ai-assistant": { allowed: false, included: false } }
```
#### Using components
You can use the `FeatureProtect` component to check if a user has access to a feature.
```tsx theme={null}
import { FeatureProtect } from "@runonatlas/react";
export default function Home() {
return (
Oh, you don't have access to this cool feature. Upgrade now!}
features={["feature-1", "feature-2"]}
>
Cool feature
);
}
```
## Limit-based features
Sometimes, just having a feature as enabled or disabled is not enough, and our pricing models require limits to be set. For example, 5 users per account, or 20GB of storage.
Setting this up with Atlas is very easy. And, if at some point the limits change, you won't need to change the code again!
Make sure to configure the limits also on your backend. For example, [Express](/sdk/express).
### Explanation in the UI
When using both the `` component and the `useFeaturesAllowed()` hook, it will automatically check if the user has access to the features you are protecting AND if the limit has not been reached.
However, it is possible that you want to show your user why the access was denied! To to do this, we give you the reasons why the access was denied. For example, with the hook `useFeaturesAllowed`:
```tsx src/routes/home/index.tsx expandable theme={null}
import { useFeaturesAllowed } from "@runonatlas/react";
export default function Home() {
const { isAllowed, reasons } = useFeaturesAllowed(["feature-1", "feature-2"]);
if (!isAllowed) {
return (
Our system has determined that...
{reasons.map((reason) => {
if (reason.reason === "notIncluded") {
return (
You need to upgrade your plan to access {reason.slug}
);
}
if (reason.reason === "limitReached") {
return (
You have reached the limit for {reason.slug}. You currently have {reason.currentUsage}/{reason.limit}.
);
}
})}
);
}
return Cool feature
;
}
```
In the `` component, instead of directly providing the FallbackComponent, you can actually provide a function that returns the component to be rendered when the access is denied. For example:
```tsx src/routes/home/index.tsx expandable theme={null}
import { FeatureProtect } from "@runonatlas/react";
export default function Home() {
return (
{
return (
Our system has determined that...
{reasons.map((reason) => {
if (reason.reason === "notIncluded") {
return (
You need to upgrade your plan to access {reason.slug}
);
}
if (reason.reason === "limitReached") {
return (
You have reached the limit for {reason.slug}. You currently have {reason.currentUsage}/{reason.limit}.
);
}
})}
);
}}
features={["feature-1", "feature-2"]}
>
Cool feature
);
}
```
## Organizations
This only covers the frontend configuration. You will also need to configure your backend. For example, [Express](/developer-portal/sdk/express?#organizations).
In many applications, it's common to allow multiple users to belong to the same organization and share a single subscription. For example: Alice creates an organization called **Standard Corp**, purchases a plan, and invites Bob and Carol to join. Since all three are members of the same organization, they should share access to the same plan and limits.
To enable this, you should use the **organization ID** as the user identifier instead of the individual user ID. This tells Atlas that access and usage should be scoped to the organization — not the individual — ensuring that Bob and Carol both have access under Standard Corp’s plan.
```tsx src/atlas/client.tsx expandable {1,12,24,25} theme={null}
import { useAuth, useClerk, useOrganization, useUser } from "@clerk/clerk-react";
import { AtlasProvider } from "@runonatlas/react";
import { useCallback } from "react";
export function AtlasClientProvider({
children,
}: {
children: React.ReactNode;
}) {
const { getToken } = useAuth();
const { user } = useUser();
const { organization } = useOrganization();
const { openSignIn } = useClerk();
const loginCallback = useCallback(() => {
openSignIn();
}, [openSignIn]);
return (
{children}
);
}
```
# Stripe Integration
Source: https://docs.runonatlas.com/developer-portal/stripe-integration
Integrate Atlas with Stripe to receive payments
## Stripe Integration
Atlas integrates with Stripe to help you manage customer purchases and payments. To connect your Stripe account to Atlas, you’ll need to provide an API key—either a secret key or a restricted key. We **strongly** recommend using a restricted key whenever possible. This approach aligns with our commitment to the principle of least privilege—a best practice in security that ensures systems only have access to the resources they absolutely need. Stripe API keys can be found in your [Stripe API Dashboard](https://dashboard.stripe.com/apikeys).
Atlas only accepts test keys in `test` mode and production keys in `prod` mode.
### API Key Options
1. Restricted Key (Recommended)
* Access: Only to specific resources you select.
* Pros: More secure. Limits Atlas access to only the necessary parts of your Stripe account.
* Cons: Requires a few more steps to configure. You may need to update permissions in the future as Atlas adds new features.
2. Secret Key (Full Access)
* Access: Full access to your Stripe account.
* Pros: Easiest to set up - no configuration required, and no updates required if Atlas needs access to a new resource type.
* Cons: Gives Atlas access to all your Stripe resources, including ones we don’t use. Not ideal from a security standpoint.
### What Permissions Does Atlas Need?
When using a restricted key, Atlas currently needs write access to the following Stripe resources:
* Customers
* Checkout Sessions
* Coupons
* Invoices
* Webhook Endpoints
### How to Connect with a Restricted API Key in Stripe?
1. Go to your [Stripe API Keys](https://dashboard.stripe.com/apikeys) page.
2. Click **Create restricted key**.
3. Select **Providing this key to another website**.
4. Name the key (e.g., Atlas Integration).
5. Set the **URL** to [https://app.runonatlas.com/](https://app.runonatlas.com/)
6. Check the box for **Customize permissions for this key**.
7. In the permissions section, set the following to **Write** (All other permissions should be set to **None**):
* Customers
* Checkout Sessions
* Coupons
* Invoices
* Webhook Endpoints
8. Save the key and copy it.
9. Navigate back to the [Atlas integrations page ](https://app.runonatlas.com/integrations).
10. Click the button for connecting to Stripe, paste your key into the modal that opens, and click **Activate**.
Atlas will verify that the provided key has the correct permissions and save it securely for use when charging customers.
Be sure to store your key securely. Treat it like a password.
# Edit and Publish a Plan
Source: https://docs.runonatlas.com/edit-and-publish-a-plan
Here’s how to customize your pricing model and publish it with Atlas.
### 1. Add a New Feature
Click “Add New Feature” to define a new entitlement (e.g., Users, Storage, etc.).
### 2. Edit the Feature
Click on the new feature to edit its name, description, and limits.
### 3. Include the Feature in a Plan
Click the “X” icon under the plan you want to include the feature in, then toggle “Included” on.
The icon will change to a ✅ checkmark after saving.
### 4. Set Feature Limits (Optional)
In the feature editor, enter a limit for the feature.
**What is a limit?**
A limit defines the maximum amount of resources that the customer can have at any given moment.
For example:
* If the feature is 'Users', a limit of 10 means that our customers can have up to 10 users.
* If the feature is 'Storage (GB)', a limit of 100 means that our customers can have up to 100GB of storage.
If a limit is set, this replaces the checkmark with the set limit numerical value.
### 5. Inline Edit Plan Details
Click directly on a tier’s name, price, or description to update it on the spot.
### 6. Add a New Plan
Use the side UI labeled “Create new plan” to add another pricing tier.
### 7. Save Your Changes
Click “Save changes” to lock in all your edits.
# FAQ
Source: https://docs.runonatlas.com/faq
The answers to the questions we get asked more often.
Missing one? [Reach out](https://discord.gg/runonatlas).
#### Do I need a developer to use Atlas?
You’ll need light engineering help to install the pricing widget and optionally integrate with our API. But most pricing setup and testing is low-code and handled through the Atlas UI.
#### Can I delete a plan after publishing it?
No. Once a plan is published, it becomes part of your live pricing experience and can no longer be deleted. This is to protect against accidental disruptions for active customers.
#### Can I delete plans before publishing?
Yes. You can delete any plans that have not yet been published.
#### What happens if I publish multiple times?
If you originally published 3 plans, then added and published a 4th, you can delete that 4th plan. However, you won’t be able to delete the original 3 because they were included in the first published set.
#### Can I end up with zero plans?
Yes, but only if you haven’t published anything yet. Once you've published at least one plan, at least one must remain in production.
#### Does Atlas support international currencies?
Yes, Atlas supports international currencies through Stripe. That said, we recommend checking with your local tax authority to ensure you're meeting all local tax requirements.
#### What happens when I change a price in Atlas?
Any live pricing pages or embedded widgets update automatically—no deploy required. A change like this will impact new subscribers to the plan.
#### Can I run multiple experiments at once?
No. Atlas currently only supports one experiment at a time.
#### Is there a way to preview changes before going live?
Yes. What you see is what you get - Atlas shows pricing changes in a staging view before you hit publish.
# Introduction
Source: https://docs.runonatlas.com/index
Atlas helps you set up your pricing situation confidently and fast. Whether you're launching your first plan or rethinking how you charge, Atlas gives you the tools to test, learn, and adapt - with minimal engineering effort.
## After You Sign Up
Sign up is instant - no setup fees or lengthy onboarding. Once you're in Atlas, you can:
1. Tell us about your product
2. Atlas generates pricing plans to get you started
3. Add the Atlas pricing widget into your product (just a little engineering)
4. 💥 Boom! You can run experiments by changing the prices in Atlas and it will update in your app immediately like magic!
Our team at Atlas uses [Windsurf](https://windsurf.com/) as our preferred AI IDE, and tools like it pair beautifully with Atlas. You can plug in AI instructions to personalize your pricing flow with just a small amount of engineering lift.
## Who Atlas Is For
Atlas is for individuals or teams who want to move fast and experiment with pricing without waiting on long dev cycles. If you’ve ever felt stuck with a static pricing page, Atlas is for you.
## What Atlas Handles
Atlas provides a [pricing widget](/developer-portal/sdk/react#pricingcomponent-widget) you can drop into your application and a [customer portal widget](/developer-portal/sdk/react#customerportalcomponent-widget) that displays user-specific pricing details like plan name, renewal date, and upgrade options.
You don’t have to build these from scratch - Atlas gives you the infrastructure out of the box.
Let users choose a plan and purchase it in just a few clicks.
Show users their plan, renewal date, and upgrade options and convert them to higher plans.
Atlas takes care of your pricing stack so you can stay focused on learning, iterating, and growing.
## Best Practices for Pricing Experiments
* **Start simple**: Begin with 2–3 plan variations. Keep changes focused so you can learn what’s working.
* **Run real tests**: Don’t just draft pricing—ship it. Atlas lets you launch tests to real customers, so you get real data.
* **Choose clear success metrics**: Whether it’s conversion rate, signups, or plan upgrades, pick one or two KPIs to measure impact.
* **Let tests run**: Give your experiments enough time and traffic to reach meaningful results. Rushing the data skews outcomes.
* **Iterate quickly**: Once you’ve learned something, use that insight to shape your next experiment. Atlas makes it easy to tweak and relaunch.
Atlas turns your pricing page into a live feedback loop. The faster you learn, the faster you grow!
For engineers looking to integrate Atlas into their product, use our SDK, or explore our API endpoints. Start here if you're building something new.
Looking for our legacy API? It’s still here for historical reference, but most folks should use our current SDK and platform tools.
# How to Update Your Pricing
Source: https://docs.runonatlas.com/manage-your-pricing-models
Once Atlas is connected to your product with our SDK, any edits you make to your pricing model will automatically reflect in your live app. No redeploy required.
1. Go to app.runonatlas.com and navigate to the Pricing tab.
2. Make your changes to plans, features, limits, or prices.
3. Click Save changes.
4. Refresh your live app to see your updated pricing reflected instantly.
Any new customers will now see and subscribe to your latest pricing.