Docs
API 2026-05-27.dahlia Dashboard

Payments

Read what part of your sales is held back, why, and when each amount is released.

A reserve keeps part of your sales back for a while, so refunds and disputes stay covered. Fluveo sets the reserve and its terms on your store's account. While it is in place, part of each sale waits in it instead of joining what you can pay out. Read this page to know how much is held, under which terms, and when it comes back.

The reserve routes are a Fluveo extension: the reference lists them under Fluveo extensions, not as part of API version 2026-05-27.dahlia, and they serve the same routes, lists and fields to every store. Every one of them is a read. See Reads only.

Plans, holds and releases#

Three objects describe a reserve. Each list answers with your store's objects only, and the holds and releases come newest first.

ObjectIdWhat it is
reserve.planresplan_...The terms: the share of each sale to keep back (percent), the way it is released (type, such as rolling_release), and its status.
reserve.holdreshold_...One amount kept back. A hold taken from a sale can name its plan in reserve_plan and the sale's charge in source_charge; reason is charge. A hold made on its own has reason standalone.
reserve.releaseresrel_...Money that left a hold and is yours again. It can name the hold in reserve_hold and its plan in reserve_plan.

A plan has many holds, and a hold can have one release or more. A store whose reserve follows no plan has an empty plans list.

List your reserve plans#

GET /v1/reserve/plans lists the plans of your store's account. Narrow it with status: active, disabled or expired.

List the active reserve plans
curl -G https://api.fluveo.com/v1/reserve/plans \
  -H "Authorization: Bearer sk_test_..." \
  -d status=active
const query = new URLSearchParams({ status: "active" });
const response = await fetch("https://api.fluveo.com/v1/reserve/plans?" + query, {
  headers: { Authorization: "Bearer sk_test_..." },
});
const plans = await response.json();
console.log(plans.data.length);
import requests

response = requests.get(
    "https://api.fluveo.com/v1/reserve/plans",
    headers={"Authorization": "Bearer sk_test_..."},
    params={"status": "active"},
)
plans = response.json()
print(len(plans["data"]))
<?php
$ch = curl_init("https://api.fluveo.com/v1/reserve/plans?" . http_build_query(["status" => "active"]));
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ["Authorization: Bearer sk_test_..."],
]);
$plans = json_decode(curl_exec($ch), true);
echo count($plans["data"]);

A store with no reserve gets an empty list:

Response
{
  "object": "list",
  "url": "/v1/reserve/plans",
  "has_more": false,
  "data": []
}

When a plan is in place, each row reads like this one, abridged. It keeps 5% of each sale and releases each amount 90 days after its charge:

A reserve.plan, abridged
{
  "id": "resplan_...",
  "object": "reserve.plan",
  "created_by": "fluveo",
  "currency": null,
  "percent": 5,
  "status": "active",
  "type": "rolling_release",
  "rolling_release": {
    "days_after_charge": 90
  }
}

currency is null when the plan covers every currency. created_by says who made the plan, fluveo or application.

List the held amounts#

GET /v1/reserve/holds lists every amount kept back. Narrow it with reserve_plan (a resplan_...), source_charge (a ch_... of one of your sales), reserve_release (the holds one release took money from), currency, and is_releasable (true or false).

List the held amounts
curl -G https://api.fluveo.com/v1/reserve/holds \
  -H "Authorization: Bearer sk_test_..." \
  -d limit=10
const query = new URLSearchParams({ limit: "10" });
const response = await fetch("https://api.fluveo.com/v1/reserve/holds?" + query, {
  headers: { Authorization: "Bearer sk_test_..." },
});
const holds = await response.json();
console.log(holds.data.length, holds.has_more);
import requests

response = requests.get(
    "https://api.fluveo.com/v1/reserve/holds",
    headers={"Authorization": "Bearer sk_test_..."},
    params={"limit": 10},
)
holds = response.json()
print(len(holds["data"]), holds["has_more"])
<?php
$ch = curl_init("https://api.fluveo.com/v1/reserve/holds?" . http_build_query(["limit" => 10]));
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ["Authorization: Bearer sk_test_..."],
]);
$holds = json_decode(curl_exec($ch), true);
echo count($holds["data"]);
Response
{
  "object": "list",
  "url": "/v1/reserve/holds",
  "has_more": false,
  "data": []
}

A hold taken from a sale looks like this, abridged. amount and amount_releasable are in the smallest unit of the currency, so 250 is 2.50:

A reserve.hold, abridged
{
  "id": "reshold_...",
  "object": "reserve.hold",
  "amount": 250,
  "amount_releasable": 0,
  "currency": "usd",
  "created_by": "fluveo",
  "destination": "risk_reserved",
  "is_releasable": false,
  "reason": "charge",
  "release_schedule": {
    "release_after": 1786060800,
    "scheduled_release": 1786060800
  },
  "reserve_plan": "resplan_...",
  "source_charge": "ch_...",
  "source_type": "card"
}

release_schedule holds the date the hold is due back. amount_releasable is the part that may be released now, and is_releasable is true when the hold may be released.

List the releases#

GET /v1/reserve/releases lists the money that left your holds. Narrow it with reserve_hold (a reshold_...), reserve_plan (a resplan_...) and currency.

List the releases
curl -G https://api.fluveo.com/v1/reserve/releases \
  -H "Authorization: Bearer sk_test_..." \
  -d limit=10
