Every /v1 request carries one of your store's secret or restricted keys. Fluveo reads the key, finds the store it belongs to, and answers only with that store's objects.
Know your keys#
A secret key belongs to exactly one store. Your store's id starts with mch_, and you can read it with GET /v1/account.
| Key | What it does today |
|---|---|
sk_test_... | Secret key, test mode. Every call works on test data, and no real money moves. |
rk_test_... | Restricted key, test mode. It calls only the endpoints its permissions allow. See Restricted keys. |
sk_live_..., rk_live_... | Live mode. Fluveo does not serve live mode yet, so a request with your store's live key answers 403. |
pk_test_..., pk_live_... | Publishable key, for the browser. fluveo.js uses it to show the payment form on your own page and to confirm the payment of a PaymentIntent whose client secret the page holds. It creates, lists and changes nothing: every other /v1 request that sends it answers 403 with code secret_key_required. |
Each object carries livemode. With a test key it is always false.
Get a key from the Dashboard#
- Sign in to the Fluveo Dashboard.
- Open Settings in the sidebar and go to the API keys section. Check that the bottom of the sidebar shows Test mode.
- Under Standard keys, click Create secret key, give it a name, such as the platform account that will use it, click Create, copy the key and click Done. The Dashboard shows the full key only once. After that it lists only the name and a masked hint.
There is no API call that creates a key.
One key per platform account#
A store can hold many secret keys and many restricted keys at the same time, each with its own name. Give each platform account its own key: if your store sells through three accounts on a checkout platform, create three keys named after them. Each key works on its own:
- Rotate key replaces one key with a new one that works at once. In Expiration you choose when the old key stops working: Now, or in 1 hour, 24 hours, 3 days or 7 days. Until then both keys work, so you can move each platform to the new key without downtime. The new secret key is named Secret key and the new publishable key Publishable key; a new restricted key keeps the old key's name and permissions.
- Edit key changes a secret or restricted key's name and note, and a restricted key's permissions.
- Expire key stops a secret or restricted key at once and cannot be undone. A publishable key cannot be expired; rotate it instead.
Rotating, editing or expiring one key never changes any other key. The list shows each key's name, creation date, last use and, after a rotation, the time left before the old key expires. There is no limit on how many secret or restricted keys a store creates.
Restricted keys#
A restricted key (rk_test_...) works like a secret key, but only on the endpoints its permissions allow. Use one when a platform, a report or a tool needs only part of your store, such as reading payments without creating them.
Create one in Settings > API keys > Restricted keys > Create restricted key: give it a name and choose None, Read or Write for each resource. Read allows the GET requests of the resource. Write allows every request of the resource, reads included. Every resource left at None, and every /v1 URL no resource covers, answers 403 to a restricted key.
Permissions lists each resource with its paths and the permission every operation of the reference needs. A resource's name there can differ from its Dashboard label: Prices is plan, and Charges and Refunds is charge. Paths match leading whole URL segments. The first matching resource on the Permissions page applies, so /v1/customers/{customer}/payment_methods needs payment_method, not customer. A request a key's permissions do not allow answers 403 before Fluveo does anything else, and the message names the permission that would allow it:
{
"error": {
"type": "invalid_request_error",
"message": "The provided key 'rk_test_****abcd' does not have the required permissions for this endpoint on account 'mch_...'. Having the 'rak_payment_intent_write' permission would allow this request to continue."
}
}The permission in the message is rak_<resource>_read or rak_<resource>_write, with the resource names Permissions lists. An endpoint no permission covers answers the same 403 with "This is a restricted API key, but the required permissions are not available for use by restricted keys." in place of the last sentence.
A path under a resource the key may call that is not an endpoint, such as /v1/payment_intents/{intent}/unknown, answers 404 "Unrecognized request URL" once the key holds that resource's permission, as it does for a secret key. Without the permission it answers the 403 above.
A restricted test key created with This key is for an AI agent checked is an agent key. The API applies the agent policy to its calls, and it is the key Fluveo MCP takes. MCP authentication shows how to make, use and revoke one.
Send the key#
Put the key in the Authorization header as a Bearer token.
curl https://api.fluveo.com/v1/balance \
-H "Authorization: Bearer sk_test_..."const response = await fetch("https://api.fluveo.com/v1/balance", {
headers: { Authorization: "Bearer sk_test_..." },
});
console.log(response.status, await response.json());import requests
response = requests.get(
"https://api.fluveo.com/v1/balance",
headers={"Authorization": "Bearer sk_test_..."},
)
print(response.status_code, response.json())<?php
$ch = curl_init("https://api.fluveo.com/v1/balance");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer sk_test_..."],
]);
echo curl_exec($ch);HTTP Basic also works: the key is the user name and the password is empty, as in curl -u sk_test_...: https://api.fluveo.com/v1/balance. The trailing colon tells curl the password is empty. Use one scheme per request.
Request bodies are form-encoded (application/x-www-form-urlencoded). Nested values use brackets: metadata[order_id]=1042 for a map and payment_method_types[]=pix for a list.
One store per key#
Fluveo has no header that switches to another account. A request that sends a header meant to act for another account answers 400 with that header in param, whatever its value. To act for another store, use that store's own key.
This is also how platforms work. A checkout platform never holds one key for many stores: each merchant pastes its own Fluveo key into the platform. See Platforms.
When the key is wrong#
| Case | Status | Error |
|---|---|---|
No Authorization header, or an empty key | 401 | invalid_request_error, message "You did not provide an API key. ..." |
| A key in the wrong shape or an unknown key, a live or publishable one included | 401 | invalid_request_error, message "Invalid API Key provided: sk_test_****abcd" with the key masked |
| An expired key: expired in the Dashboard, or rotated and past the expiration you chose | 401 | invalid_request_error, code api_key_expired, message "Expired API Key provided: sk_test_****abcd" with the key masked |
| Your store's publishable key | 403 | invalid_request_error, code secret_key_required, message "This API call cannot be made with a publishable API key. Please use a secret API key." |
| A restricted key on an endpoint its permissions do not allow | 403 | invalid_request_error, the message in Restricted keys |
Your store's sk_live_ or rk_live_ key | 403 | invalid_request_error, message "Live mode is not available yet: this API serves test mode only. Use your test secret key (sk_test_...)." |
| A valid key on a route your store may not use | 403 | invalid_request_error, the "not available" message below |
| A header that acts for another account | 400 | invalid_request_error with that header in param |
A 403 "not available" refusal carries code not_available:
{
"error": {
"type": "invalid_request_error",
"code": "not_available",
"message": "This API is not available on this account. If its setup is not finished or is still under review, finish it in the Dashboard under Settings and try again once the account is approved."
}
}Test keys work from the moment your store exists. In a new store's first seconds, while its test mode is still being set up, a test call can get this same 403 not_available with the message "This account's test mode is still being set up. Try again in a few seconds." Send the same request again a few seconds later; nothing in the Dashboard is needed. Activation in the Dashboard under Settings opens live mode only: if it is not finished or is still in review, your live keys wait for it.
Every answer carries a Request-Id header (req_...). Keep it when you report a problem. An error of a call your store's log keeps also carries error.request_log_url, a link to that call's log in the Dashboard; see Request logs. The full list of errors is on Errors.
Test it#
- Call
GET /v1/accountwith your test key. You get200and anidthat starts withmch_. - Call it with no
Authorizationheader. You get401. - Call it with your store's publishable key (
pk_test_...). You get403with codesecret_key_required. - Create a restricted key with PaymentIntents: Read.
GET /v1/payment_intentsanswers200andPOST /v1/payment_intentsanswers403namingrak_payment_intent_write.