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

# API reference

> GET /v1/price. Parameters, response fields, errors and limits.

EasyPPP has one public endpoint. It takes your public key and up to 50 Stripe Price IDs, and returns the Price ID each visitor should be charged, with an amount to display.

```
GET https://api.easyppp.com/v1/price?key=ep_live_…&prices=price_a,price_b
```

Call it from the visitor's browser. The country is taken from the request itself, so a call from your server would be priced for your server's location. There is no way to pass a country or IP address.

## Request

<ParamField query="key" type="string" required>
  Your public key from the dashboard. Starts with `ep_live_`, `ep_test_` or `ep_sbx_` and selects that Stripe environment.
</ParamField>

<ParamField query="prices" type="string" required>
  Comma-separated Stripe Price IDs, 1 to 50. Base Price IDs or sibling IDs both work; a sibling resolves through its base. `price=` is accepted for a single ID.
</ParamField>

Only `GET` and `OPTIONS` are allowed. There are no headers to set. Responses are JSON with `Access-Control-Allow-Origin: *` and `Cache-Control: no-store`.

## Response

```json theme={null}
{
  "data": [
    {
      "price": "price_1QaDeep",
      "base_price": "price_1Pz9Kx",
      "localized": true,
      "known": true,
      "country": "IN",
      "pay": 35,
      "discount_percent": 65,
      "currency": "INR",
      "amount": 335300,
      "display": "₹3,353.00",
      "estimated": true,
      "base_currency": "USD",
      "base_amount": 3500,
      "rates_as_of": "2026-10-03T22:50:29Z"
    }
  ],
  "meta": { "request_id": "6e1f…", "paused": false }
}
```

`data` has one entry per requested ID, in the order you sent them.

<ResponseField name="price" type="string">
  The Stripe Price ID to charge. A sibling when the visitor is localized, otherwise the base Price.
</ResponseField>

<ResponseField name="base_price" type="string">
  The base Price this entry belongs to. Useful as a stable key when you request several IDs.
</ResponseField>

<ResponseField name="localized" type="boolean">
  `true` when a market sibling was selected.
</ResponseField>

<ResponseField name="known" type="boolean">
  `true` when the ID is a Price EasyPPP localizes for this key. Unknown IDs are echoed back with `known: false` and null pricing fields, so you can always fall back to your own price.
</ResponseField>

<ResponseField name="country" type="string | null">
  ISO 3166-1 alpha-2 code of the visitor's country, or `null` when it could not be determined. This is the most specific location field returned.
</ResponseField>

<ResponseField name="pay" type="integer | null">
  Percentage of the base price this visitor pays. `100` when not localized. Above 100 in premium markets.
</ResponseField>

<ResponseField name="discount_percent" type="number | null">
  `100 - pay`, or `0` when there is no discount. Never negative.
</ResponseField>

<ResponseField name="currency" type="string | null">
  Currency of `amount` and `display`: the visitor's currency when a rate is available, otherwise the Price's own currency.
</ResponseField>

<ResponseField name="amount" type="integer | null">
  Amount in the smallest unit of `currency`, following Stripe's convention (no decimals for JPY and KRW, three for KWD and BHD, two for most others). Prefer `display`.
</ResponseField>

<ResponseField name="display" type="string | null">
  `amount` formatted with a currency symbol and the correct number of decimals, for example `$49.00`, `CA$66.15`, `¥4,900`.
</ResponseField>

<ResponseField name="estimated" type="boolean">
  `true` when `amount` was converted into a currency other than the Price's own. The conversion uses the exchange rate from `rates_as_of` and is for display only.
</ResponseField>

<ResponseField name="base_currency" type="string">
  The currency of the Stripe Price, which is what Stripe charges. Absent for unknown IDs.
</ResponseField>

<ResponseField name="base_amount" type="integer">
  The amount of the selected Price (the sibling when localized) in `base_currency`, in its smallest unit. This is what Stripe charges. Absent for unknown IDs.
</ResponseField>

<ResponseField name="rates_as_of" type="string">
  When the exchange rates used for the estimate were published, ISO 8601 UTC. Absent for unknown IDs.
</ResponseField>

<ResponseField name="meta.request_id" type="string | null">
  Quote this when writing to support about a specific response.
</ResponseField>

<ResponseField name="meta.paused" type="boolean">
  `true` when localized pricing is paused for this account, either by you or because no card is on file. Every entry then carries the base Price.
</ResponseField>

### A visitor who is not localized

The same fields come back, pointing at the base Price. `display` is still the base price converted into the visitor's currency when a rate is known.

```json theme={null}
{
  "price": "price_1Pz9Kx", "base_price": "price_1Pz9Kx", "localized": false, "known": true,
  "country": "CA", "pay": 100, "discount_percent": 0,
  "currency": "CAD", "amount": 13600, "display": "CA$136.00", "estimated": true,
  "base_currency": "USD", "base_amount": 10000, "rates_as_of": "2026-10-03T22:50:29Z"
}
```

This shape is returned for visitors in the base bucket, visitors whose country cannot be determined (`country: null`, amount in the base currency), visitors behind a VPN or proxy when the guard is on, and every visitor while the account is paused. The response does not say which.

### An ID EasyPPP does not know

```json theme={null}
{
  "price": "price_other", "base_price": "price_other", "localized": false, "known": false,
  "country": "CA", "currency": null, "amount": null, "display": null,
  "estimated": false, "pay": null, "discount_percent": null
}
```

## Errors

Errors are JSON with a single `error` field.

| Status | `error` | Why |
| - | - | - |
| 400 | `invalid_key` | `key` is missing or not in the expected format. |
| 400 | `missing_prices` | No Price IDs were given. |
| 400 | `too_many_prices` | More than 50 IDs. |
| 400 | `invalid_price` | An ID is not of the form `price_` followed by letters and digits. |
| 401 | `unknown_key` | A well-formed key that is not active. Keys are revoked when the Stripe account is disconnected. |
| 405 | `method_not_allowed` | Anything other than `GET` or `OPTIONS`. |
| 503 | `unavailable` | EasyPPP could not answer. Show your base price and try again on the next page load. |

On any error, show your base price and charge your base Price ID.

## Limits and timing

| | |
| - | - |
| IDs per request | 50 |
| Requests | Unmetered. There is no rate limit and no per-request charge. |
| A new key | Usable within a few seconds of being issued. |
| Product or setting changes | Reach the API within about a minute. |
| A sibling archived or deleted in Stripe | Noticed within an hour; the base Price is served meanwhile. |
| Exchange rates | Refreshed nightly. |

## The key

The key is public and meant to live in client code. It can read localized prices for the Prices you selected and nothing else: it cannot charge, refund, change anything in Stripe, or see your customers, payments or revenue. Each Stripe connection (live, test mode, sandbox) has its own key.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.