const query = new URLSearchParams({ limit: "10" });
const response = await fetch("https://api.fluveo.com/v1/reserve/releases?" + query, {
  headers: { Authorization: "Bearer sk_test_..." },
});
const releases = await response.json();
console.log(releases.data.length, releases.has_more);
import requests

response = requests.get(
    "https://api.fluveo.com/v1/reserve/releases",
    headers={"Authorization": "Bearer sk_test_..."},
    params={"limit": 10},
)
releases = response.json()
print(len(releases["data"]), releases["has_more"])
<?php
$ch = curl_init("https://api.fluveo.com/v1/reserve/releases?" . http_build_query(["limit" => 10]));
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ["Authorization: Bearer sk_test_..."],
]);
$releases = json_decode(curl_exec($ch), true);
echo count($releases["data"]);
Response
{
  "object": "list",
  "url": "/v1/reserve/releases",
  "has_more": false,
  "data": []
}

A release can name its hold, its amount in the smallest currency unit, and released_at, the time the money became available again. destination is the balance it went back to, such as payments. reason tells why it left the hold: on its date, early, by a refund, or with its plan. When a refund caused it, source_transaction says which.

Read one by id#

Each object has its own read, which answers exactly as its row in the list:

  • GET /v1/reserve/plans/{id} reads one resplan_....
  • GET /v1/reserve/holds/{id} reads one reshold_.... Add expand[]=reserve_plan or expand[]=source_charge to get the whole object in place of its id.
  • GET /v1/reserve/releases/{id} reads one resrel_.... Add expand[]=reserve_plan or expand[]=reserve_hold the same way.

Take the id from a list or from a reserve.* event. An id that is not one of your store's answers 404. See Expanding responses.

List parameters
  • statusstringOptional

    Plans only. active, disabled or expired.

  • reserve_planstringOptional

    Holds and releases. Only those of this resplan_....

  • source_chargestringOptional

    Holds only. Only the holds taken from this charge, a ch_....

  • reserve_releasestringOptional

    Holds only. Only the holds this resrel_... released money from.

  • is_releasablestringOptional

    Holds only. true for holds that may be released now, false for the others.

  • reserve_holdstringOptional

    Releases only. Only the releases of this reshold_....

  • currencystringOptional

    Holds and releases. Only those in this three-letter currency code.

  • limitintegerOptional

    How many objects one page holds, from 1 to 100. Default 10.

  • starting_afterstringOptional

    The id of the last object of the page you have, to read the next page. See Pagination.

  • ending_beforestringOptional

    The id of the first object of the page you have, to read the page before it. Never send it with starting_after.

The held money in your balance#

GET /v1/balance shows what your reserve holds in risk_reserved, with its own available and pending lists, one entry per currency in the smallest currency unit. That money is already left out of the balance's top-level available and pending, so it is never counted twice, and you cannot pay it out until it is released. See Read your balance and Balance and payouts.

When a hold is released#

A hold is released on its release date, the one in its release_schedule. Fluveo may also release it earlier. A refund can trigger a release too. A hold made on its own, with no sale, has no date: it stays until Fluveo releases it. Each release shows as a reserve.release object, and the hold's amount_releasable and is_releasable change with it.

When a plan is turned off or reaches its end, its holds are released.

Reads only#

The reserve routes only read. Your store does not create a reserve plan or a hold, change one, or release money from a hold through the API, and no route takes a POST for them. Fluveo sets the reserve and its terms.

Plan statuses and events#

Plan statusMeaning
activeThe plan keeps part of each new sale.
disabledThe plan was turned off. Its holds are being released.
expiredThe plan reached its end. Its holds are being released.

A newer value can show as sent: treat any other value as unknown, not as an error.

EventWhen
reserve.plan.createdA plan was set up on your store's account.
reserve.plan.updatedA plan's terms changed.
reserve.plan.disabledA plan was turned off.
reserve.plan.expiredA plan reached its end.
reserve.hold.createdMoney from a sale was held back.
reserve.hold.updatedA hold changed, such as its release date or the part still held.
reserve.release.createdMoney left a hold: on its date, early, or by a refund.

Each event's data.object is the plan, hold or release as it was then. See Webhooks.

Errors you may see#

ErrorWhy
404 resource_missingAn unknown reserve plan, hold or release id in a path, filter or cursor is not one of your store's. The message names the object, such as "Specified Reserve Hold not found." An unknown source_charge filter returns an empty list.
400 parameter_unknownThe list does not take that parameter, such as status on the holds list.
400 parameter_invalid_integerlimit is not a whole number. A whole number outside 1 to 100 is brought into that range instead of refused.
400 "Invalid status: must be one of active, disabled, or expired"The plans list got a status outside those three. This error has no code.
400 "Received both starting_after and ending_before parameters. Please pass in only one."Send one cursor at a time. This error has no code.
403A restricted key without Read on Reserves. See Restricted keys.

A refusal looks like this:

404 response
{
  "error": {
    "type": "invalid_request_error",
    "code": "resource_missing",
    "message": "Specified Reserve Hold not found."
  }
}

Test it#

  1. With a test key, list the plans, the holds and the releases as above. A new test store with no reserve gets "object": "list" and an empty data from each of the three. If Fluveo set a plan on your store, the plans list shows it with created_by: "fluveo".
  2. Read GET /v1/balance and find risk_reserved (see Read your balance).
  3. Read GET /v1/reserve/holds/{id} with an id your store does not have, such as reshold_000000000000000000000000: 404 resource_missing.
  4. Send status=held to the plans list: 400 "Invalid status: must be one of active, disabled, or expired".

Next#