`
* Done. Now Paddle will redirect your customers to the payment page provided by Nadles.
* Don't forget to customize the style of your Paddle checkout form to match the user portal. For that, navigate to **Checkout → Checkout settings → Inline**.
#### Apple Pay verification
To verify your user portal domain for Apple Pay with Paddle:
1. In your Paddle dashboard, navigate to **Checkout → Website approval → Apple Pay verification**.
2. Download the [Apple Pay domain association file](https://developer.paddle.com/assets/files/apple-developer-merchantid-domain-association).
3. Open the downloaded file and copy its contents.
4. On Nadles, navigate to **My User Portal → Payment integration**.
5. Paste the contents of the file into the field called **Apple Developer Merchant ID Domain Association** and save the changes.
6. Back on Paddle, click **Verify** next to your domain.
Once verified, Apple Pay will be available as a payment method in your checkout.
### On your website
If you aren't using Nadles User Portal or didn't attach a custom domain to it, you can create a payment page on your website.
Nadles will redirect your customers to this page for payment.
In order to accept payments via Paddle, you need to build an [overlay checkout page](https://developer.paddle.com/build/checkout/build-overlay-checkout) that [accepts Paddle transaction ID](https://developer.paddle.com/build/transactions/pass-transaction-checkout) and host it on your domain.
The overlay checkout page can be as simple as a single HTML page with a bit of JS code.
Let's say you own the domain name `myfirstapi.example` and plan to host the Paddle checkout page on `https://myfirstapi.example/paddle-checkout`
Follow the steps below to get your Paddle checkout up and running.
#### Domain verification
First, get `myfirstapi.example` approved by Paddle. [What is domain verification?
](https://www.paddle.com/help/start/account-verification/what-is-domain-verification)
#### Default payment link
Second, set up your [default payment link](https://developer.paddle.com/build/transactions/default-payment-link) on Paddle to:
`https://myfirstapi.example/paddle-checkout`
Nadles will redirect your customers to this page to make a payment.
Two query string parameters will be added to the URL:
1. `_ptxn` — a checkout transaction ID, added by Paddle. [Learn more.](https://developer.paddle.com/build/transactions/create-transaction)
2. `_px_success_url` — success URL, added by Nadles. Paddle will redirect the customer to this URL after the payment succeeds.
#### Paddle checkout page
Third, build the checkout page and host it on your website.
Feel free to use the following template and customize as needed:
```html theme={null}
```
Now deploy the page on `https://myfirstapi.example/paddle-checkout` and you're good to go.
That's it. Paddle integration is set up and ready.
# Stripe
Source: https://docs.nadles.com/payment-integration/stripe
## Overview
[Stripe](https://stripe.com/) integration is one of two options offered by Nadles for payment collection.
Nadles Billing Engine is fully integrated with Stripe and all you need to do in order to start accepting payments is to set up a Stripe account and configure Nadles.
You don't have to create products/prices/customer accounts in Stripe — Nadles does that automatically.
In contrast to API marketplaces, Nadles doesn't act as a middleman in the payment process.
All payments are made directly to your Stripe account.
Nadles Billing Engine automatically calculates amounts due, initiates payments and tracks the status of each payment in order to enable or disable access for customers to your API.
Nadles doesn't use Stripe Billing. All amounts are calculated by Nadles Billing Engine and turned into Stripe invoices.
For each subscription that uses Stripe as payment provider, Nadles automatically creates Stripe invoices and attempts automatic payment collection. If the attempt fails, the customer can pay the invoice manually.
## Video guide
## Prerequisites
You need an active Stripe account.
## Setting up Stripe account
For Nadles to successfully manage your Stripe payments, the following data is required:
* Publishable key
* Secret key
* Webhook signing secret
Nadles stores this information in encrypted form, you won't be able to see it again once it's saved.
Follow the steps below to obtain the necessary credentials.
* Navigate to **My User Portal → Payment integration**.
* Open your Stripe account in a separate tab. Register an account on [https://stripe.com](https://stripe.com) if you don't have one.
* Navigate to **Developer tools → API keys**.
* Copy the **Publishable key** to the **Publishable key** field on Nadles.
* Click **Create restricted key**.
* When asked "How will you be using this API key", choose **Providing this key to another website**.
* Enter `Nadles` for the name and `https://www.nadles.com/` as URL.
* Click **Create restricted key**.
* Copy the newly created restricted key to the **Secret key** field on Nadles.
* Navigate to **Event destinations** tab on Stripe.
* Click **Create an event destination** (or **Add endpoint** if you already have other endpoints set up).
* Copy the endpoint URL from the field **Your Stripe webhook URL** on Nadles.
* Click **Select events to listen to → Select all events**, then **Add events**.
* Click **Add endpoint**
* Once the endpoint is added, click **Reveal** in the **Signing secret** column.
* Copy the signing secret to the **Webhook signing secret** field on Nadles.
* Click **Save** on Nadles.
* Done! Your payment integration is set up.
# Create prices
Source: https://docs.nadles.com/pricing/create-price
Once you've created a new [product](/products/create) and set up [billable metrics](/products/billable-metrics), you can define the pricing model.
## Overview
To configure product pricing, you need to create one or more prices.
During the subscription life cycle, each price is billed independently, allowing you to configure complex pricing models.
However, if several prices have the same billing period, they all are charged in one single payment.
If you, for example, create one price billed monthly and another billed annually, Nadles will do exactly that: the customer will be charged for both prices according to their billing periods.
To create prices, navigate to **My Products → choose product → Pricing**.
Try creating products from [templates](/products/create#product-templates) to see different pricing configurations in action.
## Pricing models
Pricing model defines how Nadles calculates the payment amount based on the number of purchased or consumed billable metrics units.
The following models are supported: flat/setup fee, standard, package, graduated and volume. Below you can find a detailed description for each model.
## Recurring prices
Recurring prices are billed every X days, weeks, months or years.
They can be used to charge fees for your services periodically.
Recurring prices can be either unmetered or metered.
* **Unmetered** prices charge the full amount for the purchased quantity of billable metrics units.
You can configure the default purchase quantity in the price settings and allow customers to adjust it at checkout if needed.
The customer pays in advance for the whole pre-purchased volume and cannot use more than that:
a hard limit is applied to consumption. Once the customer has used the whole purchased volume,
Nadles blocks access until the next billing period.
* **Metered** prices implement pay-per-use models.
Nadles tracks actual usage during the subscription life cycle, aggregates it at specified intervals,
calculates the payment amount based on the real usage and bills the customer.
These pricing models imply that the payment is paid at the end of the billing period and there is no hard limit on the consumption.
For each API call, Nadles can calculate the usage dynamically based on request and response attributes
like headers, JSON body values, query string parameters, etc.
For example, your API backend can send the number of units consumed by the current request in a response header
and Nadles can record this usage and bill for it.
[Learn more](/products/billable-metrics#dynamic-usage).
### Flat fee
Recurring flat fees are used to bill the same amount every time.
This is the simplest pricing model and does not require a billable metric.
Nadles simply charges the customer the same amount at the beginning of every billing period.
You can use a flat fee price and [set a hard limit](/products/billable-metrics#set-hard-limit) in the billable metric settings to limit the maximum usage.
Alternatively, you can use unmetered Standard pricing model to impose a limit on the billable metric usage.
### Standard
Recurring standard prices charge the same amount for each unit of a billable metric.
When using this pricing model, you must select a billable metric to which the price applies.
#### Standard unmetered
When creating a standard unmetered price, you need to specifiy the default quantity that is the number of units to be purchased by customers.
This is a hard limit, the customers are unable to use more than the purchased quantity.
You can allow them to adjust the quantity at the checkout.
The limit is reset at the beginning of each billing period.
**Example**
Say, you have a billable metric "Credits"
and a standard recurring unmetered price of \$1 per unit monthly.
If you set the price quantity to 100, then the payment amount will be `$1 * 100 = $100/month`
and the customer will be able to use 100 credits per month.
#### Standard metered
When creating a standard metered price, you don't get to specify the quantity.
Instead, Nadles tracks usage per subscription, aggregates it on the specified schedule,
and calculates the payment amount based on the actual quantity.
**Example**
Say, you have a billable metric "Credits"
and a standard recurring metered price of \$1 per unit monthly.
A customer consumes 150 credits during the first month.
Then, at the end of the month Nadles will charge the customer `$1 * 150 = $150`.
To limit the maximum usage for metered prices,
consider [setting a hard limit](/products/billable-metrics#set-hard-limit) in the billable metric settings.
This may help protect your customers from unexpected overages by capping the maximum consumption.
### Package
Recurring package prices charge the same amount for each package of X units.
When using this pricing model, you must select a billable metric to which the price applies.
#### Package unmetered
For this pricing model you specify a price per package and the number of units in the package.
The quantity you configure for this model is the number of **units**, available to the customer and not the number of packages.
**Example**
Say, you have a billable metric "Credits"
and a recurring unmetered package price with 10 units in a package and \$1 per package monthly.
If you set the quantity to 143, Nadles will charge for 15 packages, so `$1 * 15 = $15/month`.
The customer will be able to use 143 credits per month.
#### Package metered
When creating a package metered price, you don't get to specify the quantity.
Instead, Nadles tracks usage per subscription, aggregates it on the specified schedule,
and calculates the payment amount based on the actual quantity.
**Example**
Say, you have a billable metric "Credits"
and a package recurring metered price of \$5 per package monthly.
A customer consumes 83 credits during the first month.
This equals to `ceil(83 / 10) = 9 packages`.
Then, at the end of the month Nadles will charge the customer `$5 * 9 = $45`.
To limit the maximum usage for metered prices,
consider [setting a hard limit](/products/billable-metrics#set-hard-limit) in the billable metric settings.
This may help protect your customers from unexpected overages by capping the maximum consumption.
### Graduated
Tiered pricing model. Charge different price per unit based on the tier configuration.
#### Graduated unmetered
When creating a graduated unmetered price, you need to specifiy the default quantity that is the number of units to be purchased by customers.
This is a hard limit, the customers are unable to use more than the purchased quantity.
You can allow them to adjust the quantity at the checkout.
The limit is reset at the beginning of each billing period.
**Example**
Given the following tiers:
**For the first 1-10 units:** \$0.5 per unit + \$5 flat fee
**For the next 11-40 units:** \$0.3 per unit
**For the next 41-∞ units:** \$0.1 per unit
If the quantity is set to 50 units, the final payment amount is calculated as:
```
10 units * $0.5 + $5 flat fee
+ 30 units * $0.3
+ 10 units * $0.1
= $20
```
#### Graduated metered
When creating a graduated metered price, you don't have to specify the quantity.
Instead, Nadles tracks usage per subscription, aggregates it on the specified schedule,
and calculates the payment amount based on the actual quantity using the "graduated" strategy.
**Example**
Given the following tiers:
**For the first 1-10 units:** \$0.5 per unit + \$5 flat fee
**For the next 11-40 units:** \$0.3 per unit
**For the next 41-∞ units:** \$0.1 per unit
If the customer uses 64 credits during the first month, then the final payment amount is calculated as:
```
10 units * $0.5 + $5 flat fee
+ 30 units * $0.3
+ 24 units * $0.1
= $21.4
```
To limit the maximum usage for metered prices,
consider [setting a hard limit](/products/billable-metrics#set-hard-limit) in the billable metric settings.
This may help protect your customers from unexpected overages by capping the maximum consumption.
### Volume
Apply different price per unit based on the total number of units.
#### Volume unmetered
When creating a volume unmetered price, you need to specifiy the default quantity that is the number of units to be purchased by customers.
This is a hard limit, the customers are unable to use more than the purchased quantity.
You can allow them to adjust the quantity at the checkout.
The limit is reset at the beginning of each billing period.
**Example**
With the following tiers:
**Total 1-100 units:** \$0.5 per unit + \$5 flat fee
**Total 101-200 units:** \$0.3 per unit
**Total 201-∞ units:** \$0.1 per unit
If the quantity is set to 50 units, it falls into the `1-100` tier and the final payment amount is calculated as:
`50 units * $0.5 + $5 flat fee = $30`
For 140 units (tier `101-200`), the final amount is calculated as:
`140 units * $0.3 = $42`
#### Volume metered
When creating a volume metered price, you don't have to specify the quantity.
Instead, Nadles tracks usage per subscription, aggregates it on the specified schedule,
and calculates the payment amount based on the actual quantity using the "volume" strategy.
**Example**
With the following tiers:
**Total 1-100 units:** \$0.5 per unit + \$5 flat fee
**Total 101-200 units:** \$0.3 per unit
**Total 201-∞ units:** \$0.1 per unit
If the customer used 50 units, the consumption falls into the `1-100` tier and the final payment amount is calculated as:
`50 units * $0.5 + $5 flat fee = $30`
If the customer used 140 units, the consumption falls into the `101-200` tier and the final amount is calculated as:
`140 units * $0.3 = $42`
## One-off prices
One-off prices are charged once. That is useful in case you would like to charge set up fees or use prepaid pricing model.
Unlike recurring, one-off prices allow your customers to buy a certain number of non-expiring units once and use them as needed.
You can configure, whether it is possible for customers to top up later.
One-off prices support the same pricing models as recurring prices (setup fee, standard, package, graduated, volume), with one difference: one-off prices are always unmetered.
It means, that they represent a hard limit on consumption and customers always pay for the full purchased quantity.
### Top ups
You can allow your customers to top up one-off items in a subscription if the price is configured with the standard, package, graduated or volume model.
If you enable top ups, the customer can buy more items right from the subscription dashboard in the user portal:
## Adjustable quantity
For recurring unmetered and one-off prices using standard, package, graduated or volume model, you can enable quantity adjustment at checkout.
If this config option is enabled, customers can adjust the purchased quantity right on the checkout page:
This is especially useful if you offer tiered pricing or would like to allow customers decide how many units to buy.
## Combining prices
Since prices are billed independently, you can combine one-off and recurring, metered and unmetered prices in a single product.
Recurring prices are automatically billed according to their settings.
One-off prices are included in the very first payment and, if top-ups are enabled,
customers are able to purchase more one-off items later.
## Separate aggregation schedule for usage-based prices
Sometimes you might need to aggregate usage and calculate overages daily, but collect a payment only once a month.
This feature allows you to implement that easily.
Just create a recurring metered price with an aggregation period of 1 day and billing period of 1 month.
Configured this way, Nadles will record the usage every day, calculate and record the amount due for the usage every day,
but will collect the payment for the total of these amounts only once a month.
**Example**
The price: 100 API calls included, \$0.1 for overage calls, aggregated daily. Billed monthly.
On day 1 the customer makes 102 API calls. Nadles records the usage and the amount for the 2 overage calls: \$0.2.
On day 2 the customer makes 133 API calls. Nadles records the usage and the amount for the 33 overage calls: \$3.3.
On day 3 the customer makes 215 API calls. Nadles records the usage and the amount for the 33 overage calls: \$21.5.
On the last day of the month Nadles will charge the customer \$0.2 + \$3.3 + \$21.5 = \$25.
# Overview
Source: https://docs.nadles.com/pricing/overview
With Nadles, you get a billing engine optimized specifically for API monetization, ensuring accurate and efficient billing for your API-based business model.
With the recent release of Billing 2.0, Nadles has become the world's most advanced monetization platform.
## Features
Regular subscriptions allow you to periodically bill your customers for provided services at any arbitrary schedule.
Prepaid models allow your customers to buy a certain amount of
[billable metrics units](/products/billable-metrics) and use them until running out of balance.
A good example is, your customers buy 1000 credits, use them and top up as needed.
Another example is a Trial plan with 500 API calls included. Once the customer makes 500 requests, Nadles revokes access.
Nadles API gateway and billing engine work together to track usage per subscription,
periodically aggregate the usage data, calculate and bill amounts due.
You can combine several prices with different billing intervals in a single subscription.
It's easier to illustrate with an example.
Let's say, you would like to offer an annual plan with overages paid monthly.
E.g.,
* \$400 a year
* 5000 API calls per month included
* \$0.1 overage, billed monthly
With Nadles, implementing this kind of pricing can be done with several mouse clicks.
Sometimes you'd like to record usage more often and collect a payment once in a while.
You can do so by setting different billing and usage aggregation periods.
For example, you can configure Nadles to aggregate usage daily and bill for the accrued amount once a month.
Are you noticing unusually high usage from a customer and worried their upcoming payment might fail?
Has a customer's recurring payment already failed and is now overdue?
Do you want to reduce the risk of non-payment by charging a specific amount in advance?
Nadles lets you issue one-time charges of any amount, while automatically adjusting the customer’s next billing cycle to reflect those charges.
By combining different kinds of pricing and billing rules you can create complex pricing scenarios that fit your business model.
# Billing for AI tokens usage
Source: https://docs.nadles.com/products/ai-and-llm-billing
TL;DR:
* Nadles natively supports automatic AI and LLM tokens usage billing.
* All you need is to add one line to billable metric configuration.
* Nadles Gateway processes the response automatically and records the tokens usage.
* Nadles Billing aggregates the usage and initiates payments for your customers.
## How it works
This is a plan that your customers will subscribe to.
For example, "Input tokens", "Output tokens", "Total tokens", etc.
Let Nadles know the response format for each endpoint, such as OpenAI Completions API, OpenAI Responses API, Anthropic APIs, Ollama, Gemini, etc.
For each billable metric.
Set limits on the number of tokens used for each billable metric.
You've now set up billing for AI tokens usage. [Set up the user portal](/user-portal/getting-started) to let your customers pay and start using your API.
## Prerequisites
* You've [added an API](/api-management/add-api#adding-a-new-api)
* You've added the [LLM endpoint](/api-management/add-api#adding-endpoints)
## Create product
Navigate to **Products** in the left menu and click **Add new product**.
Make sure to select the endpoint you want to bill for.
## Add billable metrics
Once the product is created, you can add billable metrics to it.
Billable metrics tell Nadles what you want to charge for.
For example, "Input tokens", "Output tokens", "Total tokens", etc.
Navigate to **Billable metrics** in the left menu and click **Add new metric**.
If you need to later associate more endpoints with the billable metrics for the same product, you can do so by navigating to the **Usage configuration** tab and clicking **Add endpoint**.
### Charge for input and output tokens separately
If you want to charge for input and output tokens separately, you can add two billable metrics: **Input tokens** and **Output tokens**.
Make sure to select the endpoint you want to bill for. You should associate the same endpoint with both billable metrics.
Navigate to the **Usage configuration** tab and configure the usage for each billable metric.
Set the **Quantity used by a single call** to:
* `openai_completions_usage().prompt_tokens` for **Input tokens**
* `openai_completions_usage().completion_tokens` for **Output tokens**
This will tell Nadles to record the usage of input and output tokens separately.
The `openai_completions_usage()` function returns the usage of the input and output tokens for the OpenAI Completions API.
If you are using a different API, you can use the following functions:
* openai\_responses\_usage
* anthropic\_messages\_usage
* ollama\_usage
* openai\_completions\_usage
* deepseek\_usage
* mistral\_usage
* gemini\_usage
See full list of functions and their return values [below](#supported-response-formats).
### Charge for total tokens
If you want to charge for total tokens, you can add one billable metric: **Total tokens**.
Make sure to select the endpoint you want to bill for.
Navigate to the **Usage configuration** tab and configure the usage for the billable metric.
Set the **Quantity used by a single call** to:
```js theme={null}
openai_completions_usage().prompt_tokens + openai_completions_usage().completion_tokens
```
This will tell Nadles to record the total usage of input and output tokens.
The `openai_completions_usage()` function returns the usage of the input and output tokens for the OpenAI Completions API.
If you are using a different API, you can use the following functions:
* openai\_responses\_usage
* anthropic\_messages\_usage
* ollama\_usage
* openai\_completions\_usage
* deepseek\_usage
* mistral\_usage
* gemini\_usage
See full list of functions and their return values [below](#supported-response-formats).
## Add prices
Navigate to the **Prices** in the left menu and click **Add recurring price**.
This is a basic example of a price. You can configure more complex pricing models [here](/pricing/overview).
Enter price name (e.g., "Input tokens", "Output tokens", "Total tokens", etc.) and select the billable metric you want to price.
For pay per use, check the checkbox **Usage is metered**. Or for unmetered, don't check the checkbox and set the quantity as a hard limit (e.g., 1000 tokens).
Set the price per token. For convenience, you can also set a price per N tokens, e.g. \$0.5 per 1000 tokens or \$5 per 1M tokens.
Set the billing period (e.g., monthly, yearly, etc.).
Click on **Submit**.
## What's next
With this setup, Nadles will meter the usage of tokens, calculate the payment amount based on the actual usage and initiate payments for customers subscribed to this product.
[Set up the user portal](/user-portal/getting-started) to let your customers pay and start using your API.
## Force usage reporting
To make sure usage is reported by the LLM API if the response is streaming, you may want Nadles API Gateway to modify the request body to include the option to report usage.
Use [request transformation](/api-management/transformations#body) to achieve that.
Usually that is done by adding the following to request JSON body:
```json theme={null}
{
"stream_options": {
"include_usage": true
}
}
```
Nadles can add this parameter automatically for you.
Navigate to your API on Nadles and click on **Transformations**.
Click on **Add new request transformation**.
Choose **Target:** `Body`, **Action:** `Replace`, **Value is**: `Jq expression`.
Enter the following expression as a value:
```js theme={null}
.request.body | fromjson | if .stream == true then .stream_options = {"include_usage": true} else . end
```
Click on **Submit**.
Now, Nadles will automatically add the `{ stream_options: { include_usage: true }}` parameter to the request body if the response is streaming.
## Supported response formats
Nadles supports the following response formats, both streaming and non-streaming:
### OpenAI Completions
**Used by:** OpenAI Completions API, DeepSeek, Mistral. Also, Ollama supports this format.
**Function:** `openai_completions_usage()`
**Return value:**
```json theme={null}
{
"prompt_tokens": 100,
"completion_tokens": 1500,
"total_tokens": 1600
}
```
### OpenAI Responses
**Used by:** OpenAI Responses API. Also, Ollama supports this format.
**Function:** `openai_responses_usage()`
**Return value:**
```json theme={null}
{
"input_tokens": 100,
"input_tokens_details": {
"cached_tokens": 0
},
"output_tokens": 1500,
"output_tokens_details": {
"reasoning_tokens": 0
},
"total_tokens": 1600
}
```
### Anthropic models
**Used by:** Anthropic APIs.
**Function:** `anthropic_messages_usage()`
**Return value:**
```json theme={null}
{
"input_tokens": 0,
"output_tokens": 0,
}
```
### Ollama
In addition to OpenAI and Anthropic-compatible formats, Ollama uses its own response structure.
Its streaming responses differ by using NDJSON (newline-delimited JSON) instead of Server-Sent Events (SSE).
To enable Nadles to correctly process Ollama responses, use the following built-in function:
```js theme={null}
ollama_usage()
```
It returns the following object:
```json theme={null}
{
"prompt_eval_count": 100, // number of input tokens
"eval_count": 100 // number of output tokens
}
```
### Gemini
**Used by:** Gemini API.
**Function:** `gemini_usage()`
**Return value:**
```json theme={null}
{
"promptTokenCount": 0,
"candidatesTokenCount": 0,
"totalTokenCount": 0,
}
```
# Billable metrics
Source: https://docs.nadles.com/products/billable-metrics
Billable metrics are what you charge your customers for.
Common examples of billable metrics are:
* API calls
* Uploaded/downloaded bytes
* Characters in a text for a Text-to-Speech API
* Internal currency like "coins", "tokens" or "credits"
Try creating products from [templates](/products/create#product-templates) to see different billable metrics configurations in action.
Billable metrics are required in cases when
* You want to charge customers for something other than API calls.
* Your pricing includes pay-as-you-go.
* You'd like to let your customers adjust quantity when they make a purchase.
* You want to configure tiered pricing.
* You want to offer prepaid packages ("buy 1000 tokens, use and top up as needed").
* etc.
For simple scenarios like "Flat monthly fee + rate limit" you can skip billable metric configuration.
Nadles supports flexible pricing models, including prepaid, recurring, usage-based, and any combination of the three.
In case of **prepaid** pricing, your customers buy a certain volume of billable metrics units.
You can configure your products so that your customers are able to adjust the purchase quantity upon checkout, which is particularly useful in combination with tiered pricing and volume discounts — customers can choose, how many items they buy.
In case of **usage-based** pricing, Nadles automatically tracks the actual usage of billable metrics per customer and charges for the actual usage.
When Nadles API Gateway receives an API call, it calculates, how many billable metric units the current request uses, checks if the total usage exceeds the limit, and if not — continues with request processing.
If the limit is exceeded, the API gateway returns `429 Too Many Requests`.
Nadles API Gateway performs the following steps in order to check metrics limits and record the usage:
```mermaid theme={null}
graph TD
input[GET /endpoint1]
is_response_needed{"Is response
needed for
calculations?"}
proxy_and_wait[Proxy request and wait for response]
quota_condition_met{"Does current call consume
any billable metric units?"}
calculate_usage[Calculate metrics usage]
limit_exceeded{"Limit exceeded?"}
is_hard_limit{"Is there a limit?"}
return_429[Return 429 Too Many Requests]
record_usage[Record usage]
skip_quota_check[Skip and continue]
proxy[Continue request processing]
input --> is_response_needed
is_response_needed -->|Yes| proxy_and_wait
is_response_needed -->|No| quota_condition_met
proxy_and_wait --> quota_condition_met
quota_condition_met -->|No| skip_quota_check
quota_condition_met -->|Yes| calculate_usage
calculate_usage --> is_hard_limit
is_hard_limit -->|No| record_usage
is_hard_limit -->|Yes| limit_exceeded
limit_exceeded --> |No| record_usage
limit_exceeded --> |Yes| return_429
skip_quota_check ----> proxy
record_usage --> proxy
```
## Add billable metric
* Once you've created a product, navigate to **Billable metrics** in the left menu.
* Click **Add new metric**.
* Fill out the form.
### Metric name
Human-readable name of your billable metric. It will be displayed to your customers on the checkout page and in the user portal.
### Metric label
An identifier that Nadles uses internally to track usage and perform computations when your customers upgrade and downgrade subscriptions.
### Set hard limit
You can set a hard limit on the billable metric that is reset periodically.
This is mostly useful in two cases:
1. To limit usage in free products.
When you [add an unmetered price](/pricing/create-price#recurring-prices) to the product, you at the same time implicitly define a usage limit.
Free products usually have no prices and this setting comes in handy to limit the usage.
2. To cap usage in pay-per-use products.
Uncapped pay-per-use pricing poses a significant risk to customers: a mistake in application code or malicious activity can lead to unexpected and, potentially, enormous API usage charges.
The maximum usage limit setting provides a safeguard against excessive API usage that could lead to cost overruns for your customers. Once the limit is exceeded, the API gateway will automatically stop processing requests until the limit resets.
Nadles recognizes that usage needs vary. It allows you to grant customers the ability to adjust [their own API usage limits](#let-customers-adjust-limit).
#### Let customers adjust limit
If enabled, customers have the option to adjust the limit for their subscription at checkout or later, when the subscription is active.
### Associated endpoints
Here you select endpoints associated with the billable metric.
By default, each request to one of these endpoints will consume 1 unit. Per-endpoint metric usage can be configured after the billable metric is added to the product.
## Usage configuration
Once the billable metric is added, it's time to configure, how many units each request will consume.
* Navigate to the **Usage configuration** tab.
Here, for each endpoint you can configure two things:
* [Quantity used by a single call](#quantity-used-by-a-single-call)
* [Metric usage condition](#metric-usage-condition)
### Quantity used by a single call
This field defines, how many billable metric units are used by each call to this endpoint.
#### Static usage
By default, the field is set to "1", meaning, each request uses 1 unit.
When Nadles API Gateway receives a request to the endpoint, it records usage of 1 unit.
If you want requests to different endpoints to use a different number of units, set the respective values in the **Quantity used by a single call** field for each endpoint.
This is useful, for instance, in credit-based models when different endpoints cost different number of credits.
#### Dynamic usage
You can also use JavaScript expressions to make Nadles calculate usage for each request on the fly.
Instead of a static number like 1 or 2, you can specify a JavaScript expression that evaluates to an integer representing the usage for the current request based on the request/reponse attributes. Nadles evaluates this expression for each API call and records the result.
Request and response attributes are available as [variables](#expression-variables) that you can use in your JavaScript code.
In complex cases, you can also calculate usage for each call on your side and send it to Nadles along with the response.
See the examples below.
#### Example: send usage to Nadles in response header
Your API backend can tell Nadles directly, how much of the billable metric the current request has used.
For instance, if you bill for CPU seconds used to process the request and Nadles is unable to infer that value from the request/response on its own, you can calculate it in your API backend for each request and then tell Nadles the actual value it should record by adding a custom header to the response.
The header name doesn't matter, you can use any valid header name, e.g. `x-consumed-cpu-seconds`.
The expression in this case can be:
```js theme={null}
response.headers["x-consumed-cpu-seconds"]
```
How it looks in the **Quantity used by a single call** field:
And that's it. Nadles will wait for the response and record the value from the `x-consumed-cpu-seconds` header as usage for the current request.
Header names must be in lower case.
#### Example: variable usage depending on input parameter
The endpoint `GET /prompt/{LLM_MODEL}` allows sending textual prompts to two different AI models: `gpt3` and `gpt4`.
The API provider would like calls to `gpt4` to consume 2 quota units and calls to `gpt3` to consume 1 quota unit.
To achieve that, the expression should evaluate to `2` if the path parameter `LLM_MODEL` equals to `gpt4`, `1` otherwise.
E.g.:
```js theme={null}
path.params.LLM_MODEL == "gpt4" ? 2 : 1
```
How it looks in the **Quantity used by a single call** field:
Nadles API Gateway will check the value of the path parameter and record the usage of 2 units if `LLM_MODEL` equals `gpt4`, and 1 unit otherwise.
Path parameter names are **case-sensitive**.
#### Example: batch endpoints
Let's say, you want to bill for each item in the input array.
The endpoint `POST /process` accepts a JSON array of elements to process
and the request body looks like:
```json theme={null}
[
{ "data": "ZDU2OWZlODQtODdiZS00YzZjLTk5ODktYTdjNWRjMmQ5NWJj" },
{ "data": "YTQ5NGUyNWMtNDI2NS00MjkzLWJmYWEtNzY5MjQxZjhlYjI1" },
{ "data": "YWZiOTZhNTAtMWE1Zi00Zjg4LWJmMGMtMWVhODQ2ODY3NmVj" }
]
```
Nadles should record usage equal to the number of elements in the input array. The request in this example should use 3 quota units.
The JavaScript expression to parse the request body as JSON and return the number of elements:
```js theme={null}
JSON.parse(request.body).length
```
In the **Quantity used by a single call** field:
### Metric usage condition
By default, Nadles records usage for each request according to the expression in **Quantity used by a single call**, regardless of the response status code.
This field allows you to write a JavaScript expression to control, whether Nadles should record the usage for the current request or not.
The expression must evaluate to a **boolean** value.
Request and response attributes are available as [variables](#expression-variables) that you can use in your JavaScript code.
If the expression evaluates to true, the usage is recorded. If the expression evaluates to false, Nadles doesn't record usage for the current API call.
The most common use case for this config option is to make Nadles record quota usage only if response status code is 200.
For that, the `Quota usage condition` expression will be:
```js theme={null}
response.statusCode == 200
```
In the **Metric usage condition** field:
Configured this way, Nadles will record quota usage only if the API returned a response with HTTP status code `200 OK`.
### Expression variables
You can use the following variables in **Quantity used by a single call** and **Metric usage condition** expressions.
#### Request
##### Path parameters
`path.params.*` — placeholder values specified in the endpoint URL.
**Example**
If an endpoint URL is `/resource/{resourceId}`
and the HTTP request URL is `/resource/801d49c2-ca05-42b1-97af-baf0ddf36ba3`,
then there will be a variable `path.params.resourceId` with value `"801d49c2-ca05-42b1-97af-baf0ddf36ba3"`.
```js theme={null}
path.params.resourceId // "801d49c2-ca05-42b1-97af-baf0ddf36ba3"
```
Path parameter names are **case-sensitive**.
***
##### Client IP address
`request.remote_addr` — Client IP address.
***
##### Request headers
`request.headers['header-name']` — Request header values.
**Example**
```javascript theme={null}
request.headers['content-type'] == 'application/json'
```
Header names must be in lower case.
***
##### Request query string parameters
`request.query['query_string_parameter_name']` — Request query string parameters.
**Example**
```javascript theme={null}
request.query['page'] > 100
```
Query string parameter names are **case-sensitive**.
***
##### Request body
`request.body` — Request body.
**Example**
```javascript theme={null}
request.body.length > 1000
```
#### Response
##### Response status code
`response.statusCode` — HTTP status code of the response from the upstream.
**Example**
```javascript theme={null}
response.statusCode == 200
```
***
##### Response headers
`response.headers['header-name']` — Response header values.
**Example**
```javascript theme={null}
response.headers['content-type'] == 'application/json'
```
Header names must be in lower case.
***
##### Response body
`response.body` — Raw response body.
**Example**
```javascript theme={null}
response.body.length > 0
```
# Create product
Source: https://docs.nadles.com/products/create
Read the ["Overview"](/products/overview) to learn more about the product concept.
To create a new product:
* Navigate to **My Products** section and click **Add new product**.
* Enter product name in the form. Usual product names are Basic, Starter, Pro, Premium, etc.
* Next, select endpoints to be included in the product.
You can select endpoints from several APIs. In that case, only those added to the product will be accessible for the customer subscribed to this product.
* Click **Submit**.
## Product templates
Nadles provides ready-to-use product templates for popular pricing models. Start with a template that aligns with your vision and easily modify it to create a product that fits your business model perfectly.
## Included endpoints
Here you select endpoints included in the products. Only those selected will be available to users subscribed to this product.
## Maximum number of active access keys
Your customers can issue and revoke API access keys on their own in the user portal.
If you would like to limit the maximum number of simultaneously active access keys per subscription, specify the number in this field and the customer won't be able to create more active keys than defined by the product.
## Allow switching to following products
Nadles allows your customers to switch between products in one subscription, as configured by you.
This setting defines, to which products the customers will be able to switch from this one.
The list will be empty until you create more products.
We recomment to configure upgrades/downgrades after you've created all products.
If you select no products here, no subscription upgrades/downgrades will be possible.
# Modifying products
Source: https://docs.nadles.com/products/modifying-products
As a general rule, changes in products don't affect existing subscriptions.
However, there are several exceptions.
## Adding and removing endpoints
When you add a new endpoint to the product or remove an existing one, the following rules apply:
* If an endpoint is added to a product, it appears in existing subscriptions to that product too.
* If this endpoint is then deleted, it gets deleted from the existing subscriptions too.
* Endpoints added to a product before a subscription is created remain part of the subscription even if the endpoint is later deleted from the product.
* If a new endpoint is added to a billable metric, it is also added to the same billable metric in subscriptions.
* If an existing endpoint is removed from a billable metric, it doesn't get removed from the same billable metric in subscriptions.
## Adding and removing billable metrics
* If you add new billable metrics/rate limits to the product, they don't appear in the existing subscriptions.
* If you delete a billable metric from the product, it doesn't get deleted from existing subscriptions.
## Subscription upgrades/downgrades
Whenever you modify the [Allow switching to following products](/products/create#allow-switching-to-following-products) config of a product, it has effect on existing subscriptions.
# Overview
Source: https://docs.nadles.com/products/overview
Products are at the heart of Nadles. They define all aspects of your customers' future subscriptions. Think of them as subscription plans.
Nadles Products define resources/endpoints/features that your customers have access to, various limitations that apply, and pricing.
A single product can include any combination of endpoints from any number of your APIs — and Nadles will provide access according to the rules defined in the product.
Endpoints not included in the product are unavailable for customers subscribed to that product. Nadles API Gateway will reject requests to these endpoints.
When customer subscribes to a product, Nadles issues a new API key, and the customer is able to access all endpoints in the product using a single API key, even if there are several APIs included.
There are several important questions to answer before configuring your products with Nadles.
## What do I include in the product?
A product on Nadles can be comprised of:
* A single API.
Users subscribe and use API keys issued by Nadles to access it.
* Several APIs/API bundle.
In this case the user will be able to use the same API key for all APIs in the subscription.
* A subset of endpoints of an API.
The user will be able to access only the endpoints included in the product. This is useful for the case, when you are going to split endpoints into basic and premium.
**Example**
For a currency exchange rate API, you might want to grant access to historical data on a premium plan only.
In order to achieve that, you exclude the endpoint returning historical data from the basic product, but include it in the premium product.
* Any combination of endpoints from any number of APIs.
This allows for maximum flexibility when configuring your products.
## What do I charge for?
Modern best practices in API productization advise, instead of charging for a synthetic "number of API calls" metric, to charge for the actual value the customer gets from your API ([why?](/misc/faq)).
Here are some examples of product metrics:
* Number of images compressed by your API
* Number of characters translated by your AI translator
* Number of minutes of audio generated by your Text-To-Speech engine
* Number of e-mails/SMS sent via your API
Using product-relevant names for metrics lets your customers easily understand the offer and choose the right product to subscribe to.
Any product metric can be configured and used to bill your customers with Nadles.
## How do I structure my pricing?
Do I apply usage-based pricing model? Do I charge a flat fee? How often do I charge? Should there be a free tier?
## What limitations should I impose?
There is a big variety of limitations that can be imposed on API products.
By carefully thinking those through, you offer your customers fair terms that satisfy their needs and are equally profitable to your and them.
### Limits on billable metrics
Nadles allows to impose limits on billable metrics and periodically reset quotas after a configured period of time.
Here are some examples of quotas that can be defined in your products:
| API | Quota |
| -------------------------- | ------------------------------------------------------ |
| Generating audio from text | 100 of minutes of generated audio per month |
| Image compression | 100 compressed images, 1GB of processed data per month |
| AI-powered translation | 1 000 000 translated characters per week |
| E-mail, SMS API | 1 000 sent e-mails/SMS per month |
### Limits on input parameters
Sometimes it might be useful to limit the range of input parameters in basic products, while allowing the full range of values in premium products.
**Example**
| API | Parameter | Basic product | Premium product |
| ------------------- | ------------------- | ------------- | -------------------------- |
| Weather API | Historical data | max. 10 days | no limit |
| Mortgage calculator | Repayment intervals | Month | Week, 2 weeks, month, year |
| Image recognition | Input formats | JPEG | JPEG, PNG, TIFF, RAW |
Such limitations can be imposed using rejection rules that allow you to make Nadles reject requests based on various parameters and product-scoped request/response transformations that allow you to modify requests in order to, for instance, replace a query string parameter, a value in JSON body or header with some default value.
# Rate limits
Source: https://docs.nadles.com/products/rate-limits
## Define rate limits
* Navigate to **Products → choose a product → Limits**.
* Click **Add rate limit**.
A rate limit can be specified per N seconds/minutes/hours.
* Configure the new rate limit.
Rate limits can be applied to several endpoints. In that case, the rate limit will be shared by the selected of endpoints.
The endpoints can be from different APIs, the rate limit will work regardless.
## Set max burst
Max burst is the maximum number of HTTP requests that can be sent all at once without waiting.
It represents the largest “burst” of requests allowed by the system and is useful to allow short spikes in customer traffic.
We highly recommend setting max burst for your rate limits.
Defining rate limits without max burst may result in poor customer experience due to overly strict rate limiting that blocks *any* spikes in traffic.
**🔹 What Is "Max Burst" in Simple Terms?**
Suppose a server lets a client send:
**5 HTTP requests per second** on average, but also allows **short bursts** of traffic above that rate.
This is where max burst comes in.
**🔹 Max Burst Explained**
Max burst is the maximum number of requests your client is allowed to send all at once, even if that’s more than the usual allowed rate.
For example:
Rate limit: **5 requests per second**
Max burst: **20 requests**
This means your app can **send up to 20 requests instantly, but only occasionally**.
**🔹 What Happens After a Burst?**
If your app sends **20 requests at once**, that's fine — **but only once** in that short period.
After that:
Your rate is **above the allowed 5 per second**,
so the server will block or delay any new requests
until your average rate goes back down to the allowed level.
In simple terms:
> **You will be blocked from sending more requests until enough time has passed to “cool down” the rate.**
**🔹 Visual Example**
You're allowed to send **5 requests per second**,
but you’re also allowed to send up to **20 requests at once** — that’s your **max burst**.
Here's what happens:
**1. At 0 seconds:**
You send **20 requests all at once** — that’s okay, you’re using your max burst.
**2. What now?**
You’ve just used up what you'd normally send over 4 seconds (because 5 requests per second × 4 seconds = 20 requests).
**3. So you need to wait:**
You must now **wait at least 4 full seconds** before sending any more requests,
or the server will block or delay your next ones.
**4. If you wait less than 4 seconds (like 2 or 3):**
The server will see that you're going over the allowed rate,
and it will start rejecting or slowing down your requests.
**✅ Summary**
After sending a **burst of 20 requests**,
you must **wait 4 seconds** before sending more,
so your average request rate stays within the allowed **5 per second**.
# Rejection rules
Source: https://docs.nadles.com/products/rejection-rules
Rejection rules are JavaScript expressions that let Nadles API Gateway decide, whether current request should be rejected.
If an expression evaluates to true, Nadles API Gateway will reject the request.
It's useful for implementing premium features and impose limitations on input parameters.
Some use cases for rejection rules:
Reject request if:
* The customer tries to request more than 100 elements:
`request.query.num > 100`
* The customer tries to request more than 10 pages of results:
`request.query.page > 10`
* Number of elements in the input JSON array is bigger than 50:
`JSON.parse(request.body).batch.length > 50`
One rejection rule can be applied to several endpoints. The expression will be evaluated for each call to any of the selected endpoints and if the result is true, the call will be rejected.
## Add rejection rule
* Navigate to **Products → choose a product → Limits**.
* Click **Add rejection rule**.
* Enter the expression, select the endpoints this rule applies to, and click **Save**.
* The rejection rule is now added to the product.
## Expression variables
There is a number of variables that can be used in the expression.
Note that response variables are not available, since rejection rules are evaluated **before** proxying the request.
##### Path parameters
`path.params.*` — placeholder values specified in the endpoint URL.
**Example**
If an endpoint URL is `/resource/{resourceId}`
and the HTTP request URL is `/resource/801d49c2-ca05-42b1-97af-baf0ddf36ba3`,
then there will be a variable `path.params.resourceId` with value `"801d49c2-ca05-42b1-97af-baf0ddf36ba3"`.
```js theme={null}
path.params.resourceId // "801d49c2-ca05-42b1-97af-baf0ddf36ba3"
```
Path parameter names are **case-sensitive**.
***
##### Client IP address
`request.remote_addr` — Client IP address.
***
##### Request headers
`request.headers['header-name']` — Request header values.
**Example**
```javascript theme={null}
request.headers['content-type'] == 'application/json'
```
Header names must be in lower case.
***
##### Request query string parameters
`request.query['query_string_parameter_name']` — Request query string parameters.
**Example**
```javascript theme={null}
request.query['page'] > 100
```
Query string parameter names are **case-sensitive**.
***
##### Request body
`request.body` — Request body.
**Example**
```javascript theme={null}
request.body.length > 1000
```
# Request / response transformations
Source: https://docs.nadles.com/products/transformations
Product-scoped modifications of requests and responses.
## Overview
Transformations allow you to dynamically modify requests and responses. That includes headers, query string parameters and body.
Product-scoped transformations are applied to requests/responses only if the customer is subscribed to this product.
Transformations defined on an API level are always applied first.
Read about [API-scoped transformations](/api-management/transformations).
## How it works
```mermaid theme={null}
graph TD
input[\"GET /endpoint1"/]
apply_api_req_tfs[Apply API-level request transformations]
apply_product_req_tfs[Apply product-level request transformations]
proxy_and_wait(("Proxy modified request
and wait for response"))
apply_api_resp_tfs[Apply API-level response transformations]
apply_product_resp_tfs[Apply product-level response transformations]
send_response[\\Send modified response to the user/]
input --> apply_api_req_tfs
apply_api_req_tfs --> apply_product_req_tfs
apply_product_req_tfs --> proxy_and_wait
proxy_and_wait --> apply_api_resp_tfs
apply_api_resp_tfs --> apply_product_resp_tfs
apply_product_resp_tfs --> send_response
```
To configure transformations, navigate to **Products → select product → Transformations**.
## Headers
The transformation will add/replace/remove headers in a request or response.
Header names are **case-insensitive**.
### Add
Adds a new header, even if there is already one with the same name.
**Example**
Original request:
```
GET / HTTP/1.1
X-Some-Header: some-value
```
Transformation:
```
Add header "X-Some-Header" with value "another-value".
```
Transformed request:
```
GET / HTTP/1.1
X-Some-Header: some-value
X-Some-Header: another-value
```
### Replace
Replaces header(s) with a new value.
If there are several headers with the given name, they're removed and replaced with the new value.
**Example**
Original request:
```
GET / HTTP/1.1
X-Some-Header: first-value
X-Some-Header: second-value
```
Transformation:
```
Replace header "X-Some-Header" with value "third-value".
```
Transformed request:
```
GET / HTTP/1.1
X-Some-Header: third-value
```
### Remove
Removes all headers with the given name.
**Example**
Original request:
```
GET / HTTP/1.1
X-Some-Header: first-value
X-Some-Header: second-value
```
Transformation:
```
Remove header "X-Some-Header".
```
Transformed request:
```
GET / HTTP/1.1
```
## Query string parameters
The transformation will add/replace/remove query string parameters in a request.
Query string parameter names are **case-sensitive**.
### Add
Adds a new query string parameter, even if there is already one or more with the same name.
**Example**
Original request:
```
GET /?param1=100 HTTP/1.1
```
Transformation:
```
Add query string parameter "param2" with value "test-value".
```
Transformed request:
```
GET /?param1=100¶m2=test-value HTTP/1.1
```
### Replace
Replaces query string parameter(s) with a new value.
If there are several query string parameters with the given name, they're removed and replaced with the new value.
**Example**
Original request:
```
GET /?param1=100 HTTP/1.1
```
Transformation:
```
Replace query string parameter "param1" with value 200.
```
Transformed request:
```
GET /?param1=200 HTTP/1.1
```
### Remove
Removes all query string parameters with the given name.
**Example**
Original request:
```
GET /?param1=100 HTTP/1.1
```
Transformation:
```
Remove query string parameter "param1".
```
Transformed request:
```
GET / HTTP/1.1
```
## Body
The transformation will add/replace/remove request/response body.
### Add
Adds a new body. If a request or response already has a body, it's left unchanged.
**Example**
Original request:
```
POST / HTTP/1.1
```
Transformation:
```
Add body with value {"testkey": "testvalue"}
```
Transformed request:
```
POST / HTTP/1.1
{"testkey": "testvalue"}
```
### Replace
Replaces the body with a new value. If there is no body yet, it's added.
**Example**
Original request:
```
POST / HTTP/1.1
{"originalkey": "originalvalue"}
```
Transformation:
```
Replace body with value {"newkey": "newvalue"}
```
Transformed request:
```
POST / HTTP/1.1
{"newkey": "newvalue"}
```
### Remove
Removes the body.
**Example**
Original request:
```
POST / HTTP/1.1
{"key": "value"}
```
Transformation:
```
Remove body.
```
Transformed request:
```
POST / HTTP/1.1
```
## Expressions
### JavaScript
You can use JavaScript expressions in your transformations to make them even more dynamic.
Expressions must evaluate to a new string value for the header/query string parameter/body.
#### Examples
**Example — News API**
Say, you provide an API endpoint that returns contents of a news article. E.g.,
```json theme={null}
{
"id": "b6355c53-44a3-464b-bc9e-759749cdb3ec",
"title": "The title",
"content": "Some long article body"
}
```
For your "Free" product, you'd like to return only the first 120 characters of article body.
With the following transformation added to the "Free" product, you configure Nadles API Gateway to do that:
Note that the expression evaluation result is used as the **new value** for the response body.
#### Expression variables
There are several variables you can use in your expressions.
##### Path parameters
`path.params.*` — placeholder values specified in the endpoint URL.
**Example**
If an endpoint URL is `/resource/{resourceId}`
and the HTTP request URL is `/resource/801d49c2-ca05-42b1-97af-baf0ddf36ba3`,
then there will be a variable `path.params.resourceId` with value `"801d49c2-ca05-42b1-97af-baf0ddf36ba3"`.
```js theme={null}
path.params.resourceId // "801d49c2-ca05-42b1-97af-baf0ddf36ba3"
```
Path parameter names are **case-sensitive**.
***
##### Client IP address
`request.remote_addr` — Client IP address.
***
##### Request headers
`request.headers['header-name']` — Request header values.
**Example**
```javascript theme={null}
request.headers['content-type'] == 'application/json'
```
Header names must be in lower case.
***
##### Request query string parameters
`request.query['query_string_parameter_name']` — Request query string parameters.
**Example**
```javascript theme={null}
request.query['page'] > 100
```
Query string parameter names are **case-sensitive**.
***
##### Request body
`request.body` — Request body.
**Example**
```javascript theme={null}
request.body.length > 1000
```
***
Response variables are only available in **response** transformations.
##### Response status code
`response.statusCode` — HTTP status code of the response from the upstream.
**Example**
```javascript theme={null}
response.statusCode == 200
```
***
##### Response headers
`response.headers['header-name']` — Response header values.
**Example**
```javascript theme={null}
response.headers['content-type'] == 'application/json'
```
Header names must be in lower case.
***
##### Response body
`response.body` — Raw response body.
**Example**
```javascript theme={null}
response.body.length > 0
```
### jq
You can also use [jq](https://jqlang.github.io/jq/manual/) expressions for transformations.
> jq is like sed for JSON data - you can use it to slice and filter and map and transform JSON with the same ease that sed, awk, grep and friends let you play with text.
Unlike JavaScript expressions, the result of jq transformation does not necessarily have to be a string.
If the expression result is not a string, it's encoded as JSON.
#### Examples
**Example — News API**
Say, you provide an API endpoint that returns contents of a news article. E.g.,
```json theme={null}
{
"id": "b6355c53-44a3-464b-bc9e-759749cdb3ec",
"title": "The title",
"content": "Some long article body"
}
```
For your "Free" product, you'd like to return only the first 120 characters of article body.
The following jq expression will do the job:
Note that the expression evaluation result is used as the **new value** for the response body.
#### Expression input
The followind is passed as input (`.`) to your jq expressions:
```json theme={null}
{
"path": {
"params": { // path parameters, e.g. /endpoint/{path_param_1}
"path_param_1": "path_param_value_1",
"path_param_2": "path_param_value_2"
}
},
"request": {
"remote_addr": "127.0.0.1", // client IP address
"headers": { // request headers
"request_header_name_1": "header_value_1",
"request_header_name_2": ["header_values_2", "header_values_2"]
},
"query": { // query string parameters
"query_string_param_1": "query_string_param_value_1"
},
"body": "request body content"
},
}
```
```json theme={null}
{
"path": {
"params": { // path parameters, e.g. /endpoint/{path_param_1}
"path_param_1": "path_param_value_1",
"path_param_2": "path_param_value_2"
}
},
"request": {
"remote_addr": "127.0.0.1", // client IP address
"headers": { // request headers
"request_header_name_1": "header_value_1",
"request_header_name_2": ["header_values_2", "header_values_2"]
},
"query": { // query string parameters
"query_string_param_1": "query_string_param_value_1"
},
"body": "request body content"
},
"request_transformed": { // request object, after request transformations are applied
// ...
},
"response": {
"statusCode": 200,
"headers": {
"response_header_name_1": "header_value_1",
"response_header_name_2": ["header_values_2", "header_values_2"]
},
"body": "response body content"
}
}
```
# Quickstart
Source: https://docs.nadles.com/quickstart
Start collecting payments for your API in under 10 minutes.
## Add your API
## Create product
## Activate user portal
## Set up payment integration
## Create checkout link
## Done!
You're all set. Now you can use the checkout link as URL for the "Subscribe" button on your website — Nadles takes care of your customer's journey after that.
Ensure smooth user experience with personalized branding for your portal.
What pricing models can be configured and how.
Get insights about the usage patterns and find bottlenecks.
Keep your user portal on your own website's subdomain.
# One-time charges
Source: https://docs.nadles.com/subscriptions/one-time-charges
## Overview
The Nadles Billing Engine allows API providers to create one-time charges within an existing subscription. This feature gives you more flexibility in managing billing scenarios that go beyond regular recurring payments.
## When to Use One-Time Charges
One-time charges can be used in situations such as:
* **Retrying a failed payment**: If a customer's recurring charge fails, you can create a one-time charge to reattempt collection without modifying the subscription cycle.
* **Advance payment for high usage**: If a customer's API usage exceeds expected limits before the end of the billing period, you can issue an additional charge to cover the extra cost in real time.
This capability helps API providers maintain revenue continuity and manage usage-based pricing more dynamically.
## How It Works
One-time charges are linked to an existing subscription and are processed through the Nadles Billing Engine. The charge is added on top of the regular recurring billing and appears as a separate line item on the customer’s invoice.
You can trigger these charges from the subscription product dashboard in your API provider control panel.
# Overview
Source: https://docs.nadles.com/subscriptions/overview
Subscription lifecycle on Nadles begins with creating an order.
When a customer clicks **Subscribe** on the checkout page, Nadles creates an order which expires in 24 hours if not paid.
Before taking the customer to the payment page, Nadles collects billing address to make sure invoices are issued correctly.
After a successful payment, the customer is returned to the subscription dashboard where all details about pricing, current usage and included API endpoints are displayed:
On the subscription page, the customer can
* Manage API access keys.
* Update payment method.
* View the full transaction history.
* Switch the subscription to a different product, if that is allowed by the configuration.
* Top up prepaid items, if allowed.
* Cancel subscription.
## Cancellation
Subscriptions are not canceled immediately. When the user clicks **Cancel subscription**, the cancellation is scheduled for the nearest billing date.
This helps avoid unnecessary refunds.
# Upgrades and downgrades
Source: https://docs.nadles.com/subscriptions/upgrades-and-downgrades
## Overview
When you're configuring a product, you can specify, to [which products](/products/create#allow-switching-to-following-products) customers can upgrade and downgrade subscriptions.
The "**Change subscription**" button on the customer subscription dashboard opens a modal window for customers to select the product.
Upgrades and downgrades are a complex topic. Read below about different aspects of switching subscriptions to another product.
## Unmetered items
When a subscription is switched to a product that contains the same [billable metric](/products/billable-metrics)
and an [unmetered price](/pricing/create-price#recurring-prices) with the same billing period, the billable metric's usage counter is not reset.
If the new price for the billable metric has a different billing period, the usage counter is reset to zero.
## Metered items
When a subscription upgrade or downgrade occurs, usage for [metered prices](/pricing/create-price#recurring-prices) for the current billing period is aggregated and billed immediately.
If the target product also contains pay-per-use items, the usage tracking starts from the moment of product switch.
## One-off items
[One-off items](/pricing/create-price#one-off-prices) remain in the subscription during upgrades and downgrades, and their usage data is also retained.
## Anchors
Billing cycle anchor is the starting date for the calculation of all recurring periods.
Nadles sets the anchors to the date and time of subscription creation.
Upgrades and downgrades do not affect the anchor date and the new prices' billing periods are also calculated starting from that date.
In order to ensure precise and fair billing for your customers, Nadles applies [prorations](#prorations).
## Prorations
When customers upgrade subscriptions, the unused amount from recurring unmetered prices is prorated and credited to the subscription balance.
This amount is then deducted from upcoming payments until the credit balance is zero.
The new prices are also prorated and the amount due at the moment of the subscription upgrade or downgrade is calculated for the time until the end of the price's billing period.
If that sounds too complex, see the example below.
**Upgrade example**:
Subscription started on Jan 1, 2024.
Product: "Basic", \$100 per month.
On Jan 26, the subscription is upgraded to the "Advanced" product: \$300 per month.
The subscription has 5 unused days for the price from "Basic" product.
Nadles calculates the unused amount as follows: `$100 * (5 / 31) = $16.13` and credits \$16.13 to the subscription balance.
For the new price, the remaining time until the next billing period is also 5 days.
Nadles calculates the amount due for the remaining time as follows: `$300 * (5 / 31) = 48.39`.
The final payment amount due at the moment of upgrade is: `$48.39 — $16.13 = $32.26`
— this is the actual amount the customer will pay for the upgrade.
On Feb 1, the customer will be charged the full amount of \$300 for the next billing period.
**Downgrade example**:
Subscription started on Jan 1, 2024.
Product: "Advanced", \$300 per month.
On Jan 26, the subscription is downgraded to the "Basic" product: \$100 per month.
The subscription has 5 unused days for the price from "Advanced" product.
Nadles calculates the unused amount as follows: `$300 * (5 / 31) = $48.39` and credits \$48.39 to the subscription balance.
For the new price, the remaining time until the next billing period is also 5 days.
Nadles calculates the amount due for the remaining time as follows: `$100 * (5 / 31) = 16.13`.
The final payment amount due at the moment of upgrade is: `$16.13 - $48.39 = -$32.26`.
Since the credit balance is greater than the amount due, the amount due is simply deducted from the credit balance which now becomes \$32.36.
On Feb 1, the customer should be charged \$100 for the next billing period of the new price.
But since there is a remaining credit balance, it is applied to the payment and the actual charge will be `$100 - $32.36 = $67.64`.
## Upgrading subscriptions from the API provider control panel
**TL;DR:**
There are two ways to upgrade a customer’s subscription in Nadles:
**Create an Upgrade Link →** Sends the customer to checkout to confirm and pay if needed.
**Manual Upgrade →** Upgrade directly from the admin panel, only if no payment or payment setup is required.
### Create an upgrade link
An upgrade link takes customers to the checkout page, where they can review the order and complete the upgrade themselves.
This approach is especially useful when:
* The upgrade involves a non-catalog, private, or exclusive product not visible in the public portal.
* You want the customer to explicitly confirm the change and handle any required payment or billing updates.
Once generated, you can send the link to the customer via email, chat, or your support system.
### Manually upgrade the subscription
You can also perform an upgrade directly from the provider admin panel, without requiring the customer to take action.
**Note:** This method works only when the upgrade can be completed without:
* A payment from the customer at the time of upgrade, or
* The customer needing to set up a payment method.
**Example – Suitable Scenario**
Upgrading a customer from one paid plan to a higher-priced paid plan when their payment method is already on file.
**Example – Scenario Requiring Checkout**
Upgrading a subscription from a free plan to a paid plan when no payment method is set up yet.
In such cases, you should **[create an upgrade link](#create-an-upgrade-link)** instead, which will guide the customer through the checkout process to add a payment method and complete the upgrade.
# Adding pages
Source: https://docs.nadles.com/user-portal/adding-pages
You can add fully customized pages to the Nadles User Portal using our built-in dynamic routing system.
These blank pages inherit your portal's layout, including navigation menus, headers, footers, and brand styling, making them a seamless extension of the existing UI.
## URL Pattern
Custom pages are accessible under a special URL pattern:
```
https://app.yourportaldomain.com/consumer/static/pages/*
```
Replace the asterisk (`*`) with any path you want. For example:
* `https://app.yourportaldomain.com/consumer/static/pages/help`
* `https://app.yourportaldomain.com/consumer/static/pages/chat`
* `https://app.yourportaldomain.com/consumer/static/pages/custom-dashboard`
When a user visits one of these URLs, a blank content area is rendered between the standard user portal layout elements (header, nav, footer). You can then populate this area dynamically [using JavaScript](/user-portal/html-and-javascript).
## How It Works
### 1. Page Detection
When the page loads, use JavaScript to check the current URL and determine which content should be displayed.
### 2. Inject Custom Content
Use JavaScript (or a front-end framework like React or Vue) to inject your desired content into the page. You can also interact with the Nadles User Portal API to retrieve customer data, subscription info, or usage stats.
### 3. Consistent Styling
Your custom content will automatically inherit the styling of your user portal, so it will match the rest of the interface without additional effort.
#### Example
```javascript theme={null}
const url = window.location.pathname;
if (url.includes('/consumer/static/pages/help')) {
const container = $('div.content-wrapper > div.container-xxl');
container.html(`
Help & Support
Here you can find answers to frequently asked questions.
`);
}
```
## Use Cases
* FAQs / Help Center
* Custom Dashboards
* Embedded Widgets
* Onboarding Tutorials
* Live Chat or Feedback Forms
## Best Practices
* Always check for URL patterns before injecting content.
* Keep JavaScript lightweight for fast page loads.
* Use the [Nadles User Portal API](/user-portal/api) for secure, authenticated access to customer data.
* Test your custom pages on multiple screen sizes for responsive behavior.
# API
Source: https://docs.nadles.com/user-portal/api
The Nadles User Portal includes a built-in API that allows you to programmatically access user portal data directly from within the user portal. This API is ideal for customizing user experiences, building custom dashboards, or integrating client-side tools that require user-specific information.
You can use this API directly in [JavaScript injected into user portal pages](/user-portal/html-and-javascript). All requests are scoped to the currently logged-in user, and no additional authentication is required when used within the portal environment.
## Base URL
```
https:///api/v0/
```
### `GET /customer`
Returns the current user's account data.
**Example Request**
```
GET /api/v0/customer
```
**Example Response**
```json theme={null}
{
"id": "5a1ea91e-95bc-4d75-90cd-3714d02a66d8",
"name": "Demo Customer",
"email": "demo@example.com",
"billing_address_line1": "Unter den Linden, 3",
"billing_address_line2": null,
"billing_address_city": "Berlin",
"billing_address_country_code": "DE",
"billing_address_zip_code": "10115",
"billing_address_region": null,
"is_business": true,
"business_name": "Muster GmbH",
"company_number": "HRB12345",
"tax_identifier": "12/345/67890"
}
```
### `GET /subscriptions/`
Returns a paginated list of the current user’s subscriptions.
**Query Parameters**
| Parameter | Type | Description |
| --------- | ------ | ------------------------------------- |
| `state` | string | Optional. One of `active`, `canceled` |
| `offset` | int | Optional. Pagination offset |
| `limit` | int | Optional. Max number of results |
**Example Request**
```
GET /api/v0/subscriptions/?state=active&offset=0&limit=10
```
**Example Response**
```json theme={null}
{
"data": [
{
"id": "00000000-0000-0000-0000-000000000000",
"created_at": "2025-06-25T14:20:00+00:00",
"updated_at": "2025-06-25T14:20:00+00:00",
"vendor_id": "00000000-0000-0000-0000-000000000000",
"product_id": "00000000-0000-0000-0000-000000000000",
"customer_id": "00000000-0000-0000-0000-000000000000",
"order_id": "00000000-0000-0000-0000-000000000000",
"state": "active",
"psp_name": "stripe",
"billing_version": 2,
"is_cancelation_scheduled": false,
"items": [
{
"billing_started_at": "2025-06-25T14:20:00.171519Z",
"created_at": "2025-06-25T14:20:00.623556Z",
"id": "00000000-0000-0000-0000-000000000000",
"next_billed_at": "2025-08-25T14:20:00.171519Z",
"price": {
"aggregation_period": {
"time_unit": "",
"value": 0
},
"base_product_id": "00000000-0000-0000-0000-000000000000",
"billing_period": {
"time_unit": "month",
"value": 1
},
"created_at": "2025-06-25T14:19:21.71264Z",
"currency": "USD",
"customer_id": "00000000-0000-0000-0000-000000000000",
"id": "00000000-0000-0000-0000-000000000000",
"is_metered": false,
"is_quantity_adjustable": false,
"is_topup_enabled": false,
"model": "flat_fee",
"name": "Monthly fee",
"package_quantity": 0,
"parent_id": "00000000-0000-0000-0000-000000000000",
"price": "5",
"priced_items": [],
"product_id": "00000000-0000-0000-0000-000000000000",
"quantity": 0,
"quantity_adjustment_max": 0,
"quantity_adjustment_min": 0,
"subscription_id": "00000000-0000-0000-0000-000000000000",
"tiers": [],
"type": "recurring",
"updated_at": "2025-06-25T14:19:21.71264Z",
"vendor_id": "00000000-0000-0000-0000-000000000000"
},
"price_id": "00000000-0000-0000-0000-000000000000",
"quantity": "0",
"subscription_id": "00000000-0000-0000-0000-000000000000",
"updated_at": "2025-07-25T14:20:00.748826Z"
}
],
"activated_at": "2025-06-25T14:20:00+00:00",
"expired_at": null,
"cancellation_scheduled_at": null,
"canceled_at": null,
"past_due_at": null
}
],
"metadata": {
"pagination": {
"limit": 50,
"offset": 0,
"total": 100
}
}
}
```
### `GET /subscriptions/{subscriptionId}/access-tokens/`
Returns a list of access tokens for a given subscription.
**Path Parameters**
| Parameter | Type | Description |
| ---------------- | ------ | ---------------------- |
| `subscriptionId` | string | ID of the subscription |
**Example Request**
```
GET /api/v0/subscriptions/00000000-0000-0000-0000-000000000000/access-tokens/
```
**Example Response**
```json theme={null}
{
"data": [
{
"id": "00000000-0000-0000-0000-000000000000",
"name": "Production access",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6IjEifQ.eyJpc...",
"issued_at": "2025-06-25T14:20:01+00:00",
"expires_at": null,
"is_expired": false
}
],
"metadata": {
"pagination": {
"limit": 10,
"offset": 0,
"total": 1
}
}
}
```
## Using the API
You can call this API from [JavaScript injected into user portal pages](/user-portal/html-and-javascript).
### Example: Fetch current customer data
```javascript theme={null}
fetch('/api/v0/customer')
.then(res => res.json())
.then(data => {
console.log("Customer Info:", data);
});
```
## Notes
* All endpoints are automatically authenticated in the user portal.
* CORS is fully supported for use in client-side JavaScript within the portal.
* Responses are JSON-formatted.
# Checkout links
Source: https://docs.nadles.com/user-portal/checkout-links
## Overview
Checkout Links are the entry points for your products.
They allow you to instantly set up a [checkout page](https://checkout.nadles.com/yM8K6nRL2zHsUAQGRdiM7f) without any coding.
It's the place where your customer's subscription journey begins.
Just create a checkout link for your API product and get a beautiful checkout page where customers can subscribe to your API, pay and start using it.
## Generate new link
* Navigate to **My User Portal → Checkout Links**.
* Click **Generate new checkout link**.
* Fill out the form:
**Product**
Select the product that will be presented on this checkout page. You can't change it after the checkout link is generated.
**Link name**
The name of the link.
It will help you remember where you used this link.
Example names are: `Landing page`, `Developer portal`, `Pricing page`.
**Maximum number of subscriptions**
If needed, specify the maximum number of subscriptions that can be created using this link.
It allows you to limit, how many customers can subscribe via this link.
It can be useful if you'd like to share the product with one customer — just specify `1` here and the link will get inactive once a customer creates a new subscription.
Or if you'd like to offer a limited number of seats for a special offer — set the maximum number of subscriptions here.
**Checkout page theme**
Nadles supports custom styles for your checkout pages.
You can configure the checkout page style to match your unique corporate identity.
* Click **Generate new link**.
## Use checkout links
* Copy the link and...
* Use it as URL for the **Subscribe** button on your landing page.
* Post it on Reddit.
* Send it via WhatsApp or by email.
## Checkout links and user portal
Once you've activated the user portal, checkout links get new URLs leading to your user portal instead of `https://checkout.nadles.com`.
If you used the checkout links on Nadles domain before, make sure to replace the URLs with the new ones.
Checkout page themes are overridden by the custom CSS styling from your user portal settings.
# Custom domain
Source: https://docs.nadles.com/user-portal/custom-domain
## General
You can attach a custom domain name to your Nadles User Portal, so that, instead of Nadles domain (`https://.up.nadles.com/`), users access your portal by the domain name owned by you.
Attaching your own domain updates checkout link URLs to point to your user portal.
In order to successfully complete the setup, you must be able to edit DNS records of the domain name.
## Setting up a custom domain name
To start, navigate to **My User Portal → Settings**.
Enter the domain name you'd like to use for your user portal in the **Custom domain name** section.
By clicking **Submit** you initiate a domain ownership verification process.
## Ownership verification
To verify ownership, you need to add two DNS records.
* TXT record for the base domain with a unique value generated by Nadles.
* CNAME record for the desired subdomain referencing the Nadles domain name for user portals, `up.nadles.com`.
In this example we're attaching `app.myfirstapi.com`, so the two records to add to `myfirstapi.com` are:
| Type | Name | Value |
| :---- | ---- | ------------------------------------------ |
| TXT | @ | nadles\_verification\_8KcfbFwYGieHpqTY8... |
| CNAME | app | up.nadles.com |
After you've added the records, click **Check DNS propagation status** and make sure that the records have propagated to the majority of the servers.
Then click **Verify DNS records** to make Nadles verify the ownership and issue a SSL certificate.
And your custom domain name is displayed everywhere instead of the standard URL assigned by Nadles.
# Getting started
Source: https://docs.nadles.com/user-portal/getting-started
## Overview
Nadles offers you a hosted white-label user portal where your customers can manage their subscriptions, payment methods, track usage and payment history.
See the [demo user portal](https://app.demoapiprovider.eu/) in dark colors.
To make Nadles invisible to your customers, you need to enable the user portal, apply your business style to it and attach custom domains to the API and user portal.
This is useful when you have a landing page in your corporate style and would like to ensure a seamless user experience.
## Prerequisites
* You need a Google Recaptcha V3 site and secret key. Google Recaptcha is used to protect the registration form from bot sign ups.
## User portal URL
By default, Nadles domain `https://.up.nadles.com` is assigned to your user portal. Later you can attach your own domain to it.
```
https://.up.nadles.com
```
## Activating user portal
* Navigate to **My User Portal → Settings**.
* Enter the subdomain for your user portal.
* Enter Recaptcha site and secret keys in the respective fields.
* Click **Submit**.
## Settings
On the settings page you can attach your own domain to the user portal,
specify the support email that is displayed to your customers,
customize the look and feel of your user portal
and set up social login.
## Links
The "Links" section allows you to add custom links to the user portal.
When adding a new link, you need to specify the link type, name and the URL.
You can pick from the following link types:
* Legal agreement
* OpenAPI specification
* API documentation
### Legal agreements
By defauly, Nadles displays its own legal agreements on the registration page and in the footer of the user portal.
You can add your own legal agreements by adding more links of type "Legal agreement" in the user portal settings.
### OpenAPI specification
If you add a link of type "OpenAPI specification", Nadles will render the OpenAPI specification on a separate page and add a menu item for it.
You can add multiple links of this type.
### API documentation
Nadles will add this link to the Documentation menu item.
## Social login buttons
Nadles allows users to log in via Google, GitHub and GitLab social login.
You need accounts on these platforms in order to make social login buttons work.
Authorized redirect URIs are:
* For Google: `https:///login/check-google`
* For GitHub: `https:///login/check-github`
* For GitLab: `https:///login/check-gitlab`
# HTML and JavaScript
Source: https://docs.nadles.com/user-portal/html-and-javascript
## Custom HTML & JavaScript for User Portals
Nadles allows you to customize your hosted user portal with free-form HTML and JavaScript, enabling deeper branding and functional customization — including analytics, chat widgets, or style overrides.
## Requirements
To enable this feature, you must:
* Have User Portal enabled for your account.
* Attach a [custom domain](/user-portal/custom-domain) to your portal (e.g., app.yourcompany.com).
Once a custom domain is set up, you’ll be able to inject your own HTML and JavaScript directly into the user-facing portal pages.
## Where Custom Code is Applied
Your HTML and JavaScript will be applied to all pages in the User Portal.
This allows you to fully customize the look, feel, and behavior of the portal experience for your users.
## Security Limitations
To maintain security and prevent misuse, custom HTML and JavaScript are not applied on login, signup, password reset pages and the likes.
These pages are excluded to protect user credentials and prevent potential injection-based attacks.
## How to Add Custom Code
Go to the User Portal section of your Nadles dashboard.
Under Free-form HTML, you’ll find two text areas:
**Position** – Where to inject the code, i.e. after the opening `` tag, before the closing `` tag, etc.
**HTML** – The free-form HTML to be injected into pages.
Save your changes and visit your user portal on the custom domain to see them live.
If you remove your custom domain, the custom code will no longer be applied.
## Advanced Use: Blank Pages + User Portal API
To take customization even further, Nadles allows you to:
Add blank pages to the user portal (fully empty canvas with your styles applied — only menus, headers and footers).
Use the [User Portal API](/user-portal/api) to fetch:
* Current user info
* Subscription data
* Access tokens for customer subscriptions
By combining these features, you can build fully interactive, user-aware pages — directly inside the portal.
# Pricing page
Source: https://docs.nadles.com/user-portal/pricing-page
## Overview
Pricing page displays your selected products including prices, descriptions, and "Subscribe" buttons.
It also replaces the default index page in your user portal that users see when they have no active subscriptions.
That eliminates the need for customers to return to your website from the user portal to select the product to subscribe, which makes the subscription process smoother and increases your conversion rate.
## Public pricing page
The pricing page is also available to non-authenticated users.
If you want to add a pricing page link to your website, use the following URL:
```
https:///pricing/
```
## Embedded pricing page
If you would like to embed the product list in your website, use the following URL:
```
https:///pricing/embedded/
```
This removes all elements other than the product list from the pricing page.
```
```
## Adding products to the pricing page
In order to select a product to be shown on the pricing page, do the following:
* Navigate to **My Products → select product → Settings**.
* Scroll down to the "Pricing page" section.
* Enable "Show on pricing page in user portal"
* Specify product order on the pricing page.
* Fill in the description that will be shown under the pricing info. The field supports Markdown syntax.
# Styling
Source: https://docs.nadles.com/user-portal/style-user-portal
## General
You can use free-form CSS to completely change the look of your user portal.
Navigate to the [Settings page](/user-portal/getting-started#settings) of your user portal
To override CSS variables, use `:root` pseudo-element.
```css theme={null}
:root {
--body-background-color: #999999;
}
```
See the full [list of supported CSS variables](#variables) below.
Be aware that the user portal CSS overrides checkout page themes.
## Subscribe button
This is the subscription card on the checkout page. Read below about customizing its text.
### Change button text for all checkout pages
In order to change the text on the «Subscribe» button, add the following CSS snippet to your [custom CSS](/user-portal/getting-started#settings).
```css theme={null}
.checkout-public .subscribe-button span {
display: none;
}
.checkout-public .subscribe-button::after {
content: 'Purchase';
}
```
Replace the `content` property with your custom text.
### Change button text for certain pages
Each checkout link has a hash, you can find it in the URL:
Nadles adds a CSS class `checkout-link-` to each checkout page.
In order to change the button text for a certain checkout page, use the following CSS:
```css theme={null}
.checkout-public.checkout-link- .subscribe-button span {
display: none;
}
.checkout-public.checkout-link- .subscribe-button::after {
content: 'Buy credits';
}
```
Replace `` with the hash of the checkout page.
### Change card title
In order to change the card title ("Subscribe" by default), add the following CSS snippet to your [custom CSS](/user-portal/getting-started#settings).
```css theme={null}
.checkout-public .subscribe-card .card-title span {
display: none;
}
.checkout-public .subscribe-card .card-title::after {
content: 'Purchase';
}
```
Replace the `content` property with your custom text.
## Example CSS
### Demo portal theme
The following CSS was used to style the [demo user portal](https://app.demoapiprovider.eu/).
Try applying it to your user portal to see how it works.
```css theme={null}
:root {
--heading-color: rgba(230, 230, 241, 0.9);
--body-background-color: rgb(35, 35, 51);
--primary-color-rgb: 105 108 255;
--primary-color-hover-rgb: 94 97 230;
--dark-color-rgb: 120 131 147;
--card-background-color: rgb(43, 44, 64);
--card-color: rgba(230, 230, 241, 0.7);
--row-color-hover:var(--card-color);
--card-title-color: rgb(105, 108, 255);
--card-emphasis-color: rgba(230, 230, 241, 0.9);
--card-box-shadow: 0 3px 8px rgba(20, 20, 29,0.22),0 0 transparent,0 0 transparent;
--card-header-border-bottom: 0;
--card-border-radius: 6px;
--menu-item-link-background-color-hover: rgba(230, 230, 241, 0.06);
--menu-item-active-link-background-color: rgba(230, 230, 241, 0.06);
--menu-item-active-link-background-color-hover: rgba(230, 230, 241, 0.06);
--menu-app-brand-name-content: '';
--menu-app-brand-logo-url: url('https://checkout.nadles.com/static/checkout/l_68c152034f6e07ecf776d3f369dde51b8fa6964d4d30a048f3f3102e921cd677.png');
--input-border-color: rgb(133, 133, 133);
--button-primary-outline-background-color: rgba(0 0 0 0);
--button-danger-outline-background-color: rgba(0 0 0 0);
--row-background-color-hover: var(--menu-item-active-link-background-color);
--table-header-border-bottom: var(--table-row-border);
}
.user-portal .layout-menu .app-brand .app-brand-logo,
.user-portal .authentication-inner .app-brand .app-brand-logo {
max-height: 6rem;
}
.app-brand-logo {
margin-right: 0 !important;
}
.bx-package:before {
content: "\ea0e";
}
```
## Variables
The simplest way to change the look of your user portal is to change values of these variables.
| Variable | Description |
| ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| `--primary-color-rgb: 100 67 118` | RGB code for primary color. |
| `--primary-color: rgb(var(--primary-color-rgb) / 1)` | |
| `--primary-color-hover-rgb: 176 120 206` | RGB code for primary color, hover. |
| `--primary-color-hover: rgba(var(--primary-color-hover-rgb))` | |
| `--secondary-color-rgb: 120 131 147` | RGB code for secondary color. |
| `--secondary-color: rgb(var(--secondary-color-rgb) / 1)` | |
| `--dark-color-rgb: 32 47 63` | RGB code for dark color. |
| `--dark-color: rgb(var(--dark-color-rgb) / 1)` | |
| `--danger-color-rgb: 255 62 29` | RGB code for danger color. |
| `--danger-color: rgb(var(--danger-color-rgb) / 1)` | |
| `--danger-color-hover: #e6381a` | RGB code for danger color, hover. |
| `--body-background-color: var(--bs-body-bg)` | Body background color, see also: --card-background-color. |
| `--body-color: var(--bs-body-color)` | Body text color, see also: --card-color. |
| `--body-font-family: var(--bs-body-font-family)` | |
| `--body-font-size: var(--bs-body-font-size)` | |
| `--body-font-weight: var(--bs-body-font-weight)` | |
| `--link-color: var(--primary-color)` | Default link color. |
| `--link-color-hover: var(--primary-color-hover)` | Default link color, hover. |
| `--heading-color: var(--body-color)` | Default color for headers (h1, h2, ...). |
| `--card-background-color: #ffffff` | Background color for cards. |
| `--card-color: var(--body-color)` | Text color for cards. |
| `--card-emphasis-color: #000000` | Pricing summary on the pricing card on subscription page. |
| `--card-margin: 0 0 1rem 0` | |
| `--card-box-shadow: 0 2px 6px 0 rgba(67, 89, 113, 0.12)` | Box shadow parameter for cards. |
| `--card-border-radius: 0.5rem` | Border radius for cards. |
| `--card-header-border-bottom: 1px solid #f1f1f1` | The border under card headers. |
| `--card-header-margin: 0 0 1.5rem 0` | |
| `--card-title-color: #566a7f` | Text color of card titles/headers. |
| `--menu-background-color: var(--card-background-color)` | Background color for the menu. |
| `--menu-color: var(--card-color)` | Menu text color. |
| `--menu-app-brand-logo-url: url('/assets/images/logo_noemboss.svg')` | URL for the brand logo. Nadles logo by default. |
| `--menu-app-brand-name-content: attr(data-provider-name)` | Text after the logo. API provider name by default. |
| `--menu-app-brand-link-color: rgb(63, 57, 90)` | Logo link text color, when the logo is clickable. |
| `--menu-app-brand-link-color-hover: var(--menu-app-brand-link-color)` | Logo link text color, when the logo is clickable, hover. |
| `--menu-header-color: #a1acb8` | Text color of menu header — customer name right under the logo in the menu. |
| `--menu-item-color: var(--card-color)` | Text color of inactive non-link menu item. |
| `--menu-item-color-hover: var(--card-color)` | Text color of inactive non-link menu item, hover. |
| `--menu-item-background-color: var(--card-background-color)` | Background color of `` element in the menu. |
| `--menu-item-background-color-hover: var(--card-background-color)` | Background color of `` element in the menu, hover. |
| `--menu-item-link-color: var(--menu-item-color)` | Text color of links in menu items. |
| `--menu-item-link-color-hover: var(--menu-item-color-hover)` | Text color of links in menu items, hover. |
| `--menu-item-link-background-color: var(--menu-item-background-color)` | Background color of links in menu items. |
| `--menu-item-link-background-color-hover: rgba(67, 89, 113, 0.04)` | Background color of links in menu items, hover. |
| `--menu-item-active-color: var(--link-color)` | Text color in active menu items, non-link. |
| `--menu-item-active-color-hover: var(--link-color-hover)` | Text color in active menu items, non-link, hover. |
| `--menu-item-active-background-color: var(--card-background-color)` | Background color of active `` element in the menu. |
| `--menu-item-active-background-color-hover: var(--card-background-color)` | Background color of active `` element in the menu, hover. |
| `--menu-item-active-link-color: var(--menu-item-active-color)` | Text color of links in active menu items. |
| `--menu-item-active-link-color-hover: var(--menu-item-active-color-hover)` | Text color of links in active menu items, hover. |
| `--menu-item-active-link-background-color: rgba(105, 108, 255, 0.16)` | Background color of links in active menu items. |
| `--menu-item-active-link-background-color-hover: rgba(105, 108, 255, 0.16)` | Background color of links in active menu items, hover. |
| `--menu-item-icon-color: var(--link-color)` | Menu icons text color. |
| `--menu-item-icon-color-hover: var(--link-color-hover)` | Menu icons text color, hover. |
| `--menu-item-active-icon-color: var(--link-color)` | Menu icons text color in active menu items. |
| `--menu-item-active-icon-color-hover: var(--link-color-hover)` | Menu icons text color in active menu items, hover. |
| `--menu-item-bullet-color: var(--primary-color)` | Menu bullet color in active menu items. |
| `--menu-item-bullet-color-hover: var(--primary-color)` | Menu bullet color in active menu items, hover. |
| `--navbar-background-color: var(--card-background-color)` | Top navbar background color. |
| `--navbar-link-color: var(--card-color)` | Top navbar link text color. |
| `--navbar-link-color-hover: var(--card-color)` | Top navbar link text color, hover. |
| `--navbar-box-shadow: var(--card-box-shadow)` | Top navbar box shadow. |
| `--navbar-dropdown-menu-box-shadow: 0 0.25rem 1rem rgba(161, 172, 184, 0.45)` | Top navbar dropdown menu box shadow. |
| `--navbar-border-radius: var(--card-border-radius)` | Top navbar border radius. |
| `--table-header-color: var(--card-title-color)` | Table headers text color. |
| `--table-header-border-bottom: 1px solid #d9dee3` | Table headers bottom border. |
| `--table-row-border: 1px solid #f1f1f166` | Table row bottom border. |
| `--row-background-color-hover: rgba(67, 89, 113, 0.06)` | Table row background color, hover. |
| `--row-color-hover: var(--body-color)` | Table row text color, hover. |
| `--transaction-history-posting-positive: rgba(var(--bs-success-rgb), 1)` | Text color for transaction history positive items (e.g., + \$100) |
| `--transaction-history-posting-negative: rgba(var(--bs-warning-rgb), 1)` | Text color for transaction history negative items (e.g., - \$50) |
| `--button-color-default: #ffffff` | Button text color default. |
| `--button-primary-color: var(--button-color-default)` | |
| `--button-primary-color-hover: var(--button-color-default)` | |
| `--button-primary-background-color: var(--primary-color)` | |
| `--button-primary-background-color-hover: var(--primary-color)` | |
| `--button-primary-border-color: var(--button-primary-background-color)` | |
| `--button-primary-outline-color: var(--button-primary-background-color)` | |
| `--button-primary-outline-color-hover: var(--button-primary-color-hover)` | |
| `--button-primary-outline-background-color: var(--button-primary-color)` | |
| `--button-primary-outline-background-color-hover: var(--button-primary-background-color-hover)` | |
| `--button-primary-outline-border-color: var(--button-primary-background-color)` | |
| `--button-secondary-color: var(--button-color-default)` | |
| `--button-secondary-color-hover: var(--button-color-default)` | |
| `--button-secondary-background-color: var(--secondary-color)` | |
| `--button-secondary-background-color-hover: var(--secondary-color)` | |
| `--button-secondary-border-color: var(--button-secondary-background-color)` | |
| `--button-secondary-outline-color: var(--button-secondary-background-color)` | |
| `--button-secondary-outline-color-hover: var(--button-secondary-color)` | |
| `--button-secondary-outline-background-color: var(--button-secondary-color)` | |
| `--button-secondary-outline-background-color-hover: var(--button-secondary-background-color)` | |
| `--button-secondary-outline-border-color: var(--button-secondary-background-color)` | |
| `--button-dark-color: var(--button-color-default)` | |
| `--button-dark-color-hover: var(--button-color-default)` | |
| `--button-dark-background-color: var(--dark-color)` | |
| `--button-dark-background-color-hover: var(--dark-color)` | |
| `--button-dark-border-color: var(--button-dark-background-color)` | |
| `--button-dark-outline-color: var(--button-dark-background-color)` | |
| `--button-dark-outline-color-hover: var(--button-dark-color)` | |
| `--button-dark-outline-background-color: var(--transparent)` | |
| `--button-dark-outline-background-color-hover: var(--button-dark-background-color)` | |
| `--button-dark-outline-border-color: var(--button-dark-background-color)` | |
| `--button-danger-color: var(--button-color-default)` | |
| `--button-danger-color-hover: var(--button-color-default)` | |
| `--button-danger-background-color: var(--danger-color)` | |
| `--button-danger-background-color-hover: var(--danger-color-hover)` | |
| `--button-danger-border-color: var(--button-danger-background-color)` | |
| `--button-danger-outline-color: var(--button-danger-background-color)` | |
| `--button-danger-outline-color-hover: var(--button-danger-color-hover)` | |
| `--button-danger-outline-background-color: var(--button-danger-color)` | |
| `--button-danger-outline-background-color-hover: var(--button-danger-background-color-hover)` | |
| `--button-danger-outline-border-color: var(--button-danger-background-color)` | |
| `--input-color: var(--card-color)` | Input text color. |
| `--input-background-color: var(--card-background-color)` | Input background color. |
| `--input-border-color: #d9dee3` | Input border color. |
| `--input-border-color-focus: var(--primary-color)` | Input border color when focused. |
| `--input-group-button-color: var(--input-color)` | Buttons in input groups text color, e.g., API access URL on checkout pages. |
| `--input-group-button-background-color: var(--input-background-color)` | Buttons in input groups background color for, e.g., API access URL on checkout pages. |
| `--product-name-color: var(--card-title-color)` | Product name text color. |
| `--api-header-color: var(--card-title-color)` | API header text color. |
| `--api-icon-color: var(--api-header-color)` | API header icon color. |
| `--modal-content-background-color: var(--card-background-color)` | Modal windows background color. |
| `--login-form-bullets-background: ` | Bullets behind the login/registration/password reset forms. |
| `--text-muted-color: #a1acb8;` | Muted text color. |