Bottle Blue LLC

Bottle API Reference

Market and government data, weather and environment, plus the building blocks for your next app: notes, lists, queues, streaks, leaderboards, and more. One REST API, one key.

REST JSON HTTPS v1 · stable
0
Endpoints
across all categories
0
API Hosts
Bottle Blue, Aqua Botany, PocketQuant, cloud & web
0
Categories
data & app domains
99.9%
Uptime
service target

What are you building?

One key, one JSON contract. Pick the part you need and the rest of this page gets out of the way.

Quickstart

Make your first Bottle API call in under 2 minutes.

1

Create an account & generate a key

Sign up at app.bottlebluellc.com, then visit your dashboard to generate an API key. Your key is an alphanumeric string that identifies your account on every request.

Signing up, viewing plans, generating a key and managing your subscription all work on the free plan. When you're ready to call the data endpoints, pick the plan that fits (Hobby, Pro, or Enterprise) and your key unlocks them instantly.

2

Store the key as an environment variable

export BBL_API_KEY="your_key_here"
import os
# Set in your shell first:  export BBL_API_KEY="your_key_here"
API_KEY = os.environ["BBL_API_KEY"]
// Set in your shell first:  export BBL_API_KEY="your_key_here"
const API_KEY = process.env.BBL_API_KEY;

Never hard-code your key in source files. Use environment variables or a secrets manager.

3

Make your first request

The /math endpoint is a great first call: synchronous, no extra parameters required, and it works on both hosts.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "percentage", "params": {"kind": "tip", "amount": 50, "percent": 20}}'
import os, requests

API_KEY = os.environ["BBL_API_KEY"]

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY, "Content-Type": "application/json"},
    json={"type": "percentage", "params": {"kind": "tip", "amount": 50, "percent": 20}},
)
print(resp.status_code, resp.json())
const API_KEY = process.env.BBL_API_KEY;

const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({ type: "percentage", params: { kind: "tip", amount: 50, percent: 20 } }),
});
const data = await resp.json();
console.log(data);
4

Read the response

A successful call returns 200 OK with a JSON body. Error responses include a requestId, so quote it when contacting support.

200 Response
{
  "result": 10.0,
  "meta": {
    "category": "percent",
    "formula_id": "percent:tip"
  }
}

That’s it. Every other endpoint follows the same pattern: set the header, send JSON, read JSON. Explore the sidebar to find the right endpoint for your use case.

Authentication

Send your API key in the x-api-key header on every request. Requests without a valid key receive 401.

A request is authorized only when the key is active and the owning account has an active paid subscription (Hobby, Pro, or Enterprise). Free gives you an account and an API key to sign up and get set up; upgrade to a paid plan whenever you are ready to call the full API. A key without a paid subscription receives 403 on the gated endpoints. The examples below read the key from a BBL_API_KEY environment variable; never hard-code it.

export BBL_API_KEY="your_key_here"

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "percentage", "params": {"kind": "tip", "amount": 50, "percent": 20}}'
import os, requests

API_KEY = os.environ["BBL_API_KEY"]
resp = requests.get("https://api.aquabotany.com/v1/weather",
                    headers={"x-api-key": API_KEY},
                    params={"lat": 44.98, "lon": -93.26})
print(resp.json())
const API_KEY = process.env.BBL_API_KEY;
const resp = await fetch("https://api.aquabotany.com/v1/weather?lat=44.98&lon=-93.26", {
  headers: { "x-api-key": API_KEY }
});
console.log(await resp.json());

Errors & rate limits

The API uses conventional HTTP status codes and returns a JSON error envelope with a machine-readable code and a requestId.

StatusMeaning
400Malformed request (bad JSON or parameters).
401Missing or invalid API key.
403Authenticated but not allowed (usually no active subscription).
404No such resource.
429Rate limit exceeded. Limits are per-plan: Free 100 requests/day (10/min burst), Hobby 1,667/day (60/min), Pro 10,000/day (300/min), Enterprise 100,000/day (1,000/min), its published allowance. Above Enterprise, a Custom plan sets volume and rate limits per contract, agreed in writing. Codes: BURST_LIMIT_EXCEEDED / DAILY_LIMIT_EXCEEDED. Every 429 carries a Retry-After header giving the whole seconds until the window that tripped actually resets, so prefer it over a fixed backoff: a retry sooner than that is counted against your quota and cannot succeed. Browser clients can read it because the response also sets Access-Control-Expose-Headers: Retry-After.
500Something went wrong on our side. Quote the requestId.
502An upstream data provider returned an error or an unusable response. Code: UPSTREAM_ERROR.
503An upstream data provider is unreachable. Code: UPSTREAM_UNAVAILABLE.
504An upstream data provider did not respond in time. Code: UPSTREAM_TIMEOUT.

Attribution

One line, one link, wherever you show Bottle API data.

Display the words Powered by Bottle API, linked to bottleapi.com. That is the requirement, in full.

What is required

  • The text is "Powered by Bottle API", linked to https://bottleapi.com.
  • It appears on every page or view that displays Bottle API data, not once on an About page.
  • It is legible and visible on arrival. Normal reading size, normal contrast, in view without a hover, a tap or an expanded panel.
  • It applies on every plan: Free, Hobby and Pro. An Enterprise agreement may vary or waive it in writing.

Paste this

HTML
<a href="https://bottleapi.com" rel="noopener">Powered by Bottle API</a>

The link may carry rel="noopener" and may open in a new tab. Style it to sit in your own design, in your own typeface and colours. Keep the five words and the destination exactly as written.

Credit the original source too

Attribution to Bottle API is separate from, and additional to, attribution to the body that published a figure. Crediting us does not discharge a duty to say that a filing came from SEC EDGAR or that a reference rate came from the European Central Bank. Where the source is part of what you are showing, name it alongside us.

Using the data

Commercial use inside your own product is included on every paid plan. Call the API from your backend, show what comes back inside your application, and charge your own users for that application, including by subscription. They pay you for the product you built and they need no account with us.

Four things sit outside that. Handing our data onward to your users through an API, a feed, an export, a download or any other machine-readable interface needs an Enterprise agreement. So does keeping a standing copy of it, meaning bulk caching, mirroring or archiving. So does sustained use above the published allowance of your plan. And operating a service that offers data access in competition with ours is outside the licence altogether.

Short-lived caching of individual responses inside your own application is permitted and expected. Section 9 of the Terms of Use is the authority for all of this.

Enterprise is a published plan and you buy it yourself, alongside Hobby and Pro, from app.bottlebluellc.com. Keeping a standing copy of the data, and handing it onward to your users through a machine-readable interface, are included in the Enterprise plan and governed by the Terms of Use, so there is nothing separate to sign. Volume above the Enterprise allowance is arranged individually as a custom plan, by email to support@bottlebluellc.com.

Data platforms (the platform field)

The stateful endpoints (notes, lists, tasks, articles and their social actions, streaks, environments, plants, devices, and the other building blocks) are multi-tenant by platform. One set of endpoints can back several apps and each account's own projects at the same time, without their data colliding. Add an optional platform value to a request and it segments the data that request reads or writes.

Note: using platform: "bb_aquabotany" puts you in that product's data namespace, and the sign-in response echoes it back as data_platform so you can send it here. What kind of app you signed in from is a separate field, client, which never affects data.

How to send it

Send platform as a query-string parameter (any method) or as a field in the JSON body (on writes). If both are present the query string wins. Omit it to use your default namespace.

Values

ValueMeaning
omitted or emptyYour personal default namespace. All data is already scoped to your account, so this is your private global space.
my_project
(any non-numeric string, up to 64 chars of letters, digits, and _.:-)
One of your own projects, created implicitly the first time you use the name. It is private to your account: with your key you only ever reach your own data, so two different accounts can safely reuse the same project name and never see each other's rows.
bb_aquabotany
(a product slug)
A Bottle Blue product namespace, named by a friendly slug: bb_aquabotany, bb_pocketquant, bb_bottleblue, bb_apparatus, bb_trackless. This is the preferred way to address a product. A slug is reserved, so it cannot also be used as a personal project name, and it must be a registered, active product or the request is rejected 404 UNKNOWN_PLATFORM. Any authenticated request may currently address a product namespace, including on the Free tier - a paid-plan requirement (Hobby, Pro, or Enterprise) is wired but not yet enforced at the platform-resolution layer. Standard per-endpoint gating and rate limits still apply. Signed-in users of a Bottle Blue app reach their own data in that app's namespace through their in-app subscription instead; an in-app subscription does not include API access.
*Wildcard: read across your own platforms (your projects and the default namespace). A Bottle Blue product namespace is a shared space rather than one of yours, so its rows are not included; read a product namespace by naming it. Reads only; writing to * is rejected 400 WILDCARD_WRITE.

You cannot name a project a reserved word (platform, platforms, all, none); those return 400 RESERVED_PLATFORM. Sending platform: "default" is not an error; it is simply the same as omitting the field, so default can never be a separate project. A single item addressed by id lives in exactly one platform, so fetching it under the wrong platform returns 404. Your own platforms (your projects, the default namespace, and the * wildcard) are private to your account: every read returns only your own data. The one exception is a Bottle Blue product platform (bb_), where a few features are deliberately shared community spaces: the events, challenges and rewards catalogs read as a shared feed there, and leaderboards and scoreboards are shared boards. On your own project platforms even those are private to you.

Examples

# Write into a project namespace (platform in the body)
curl https://api.bottlebluellc.com/v1/notes \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Seedlings", "content": "start basil", "platform": "my_garden"}'

# List just that project (platform as a query parameter)
curl "https://api.bottlebluellc.com/v1/notes?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"

# List across every platform you own
curl "https://api.bottlebluellc.com/v1/notes?platform=*" \
  -H "x-api-key: $BBL_API_KEY"
import os, requests

API_KEY = os.environ["BBL_API_KEY"]
BASE = "https://api.bottlebluellc.com/v1"

# Write into a project namespace
requests.post(f"{BASE}/notes", headers={"x-api-key": API_KEY},
              json={"title": "Seedlings", "content": "start basil", "platform": "my_garden"})

# List one platform, then across all of them
requests.get(f"{BASE}/notes", headers={"x-api-key": API_KEY}, params={"platform": "my_garden"})
requests.get(f"{BASE}/notes", headers={"x-api-key": API_KEY}, params={"platform": "*"})
const API_KEY = process.env.BBL_API_KEY;
const BASE = "https://api.bottlebluellc.com/v1";

// Write into a project namespace
await fetch(`${BASE}/notes`, {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({ title: "Seedlings", content: "start basil", platform: "my_garden" })
});

// List one platform, then across all of them
await fetch(`${BASE}/notes?platform=my_garden`, { headers: { "x-api-key": API_KEY } });
await fetch(`${BASE}/notes?platform=*`, { headers: { "x-api-key": API_KEY } });

Where it applies

Every stateful building block accepts platform: notes, lists, queues, tasks, assets, articles and their social actions, streaks, leaderboards, scoreboards, rewards, events, challenges, actions, notifications, work orders, reminders, settings, image uploads, environments, plants, devices, and commands. Utility and read-only data endpoints (math, weather, market data, calculators, government data) and the /auth and billing endpoints are global and ignore it.

A few families behave slightly differently:

  • Social (article likes, comments, shares, feed): a social action carries the same platform as its article, so likes and comments are scoped to the platform the article was published in.
  • Streaks: a streak is inherently single-platform (one profile, one date range), so * is rejected here with 400 WILDCARD_UNSUPPORTED. Use a specific platform or omit it.
  • Per-account routes: your reward-points balance and redemption history are global to your account, so GET /rewards/points and GET /rewards/history ignore platform. The same goes for GET /images (the presigned upload URL); the platform is recorded when the upload is registered with POST /images.

The Link System (the /links endpoint)

Every other building block stores content. /links stores relationships: a small record connecting any two records in your account. Picture a gardening event, "Spring planting day". On its own it is just a calendar entry, but the day has moving parts: a task to water the new seedlings, and a reminder to buy mulch the day before. A link lets the event own each of those, so afterward you can ask one question that was impossible before - what belongs to Spring planting day? - and get the watering task and the reminder back. This section explains the idea; the full request and response reference lives in the /links endpoint operations further down the page.

A reference is an address

Both ends of a link are written as a ref: one string that names any record in the system, the way a postal address names one house. Here is the ref for the Spring planting day event:

bb:event:default:evt_8f3a2c
PieceMeaning
bbFixed prefix marking a Bottle Blue reference.
eventWhich building block the record lives in - here, an event.
defaultWhich platform namespace, the same value you send as platform. See Data platforms.
evt_8f3a2cThe id POST /events already handed back as eventId. Every endpoint returns its own id field (eventId, task_id, reminder.id, note_id, and so on) - any of them slots straight in here.

Because one format addresses every entity, a single endpoint covers every combination of building blocks, including ones added later.

Linkable entities

note task list listitem setting asset article image reward action event challenge notification leaderboard scoreboard workorder reminder queueitem streak environment plant device command

Choosing a relationship type

Every link carries a type - the verb that says how the two records relate (has_task, has_reminder, and so on). The type is free-form: any string of letters, digits, and _ . - up to 64 characters is accepted. It is not a fixed enum - the API never checks the verb against a list, so the vocabulary is yours to choose to fit your domain. Omit it and the type defaults to related.

These are conventions we suggest, not values the API enforces - pick whatever reads well in your own data:

Suggested verbReads as
has_taskan event (or project) owns a task
has_reminderan event owns a reminder
blocksthis record must be done before another can start
depends_onthe reverse of blocks
part_ofthis record belongs to a larger one
attachmenta file or image attached to a record
relateda loose association (the default when type is omitted)

A worked example

Build the Spring planting day event, give it a watering task and a mulch reminder, then ask what belongs to it.

# 1. Create the three records exactly as you do today
curl https://api.aquabotany.com/v1/events \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Spring planting day", "type": "gardening", "description": "Get the beds in.", "datetime": "2026-09-01T09:00:00"}'
# -> {"eventId": "evt_8f3a2c"}

curl https://api.aquabotany.com/v1/tasks \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"action_type": "water", "details": {"plant": "seedlings", "amount_ml": 500}}'
# -> {"task_id": "tsk_abc123"}

curl https://api.aquabotany.com/v1/tasks/reminders \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Buy mulch", "due_at": "2026-08-30T08:00:00Z"}'
# -> {"reminder": {"id": "rem_45de67", ...}}

# 2. Connect them. The ids above become refs.
curl https://api.bottlebluellc.com/v1/links \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"from": "bb:event:default:evt_8f3a2c", "to": "bb:task:default:tsk_abc123", "type": "has_task"}'

curl https://api.bottlebluellc.com/v1/links \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"from": "bb:event:default:evt_8f3a2c", "to": "bb:reminder:default:rem_45de67", "type": "has_reminder"}'

# 3. What belongs to Spring planting day? (the task and the reminder come back)
curl "https://api.bottlebluellc.com/v1/links?from=bb:event:default:evt_8f3a2c" \
  -H "x-api-key: $BBL_API_KEY"

# 4. And the reverse: what is the watering task part of?
curl "https://api.bottlebluellc.com/v1/links?to=bb:task:default:tsk_abc123" \
  -H "x-api-key: $BBL_API_KEY"
import requests

AQ = "https://api.aquabotany.com/v1"
BB = "https://api.bottlebluellc.com/v1"
H = {"x-api-key": API_KEY}

# 1. Create the three records exactly as you do today
event_id = requests.post(f"{AQ}/events", headers=H, json={
    "title": "Spring planting day", "type": "gardening",
    "description": "Get the beds in.", "datetime": "2026-09-01T09:00:00"}).json()["eventId"]
task_id = requests.post(f"{AQ}/tasks", headers=H, json={
    "action_type": "water", "details": {"plant": "seedlings", "amount_ml": 500}}).json()["task_id"]
reminder_id = requests.post(f"{AQ}/tasks/reminders", headers=H, json={
    "title": "Buy mulch", "due_at": "2026-08-30T08:00:00Z"}).json()["reminder"]["id"]

# 2. Connect them. The ids above become refs.
requests.post(f"{BB}/links", headers=H, json={
    "from": f"bb:event:default:{event_id}",
    "to": f"bb:task:default:{task_id}",
    "type": "has_task",
})
requests.post(f"{BB}/links", headers=H, json={
    "from": f"bb:event:default:{event_id}",
    "to": f"bb:reminder:default:{reminder_id}",
    "type": "has_reminder",
})

# 3. What belongs to Spring planting day? (the task and the reminder)
belongs = requests.get(f"{BB}/links", headers=H,
                       params={"from": f"bb:event:default:{event_id}"}).json()

# 4. And the reverse: what is the watering task part of?
parents = requests.get(f"{BB}/links", headers=H,
                       params={"to": f"bb:task:default:{task_id}"}).json()
const AQ = "https://api.aquabotany.com/v1";
const BB = "https://api.bottlebluellc.com/v1";
const H = { "x-api-key": API_KEY, "Content-Type": "application/json" };

// 1. Create the three records exactly as you do today
const { eventId } = await (await fetch(`${AQ}/events`, {
  method: "POST", headers: H,
  body: JSON.stringify({ title: "Spring planting day", type: "gardening",
    description: "Get the beds in.", datetime: "2026-09-01T09:00:00" })
})).json();

const { task_id } = await (await fetch(`${AQ}/tasks`, {
  method: "POST", headers: H,
  body: JSON.stringify({ action_type: "water", details: { plant: "seedlings", amount_ml: 500 } })
})).json();

const { reminder } = await (await fetch(`${AQ}/tasks/reminders`, {
  method: "POST", headers: H,
  body: JSON.stringify({ title: "Buy mulch", due_at: "2026-08-30T08:00:00Z" })
})).json();
const reminder_id = reminder.id;

// 2. Connect them. The ids above become refs.
await fetch(`${BB}/links`, {
  method: "POST", headers: H,
  body: JSON.stringify({
    from: `bb:event:default:${eventId}`,
    to: `bb:task:default:${task_id}`,
    type: "has_task"
  })
});
await fetch(`${BB}/links`, {
  method: "POST", headers: H,
  body: JSON.stringify({
    from: `bb:event:default:${eventId}`,
    to: `bb:reminder:default:${reminder_id}`,
    type: "has_reminder"
  })
});

// 3. What belongs to Spring planting day? (the task and the reminder)
const belongs = await (await fetch(
  `${BB}/links?from=bb:event:default:${eventId}`, { headers: H })).json();

// 4. And the reverse: what is the watering task part of?
const parents = await (await fetch(
  `${BB}/links?to=bb:task:default:${task_id}`, { headers: H })).json();

Rules

  • Both ends must be in the same platform. A link joins two records inside one namespace, and both refs must name the platform the request is scoped to. Anything else returns 400 CROSS_PLATFORM_LINK. This may be relaxed in a later version; the ref format already carries a platform, so nothing you write today would need to change.
  • Links are private to your account. They are stored under your user, so nobody else can read, traverse, or delete them, even knowing the exact refs.
  • There are no duplicates and no link id. The from + type + to triple is the identity, so sending the same link twice is safe and simply replays the first one with "created": false.
  • A record cannot link to itself: 400 SELF_LINK.
  • The wildcard is read-only. platform=* reads links across every platform you have; writing to it returns 400 WILDCARD_WRITE.
  • Deleting a record does not delete links pointing at it. Nothing checks a ref against its target. If the target is gone, following the ref returns 404 and you can delete the link yourself. This keeps a delete on any building block fast and predictable.
  • Up to 500 links per account, after which a create returns 409 LIMIT_REACHED.

One call instead of two: ?expand=links

Every building block with a get-one-record endpoint supports the expand=links query parameter. Add it and the response carries a links object alongside the record, so reading a record and its relationships is one round trip instead of a read plus a /links query:

GET /notes/{note_id}?expand=links

{
  "note_id": "9b1c2f6e-...",
  "title": "Spring planting day prep",
  ...,
  "links": {
    "out": [ { "from": "bb:note:default:9b1c2f6e-...",
               "to": "bb:task:default:abc123",
               "type": "has_task",
               "platform": "default",
               "created_at": "2026-07-23T12:00:00" } ],
    "in": [],
    "truncated": false
  }
}

out rows are links FROM this record; in rows are links TO it; each row is the same shape GET /links returns. Three rules keep it predictable:

  • Opt-in only. Without the parameter the response is exactly what it always was - no links key appears, so existing clients see nothing new until they ask.
  • It is sugar, not a replacement. Each direction is capped at 50 rows and does not paginate. "truncated": true means there are more; the full, paginated answer lives at GET /links?from=... / ?to=....
  • Scoped like everything else. Expansion is anchored on the record's own platform namespace and only ever returns your own links.

Supported on the get-one-record endpoints of /notes, /tasks, /list, /settings, /assets, /articles, /events, /challenges, /rewards, /actions, /leaderboard, /scoreboard, /streaks, and /workorder.

Market data

Base URL https://api.bottlebluellc.com/v1

GETapi.bottlebluellc.com/market

Discovery index: every market and government dataset this API serves.

curl https://api.bottlebluellc.com/v1/market \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/market",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/market", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/market", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "market_categories": {
    "bonds":          { "category": "BOND_YIELD",     "snapshot": "/market/bonds",          "history": "/market/bonds/{entity}?start=&end=" },
    "box-office":     { "category": "BOX_OFFICE",     "snapshot": "/market/box-office",     "history": "/market/box-office/{entity}?start=&end=" },
    "commodities":    { "category": "COMMODITY",      "snapshot": "/market/commodities",    "history": "/market/commodities/{entity}?start=&end=" },
    "cot":            { "category": "COT",            "snapshot": "/market/cot",            "history": "/market/cot/{entity}?start=&end=" },
    "crypto":         { "category": "CRYPTO",         "snapshot": "/market/crypto",         "history": "/market/crypto/{entity}?start=&end=" },
    "crypto-metrics": { "category": "CRYPTO_METRIC",  "snapshot": "/market/crypto-metrics", "history": "/market/crypto-metrics/{entity}?start=&end=" },
    "debt":           { "category": "FISCAL",         "snapshot": "/market/debt",           "history": "/market/debt/{entity}?start=&end=" },
    "drought":        { "category": "DROUGHT",        "snapshot": "/market/drought",        "history": "/market/drought/{entity}?start=&end=" },
    "econ":           { "category": "ECON_INDICATOR", "snapshot": "/market/econ",           "history": "/market/econ/{entity}?start=&end=" },
    "energy":         { "category": "ENERGY",         "snapshot": "/market/energy",         "history": "/market/energy/{entity}?start=&end=" },
    "fx":             { "category": "FX",             "snapshot": "/market/fx",             "history": "/market/fx/{entity}?start=&end=" },
    "global-indices": { "category": "GLOBAL_INDEX",   "snapshot": "/market/global-indices", "history": "/market/global-indices/{entity}?start=&end=" },
    "indices":        { "category": "INDEX",          "snapshot": "/market/indices",        "history": "/market/indices/{entity}?start=&end=" },
    "metals":         { "category": "METAL_PRICE",    "snapshot": "/market/metals",         "history": "/market/metals/{entity}?start=&end=" },
    "rates":          { "category": "INTEREST_RATE",  "snapshot": "/market/rates",          "history": "/market/rates/{entity}?start=&end=" },
    "rig-count":      { "category": "RIG_COUNT",      "snapshot": "/market/rig-count",      "history": "/market/rig-count/{entity}?start=&end=" },
    "sectors":        { "category": "SECTOR",         "snapshot": "/market/sectors",        "history": "/market/sectors/{entity}?start=&end=" },
    "sentiment":      { "category": "SENTIMENT",      "snapshot": "/market/sentiment",      "history": "/market/sentiment/{entity}?start=&end=" },
    "spreads":        { "category": "SPREAD",         "snapshot": "/market/spreads",        "history": "/market/spreads/{entity}?start=&end=" },
    "weather":        { "category": "WEATHER",        "snapshot": "/market/weather",        "history": "/market/weather/{entity}?start=&end=" }
  },
  "analytics": {
    "stats": {
      "all": "/market/stats",
      "by_category": "/market/stats/{category}   (category = any market slug, e.g. bonds)",
      "by_entity": "/market/stats/{category}/{entity}   e.g. /market/stats/bonds/^TNX",
      "note": "per-instrument stats vs 5y of its own history; the date is the statement day, each row's 'latest_date' is the underlying data day. z-score / realized vol / momentum fields are null when undefined."
    },
    "earnings": {
      "upcoming": "/market/earnings?days=N",
      "by_ticker": "/market/earnings/{ticker}",
      "note": "forward-looking: rows are written ahead of the report date, so this queries forward from today (default 8 days)."
    }
  },
  "government_datasets": {
    "13f-changes":  "/government/13f-changes?year=&limit=",
    "congress":     "/government/congress?year=&limit=",
    "contracts":    "/government/contracts?year=&limit=",
    "house":        "/government/house?year=&limit=",
    "insiders":     "/government/insiders?year=&limit=",
    "lobbying":     "/government/lobbying?year=&limit=",
    "off-exchange": "/government/off-exchange?year=&limit=",
    "senate":       "/government/senate?year=&limit=",
    "wsb":          "/government/wsb?year=&limit="
  },
  "government_analytics": {
    "leaderboard": "/government/leaderboard?date=&limit=&chamber=",
    "portfolio": "/government/portfolio/{chamber}/{name}?date=",
    "note": "politician books + P&L, rebuilt daily at 6:30 PM ET; chamber is senate, house or executive; {name} must match the stored name, which the leaderboard lists."
  },
  "feeds": {
    "news": "/market/news?source=&limit=",
    "news_sources": ["MarketWatch", "Yahoo Finance", "Google News", "Google News Tech", "Government AI"],
    "trending": "/market/trending?source=&limit=",
    "trending_sources": ["Yahoo Finance", "pocketquant.io"],
    "predictions": "/market/predictions?limit="
  }
}
GETapi.bottlebluellc.com/market/news

Stored market headlines.

Most sources are scraped headlines with a link. The Government AI source is different: it is a daily AI-written summary of the previous day's government trading data. Its headline is the full post text, its topic names the day's dataset (e.g. Congress Trades), and it has no link (the summary is the content). Government AI rows appear once per posting day, Monday through Saturday.

ParameterInDescription
sourcequeryoptional Filter by source: MarketWatch, Yahoo Finance, Google News, Google News Tech, or Government AI.
limitqueryoptional Max items (default 25).
curl https://api.bottlebluellc.com/v1/market/news \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/market/news",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/market/news", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/market/news", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "sources": [
    "Government AI",
    "pocketquant.io",
    "..."
  ],
  "count": 3,
  "items": [
    {
      "source": "Government AI",
      "timestamp": "2026-07-08T13:30:02.118392",
      "headline": "Yesterday's congressional filings leaned into energy: four members disclosed new positions in XOM and CVX while two reported selling semiconductor holdings, a rotation worth watching against this week's crude strength.",
      "topic": "Congress Trades"
    },
    {
      "source": "pocketquant.io",
      "timestamp": "2026-07-08T12:31:04.118392",
      "headline": "Stocks edge higher as June CPI cools more than expected"
    },
    {
      "source": "pocketquant.io",
      "timestamp": "2026-07-08T11:58:47.902165",
      "headline": "Fed officials signal patience on rate cuts ahead of FOMC minutes"
    }
  ]
}
GETapi.bottlebluellc.com/market/predictions

Daily model-generated SPY predictions.

Items written after 2026-07-27 may also carry prediction_short, a short-form variant of the same day's call written for quick display, alongside the full prediction text. Older rows have only prediction.

curl https://api.bottlebluellc.com/v1/market/predictions \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/market/predictions",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/market/predictions", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/market/predictions", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "count": 2,
  "items": [
    {
      "ticker": "SPY",
      "timestamp": "2026-07-08T09:15:02.481932",
      "prediction": "SPY is likely to drift modestly higher today. Futures point to a flat-to-positive open after a cooler June CPI print, 10-year yields eased to 4.18%, and breadth was constructive yesterday. Expect a range of roughly 618-624 with a close near 622, barring surprises in this afternoon's FOMC minutes.",
      "prediction_short": "SPY leaning higher: cool CPI, easing yields, constructive breadth. Watching 618-624 with a close near 622."
    },
    {
      "ticker": "SPY",
      "timestamp": "2026-07-07T09:15:01.276854",
      "prediction": "Expect SPY to trade sideways with a slight upward bias. Overnight futures are flat, crude is stable near $67, and there are no major data releases; a close between 619 and 623 looks most probable."
    }
  ]
}
GETapi.bottlebluellc.com/market/{category}

Latest daily snapshot for a market category.

ParameterInDescription
categorypathrequired One of bonds, commodities, crypto, indices, global-indices, sectors, fx, rates, econ, metals, debt, crypto-metrics, sentiment, cot, energy, weather, drought, rig-count, box-office, spreads. The drought snapshot's coverage bands are cumulative (d0 means "D0 or worse", so they do not sum to 100) and include the DSCI index; box-office returns the weekend top-10 per-film rows plus a weekend-total row (see the worked example below); metals rows written after 2026-07-27 carry usd_per_unit inside price_data, the actual USD price (USD per troy ounce for XAU/XAG, USD per EUR for EUR), while price_in_usd is the raw units-per-USD rate kept under its historical name for compatibility, not the price; the spreads copper/gold ratio is a legitimately small number (about 0.0013), returned unrounded.
datequeryoptional YYYY-MM-DD; defaults to the latest available day.
curl https://api.bottlebluellc.com/v1/market/crypto \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/market/crypto",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/market/crypto", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/market/crypto", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "category": "crypto",
  "date": "2026-07-08",
  "count": 2,
  "items": [
    {
      "date": "2026-07-08",
      "entity": "BTC-USD",
      "timestamp": "2026-07-08T13:35:12.408213Z",
      "label": "Bitcoin",
      "ticker": "BTC-USD",
      "price": 118432.27,
      "prev_close": 117210.55,
      "change": 1221.72,
      "change_pct": 1.04,
      "source": "pocketquant.io"
    },
    {
      "date": "2026-07-08",
      "entity": "ETH-USD",
      "timestamp": "2026-07-08T13:35:12.734569Z",
      "label": "Ethereum",
      "ticker": "ETH-USD",
      "price": 4381.9,
      "prev_close": 4402.15,
      "change": -20.25,
      "change_pct": -0.46,
      "source": "pocketquant.io"
    }
  ]
}

Worked example: the box-office weekend

GET /market/box-office returns the most recent weekend's top-10 films, one row per film, plus one aggregate row derived from the weekend total. The snapshot date is the Sunday the weekend ends (rows are typically published early the following week). Per-film fields: rank, title, gross (this weekend's gross in USD), change_pct_lw (percent change vs last weekend, null for a debut), theaters, total_gross (running total for the release), weeks (weeks in release), distributor, and ticker, the distributor's listed parent company (null when the distributor has no listed parent, e.g. A24 or Angel). The aggregate row (entity TOTAL) carries the summed top-10 weekend gross as value plus label, units, and films. The example below shows the first two film rows and the total row; a real response has all ten films (count 11).

200 Response
{
  "category": "box-office",
  "date": "2026-07-19",
  "count": 11,
  "items": [
    {
      "date": "2026-07-19",
      "entity": "01",
      "weekend_id": "2026W29",
      "rank": 1,
      "title": "The Odyssey",
      "gross": 123502900,
      "change_pct_lw": null,
      "theaters": 3919,
      "total_gross": 123502900,
      "weeks": 1,
      "distributor": "Universal Pictures",
      "ticker": "CMCSA",
      "timestamp": "2026-07-27T22:00:06.071117Z",
      "source": "Bottle API"
    },
    {
      "date": "2026-07-19",
      "entity": "02",
      "weekend_id": "2026W29",
      "rank": 2,
      "title": "Moana",
      "gross": 17937618,
      "change_pct_lw": -58.4,
      "theaters": 4200,
      "total_gross": 81019028,
      "weeks": 2,
      "distributor": "Walt Disney Studios Motion Pictures",
      "ticker": "DIS",
      "timestamp": "2026-07-27T22:00:06.553191Z",
      "source": "Bottle API"
    },
    {
      "date": "2026-07-19",
      "entity": "TOTAL",
      "weekend_id": "2026W29",
      "label": "Top 10 Weekend Gross",
      "value": 185866200,
      "units": "USD",
      "films": 10,
      "timestamp": "2026-07-27T22:00:07.952037Z",
      "source": "Bottle API"
    }
  ]
}
GETapi.bottlebluellc.com/market/{category}/{entity}

Historical time series for one instrument or series.

For box-office, the only chartable entity is TOTAL: GET /market/box-office/TOTAL charts the top-10 weekend total (value, USD), one point per weekend, dated the Sunday the weekend ends. Per-film ranks are not stable series, so individual films are not chartable; read them from the snapshot instead.

ParameterInDescription
categorypathrequired A market category (see /market).
entitypathrequired Instrument/series id, e.g. ^TNX, BTC-USD, DFF.
startqueryoptional YYYY-MM-DD start of range.
endqueryoptional YYYY-MM-DD end of range (max 366 days).
curl https://api.bottlebluellc.com/v1/market/crypto/BTC-USD \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/market/crypto/BTC-USD",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/market/crypto/BTC-USD", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/market/crypto/BTC-USD", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "category": "crypto",
  "entity": "BTC-USD",
  "start": "2026-06-08",
  "end": "2026-07-08",
  "count": 2,
  "items": [
    {
      "date": "2026-07-07",
      "entity": "BTC-USD",
      "timestamp": "2026-07-07T13:35:11.106482Z",
      "label": "Bitcoin",
      "ticker": "BTC-USD",
      "price": 117210.55,
      "prev_close": 116004.9,
      "change": 1205.65,
      "change_pct": 1.04,
      "source": "pocketquant.io"
    },
    {
      "date": "2026-07-08",
      "entity": "BTC-USD",
      "timestamp": "2026-07-08T13:35:12.408213Z",
      "label": "Bitcoin",
      "ticker": "BTC-USD",
      "price": 118432.27,
      "prev_close": 117210.55,
      "change": 1221.72,
      "change_pct": 1.04,
      "source": "pocketquant.io"
    }
  ]
}
GETapi.bottlebluellc.com/market/earnings

Upcoming large-cap earnings (forward-looking).

Unlike every other market category, earnings rows are written ahead of their date (a scheduled report is not an observation), so this endpoint queries forward from today. Add /{ticker} for one symbol's next report, e.g. /market/earnings/AAPL.

ParameterInDescription
daysqueryoptional Forward window in days (default 8, max 40).
datequeryoptional An exact day YYYY-MM-DD instead of the forward window.
curl "https://api.bottlebluellc.com/v1/market/earnings?days=7" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/market/earnings",
    headers={"x-api-key": API_KEY},
    params={"days": 7},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/market/earnings?days=7", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/market/earnings?days=7", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "category": "earnings",
  "from": "2026-07-13",
  "to": "2026-07-20",
  "count": 39,
  "items": [
    {
      "date": "2026-07-15",
      "entity": "JNJ",
      "symbol": "JNJ",
      "name": "Johnson and Johnson",
      "report_date": "2026-07-15",
      "time": "Before Open",
      "market_cap": 400123000000,
      "fiscal_quarter": "Jun/2026",
      "eps_forecast": 2.68,
      "num_estimates": "18",
      "last_year_eps": 2.97
    }
  ]
}
GETapi.bottlebluellc.com/market/earnings/{ticker}

One symbol's next scheduled earnings report in the forward window.

Same forward-looking window as /market/earnings, narrowed to one ticker. A symbol reports at most once in the window, so items holds either that one scheduled report or nothing; widen days (up to 40) if the next report is further out. The response echoes the symbol back as ticker.

ParameterInDescription
tickerpathrequired The symbol, e.g. AAPL.
daysqueryoptional Forward window in days (default 8, max 40).
datequeryoptional An exact day YYYY-MM-DD instead of the forward window.
curl "https://api.bottlebluellc.com/v1/market/earnings/AAPL?days=40" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/market/earnings/AAPL",
    headers={"x-api-key": API_KEY},
    params={"days": 40},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/market/earnings/AAPL?days=40", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/market/earnings/AAPL?days=40", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "category": "earnings",
  "from": "2026-07-28",
  "to": "2026-09-05",
  "count": 1,
  "ticker": "AAPL",
  "items": [
    {
      "date": "2026-07-30",
      "entity": "AAPL",
      "symbol": "AAPL",
      "name": "Apple Inc.",
      "report_date": "2026-07-30",
      "time": "After Close",
      "market_cap": 4724334930960,
      "fiscal_quarter": "Jun/2026",
      "eps_forecast": 1.88,
      "num_estimates": "11",
      "last_year_eps": 1.57,
      "timestamp": "2026-07-26T22:00:20.099706Z"
    }
  ]
}
GETapi.bottlebluellc.com/market/stats

Per-instrument statistics scored against 5 years of history, for every instrument at once.

Every instrument in the store is scored against its own 5-year history. This is the firehose form; narrow it with /market/stats/{category} (one source category) or /market/stats/{category}/{entity} (one instrument). The date is the day the statement was made; each row's latest_date is the underlying data day and may lag by a day or two. The zscore, realized_vol_30d and change_*_pct fields are null by design when undefined (a flat series has no z-score); they are not coerced to zero.

ParameterInDescription
datequeryoptional YYYY-MM-DD; defaults to the latest statement day.
curl "https://api.bottlebluellc.com/v1/market/stats" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/market/stats",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/market/stats", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/market/stats", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "scope": "all",
  "date": "2026-07-27",
  "count": 104,
  "items": [
    {
      "date": "2026-07-27",
      "label": "US 10-Year Treasury",
      "entity": "^TNX",
      "source_category": "BOND_YIELD",
      "latest": 4.647,
      "latest_date": "2026-07-27",
      "observations": 1258,
      "mean": 3.7,
      "percentile": 96.86,
      "zscore": 1.0,
      "realized_vol_30d": 13.88,
      "change_12m_pct": 6.19,
      "high_52w": 4.711,
      "low_52w": 3.953,
      "lookback_years": 5
    },
    {
      "date": "2026-07-27",
      "label": "US 30-Year Treasury",
      "entity": "^TYX",
      "source_category": "BOND_YIELD",
      "latest": 5.125,
      "latest_date": "2026-07-27",
      "observations": 1258,
      "mean": 4.01,
      "percentile": 99.24,
      "zscore": 1.18,
      "realized_vol_30d": 11.49,
      "change_12m_pct": 4.36,
      "high_52w": 5.187,
      "low_52w": 4.539,
      "lookback_years": 5
    }
  ]
}
GETapi.bottlebluellc.com/market/stats/{category}

Per-instrument statistics for every instrument in one source category.

Same statement rows as /market/stats, filtered to one source category. The category path segment is a normal market slug; the response scopes itself to it. Items are abbreviated here; a real bonds response has all four Treasury series.

ParameterInDescription
categorypathrequired A market slug: one of bonds, commodities, crypto, indices, global-indices, sectors, fx, rates, econ, metals, debt, crypto-metrics, sentiment, cot, energy, weather, drought, rig-count, box-office, spreads.
datequeryoptional YYYY-MM-DD; defaults to the latest statement day.
curl "https://api.bottlebluellc.com/v1/market/stats/bonds" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/market/stats/bonds",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/market/stats/bonds", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/market/stats/bonds", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "scope": "bonds",
  "date": "2026-07-27",
  "count": 4,
  "items": [
    {
      "date": "2026-07-27",
      "label": "US 10-Year Treasury",
      "entity": "^TNX",
      "source_category": "BOND_YIELD",
      "latest": 4.647,
      "latest_date": "2026-07-27",
      "observations": 1258,
      "mean": 3.7,
      "percentile": 96.86,
      "zscore": 1.0,
      "realized_vol_30d": 13.88,
      "change_12m_pct": 6.19,
      "high_52w": 4.711,
      "low_52w": 3.953,
      "lookback_years": 5
    },
    {
      "date": "2026-07-27",
      "label": "US 30-Year Treasury",
      "entity": "^TYX",
      "source_category": "BOND_YIELD",
      "latest": 5.125,
      "latest_date": "2026-07-27",
      "observations": 1258,
      "mean": 4.01,
      "percentile": 99.24,
      "zscore": 1.18,
      "realized_vol_30d": 11.49,
      "change_12m_pct": 4.36,
      "high_52w": 5.187,
      "low_52w": 4.539,
      "lookback_years": 5
    }
  ]
}
GETapi.bottlebluellc.com/market/stats/{category}/{entity}

The full statistics statement for one instrument.

The narrowest stats form returns one singular item rather than an items array, with the complete field set: distribution stats over the 5-year lookback (mean, stdev, min, max, percentile, zscore), momentum (change_1m_pct through change_12m_pct), volatility (realized_vol_30d, annualized from the last vol_window_observations observations), and the 52-week range with distances from it.

ParameterInDescription
categorypathrequired A market slug (see /market/stats/{category} for the full list).
entitypathrequired An instrument id under that category, e.g. ^TNX.
datequeryoptional YYYY-MM-DD; defaults to the latest statement day.
curl "https://api.bottlebluellc.com/v1/market/stats/bonds/^TNX" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/market/stats/bonds/^TNX",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/market/stats/bonds/^TNX", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/market/stats/bonds/^TNX", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "scope": "bonds",
  "entity": "^TNX",
  "date": "2026-07-27",
  "item": {
    "date": "2026-07-27",
    "label": "US 10-Year Treasury",
    "entity": "^TNX",
    "source_category": "BOND_YIELD",
    "latest": 4.647,
    "latest_date": "2026-07-27",
    "observations": 1258,
    "lookback_years": 5,
    "periods_per_year": 252,
    "mean": 3.7001,
    "stdev": 0.9494,
    "min": 1.174,
    "max": 4.988,
    "percentile": 96.86,
    "zscore": 1.0,
    "realized_vol_30d": 13.88,
    "vol_window_observations": 30,
    "change_1m_pct": 4.03,
    "change_3m_pct": 5.85,
    "change_6m_pct": 9.94,
    "change_12m_pct": 6.19,
    "high_52w": 4.711,
    "low_52w": 3.953,
    "pct_from_high_52w": -1.36,
    "pct_from_low_52w": 17.56,
    "timestamp": "2026-07-27T23:00:16.490170Z"
  }
}

Government data

Base URL https://api.bottlebluellc.com/v1

GETapi.bottlebluellc.com/government/{dataset}

US government financial-disclosure datasets.

ParameterInDescription
datasetpathrequired congress, senate, house, lobbying, contracts, off-exchange, insiders, wsb, or 13f-changes.
yearqueryoptional 4-digit year (default current).
limitqueryoptional Max rows, 1 to 500. Defaults to 100 on insiders.
tickerqueryoptional insiders only. Returns just that symbol's filings. Case does not matter and surrounding spaces are ignored. Other datasets accept the parameter and ignore it, so the rows come back unfiltered.
startKeyqueryoptional insiders only. The nextKey from your previous response, to fetch the next page. Other datasets ignore it.
allYearsqueryoptional insiders only. Send true together with ticker to search every stored year instead of one. It has no effect on its own.

Paging the insiders dataset. Every response carries truncated. When it is true, insiders also returns a nextKey: send that back as startKey to get the next page, and stop when truncated is false. Treat nextKey as an opaque string. It is bound to the query that produced it, so a key from one ticker or year cannot resume another and is refused with 400 invalid startKey.

curl https://api.bottlebluellc.com/v1/government/congress \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/government/congress",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/government/congress", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/government/congress", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "dataset": "congress",
  "year": "2026",
  "count": 2,
  "items": [
    {
      "name": "Nancy Pelosi",
      "timestamp": "2026-06-24",
      "year": "2026",
      "amount": 32500,
      "report_date": "2026-07-02",
      "last_modified": "2026-07-02",
      "price_range": "$15,001 - $50,000",
      "ticker": "NVDA",
      "transaction": "Purchase",
      "party": "D",
      "house": "Representative"
    },
    {
      "name": "Tommy Tuberville",
      "timestamp": "2026-06-18",
      "year": "2026",
      "amount": 8000,
      "report_date": "2026-06-30",
      "last_modified": "2026-06-30",
      "price_range": "$1,001 - $15,000",
      "ticker": "XOM",
      "transaction": "Sale",
      "party": "R",
      "house": "Senator"
    }
  ],
  "truncated": false
}

Item shapes by dataset

The envelope (dataset, year, count, truncated, items) is the same for all nine datasets; the item fields differ per dataset. congress is shown above. The table and the sample items below give the shape of the other eight, one real row each. Numeric amounts are plain JSON numbers; year is a string everywhere except contracts, where it is a number.

DatasetItem fields
senatesenator, bio_guide_id, ticker, transaction, amount, price_range, timestamp, year, last_modified
housename, ticker, transaction, amount, price_range, description, timestamp, year, last_modified
lobbyingclient, registrant, ticker, amount, issue, specific_issue, timestamp, year, filing_uuid, filing_period (issue and specific_issue may be null; filing_uuid and filing_period identify the Senate LDA filing and its reporting period, and are present on rows stored from 2026-09-09 onward)
contractsticker, agency, description, amount, quarter, year, timestamp (older rows may lack agency/description)
off-exchangeticker, dpi (dark-pool indicator, the short share of OTC volume), otc_short, otc_total, timestamp, year
insidersname, position, ticker, transaction, shares, price, value, own_type, report_date, timestamp, year, acquired_disposed (acquired_disposed is "A" if the shares were acquired and "D" if they were disposed of, taken from the filing, and is present on rows stored from 2026-10-03 onward)
wsbticker, rank, mentions, sentiment, timestamp, year (sentiment may be empty)
13f-changesowner, ticker, change, old_shares, new_shares, report_period, timestamp, year (report_period may be empty)
200 Sample item per dataset
{
  "senate": {
    "senator": "Sheldon Whitehouse",
    "bio_guide_id": "W000802",
    "ticker": "NVDA",
    "transaction": "Sale (Partial)",
    "amount": 15001,
    "price_range": "$15,001 - $50,000",
    "timestamp": "2026-06-30T00:00:00Z",
    "year": "2026",
    "last_modified": "2026-07-09"
  },
  "house": {
    "name": "James A. Himes",
    "ticker": "XOM",
    "transaction": "Sale",
    "amount": 15001,
    "price_range": "$15,001 - $50,000",
    "description": "N/A",
    "timestamp": "2026-07-20T00:00:00Z",
    "year": "2026",
    "last_modified": "2026-07-21"
  },
  "lobbying": {
    "client": "OCEANEERING INTERNATIONAL INC.",
    "registrant": "IBEX2 SOLUTIONS, LLC",
    "ticker": "OII",
    "amount": 30000,
    "issue": null,
    "specific_issue": null,
    "timestamp": "2026-09-09",
    "year": "2026",
    "filing_uuid": "ef6248b6-0c1e-4a52-9f3a-51d35e9e7c40",
    "filing_period": "2nd Quarter (Apr 1 - June 30)"
  },
  "contracts": {
    "ticker": "TDW",
    "agency": "Department of State",
    "description": "FUEL MANAGEMENT SYSTEM PM",
    "amount": 16585,
    "quarter": 3,
    "year": 2026,
    "timestamp": "2026-07-26"
  },
  "off-exchange": {
    "ticker": "RIVN",
    "dpi": 0.497587333917165,
    "otc_short": 7595715,
    "otc_total": 15265090,
    "timestamp": "2026-07-24",
    "year": "2026"
  },
  "insiders": {
    "name": "Patel Snehal",
    "position": "CEO and CFO",
    "ticker": "GLSI",
    "transaction": "Purchase",
    "shares": 1100,
    "price": 12.88,
    "value": 14168,
    "own_type": "Direct",
    "report_date": "2026-07-23",
    "timestamp": "2026-07-24",
    "year": "2026",
    "acquired_disposed": "A"
  },
  "wsb": {
    "ticker": "AA",
    "rank": 52,
    "mentions": 3,
    "sentiment": "",
    "timestamp": "2026-07-27",
    "year": "2026"
  },
  "13f-changes": {
    "owner": "DT Investment Partners, LLC",
    "ticker": "AGG",
    "change": -9091,
    "old_shares": 34507,
    "new_shares": 25416,
    "report_period": "",
    "timestamp": "2026-07-27",
    "year": "2026"
  }
}
GETapi.bottlebluellc.com/government/leaderboard

Daily performance ranking of politician stock portfolios.

House and Senate members plus the president (chamber Executive) are ranked by portfolio return, one board per build day, rebuilt daily at 6:30 PM ET.

The inputs are public disclosures: congressional trade filings for the House and Senate, and the president's OGE Form 278e filings for the Executive chamber.

A politician is ranked only if they have deployed at least $10,000, have at least 3 disclosed trades, and have a disclosure within the last 730 days (~24 months) - this keeps a stale, no-longer-trading filer's old return from parking at the top of the board. Each row carries disclosed_trades (the count backing the ranking) and last_trade_date so a caller can see what a ranking rests on.

Each row also carries bioguide_id, the member's identifier in the Biographical Directory of the United States Congress, which is the stable way to join a row to an outside congressional dataset instead of matching on a name that filings spell inconsistently. It is optional, and the key is omitted rather than empty when there is no id: always for the Executive chamber, because the directory covers Congress only, and occasionally for a House or Senate row when a filing's spelling of a name cannot be matched to exactly one member.

Test for the key's presence rather than for a truthy value, and treat one id as identifying one person: a build never carries the same bioguide_id on two rows.

With no date the API serves the most recent build, walking back up to 7 days; a pinned date with no build that day returns an empty board (count 0) rather than an error, matching the market snapshot endpoints. With chamber the filter is applied before the limit cut, so ?chamber=&limit= means the top N of that chamber, not that chamber's survivors of the top N; the response then echoes the chamber back lowercased as chamber. truncated is true when limit cut the list.

A malformed date returns 400 date must be YYYY-MM-DD, an unknown chamber returns 400 with the valid chambers list, and when no build exists in the last 7 days the endpoint returns 404. Items are abbreviated below; a real board carries every ranked politician.

ParameterInDescription
datequeryoptional YYYY-MM-DD build date; defaults to the most recent build within 7 days.
limitqueryoptional Max rows (default 50). Out-of-range or non-numeric values are clamped or defaulted, never rejected.
chamberqueryoptional Filter to one chamber: senate, house, or executive (case-insensitive).
# The full board, latest build
curl https://api.bottlebluellc.com/v1/government/leaderboard \
  -H "x-api-key: $BBL_API_KEY"

# One chamber's top 10
curl "https://api.bottlebluellc.com/v1/government/leaderboard?chamber=senate&limit=10" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/government/leaderboard",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/government/leaderboard", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/government/leaderboard", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "dataset": "leaderboard",
  "date": "2026-07-30",
  "count": 2,
  "truncated": false,
  "items": [
    {
      "rank": 1,
      "name": "David Perdue",
      "chamber": "Senate",
      "bioguide_id": "P000612",
      "return_pct": 12.4,
      "total_pnl": 184000.5,
      "market_value": 1660000.0,
      "total_invested": 1476000.0,
      "open_positions": 14,
      "disclosed_trades": 37,
      "last_trade_date": "2026-07-22"
    },
    {
      "rank": 2,
      "name": "Josh Gottheimer",
      "chamber": "House",
      "bioguide_id": "G000583",
      "return_pct": 9.8,
      "total_pnl": 61250.0,
      "market_value": 686500.0,
      "total_invested": 625250.0,
      "open_positions": 22,
      "disclosed_trades": 18,
      "last_trade_date": "2026-06-30"
    }
  ]
}
GETapi.bottlebluellc.com/government/portfolio/{chamber}/{name}

One politician's current book, positions marked to market.

The narrowest government-analytics form returns one singular item rather than an items array: the politician's open positions marked to market (ticker, shares, cost_basis, market_value, unrealized_pnl, unrealized_pct per position) plus the book-level totals (total_invested, market_value, realized_pnl, unrealized_pnl, total_pnl, return_pct, disclosed_trades, last_trade_date). Same daily 6:30 PM ET build as /government/leaderboard, and name must be the exact name as that board lists it; URL-encode spaces, e.g. /government/portfolio/executive/Donald%20J.%20Trump.

With no date the API serves the most recent build, walking back up to 7 days.

A malformed date returns 400 date must be YYYY-MM-DD, an unknown chamber returns 400 with the valid chambers list, and an unknown name returns 404 with a hint pointing at the leaderboard roster. Positions are abbreviated below; a real book lists all open_positions of them.

The item also carries bioguide_id on the same terms as the leaderboard row for the same politician, and the two always agree: the member's identifier in the Biographical Directory of the United States Congress, omitted rather than empty when there is no id, and never present for the Executive chamber.

ParameterInDescription
chamberpathrequired senate, house, or executive (case-insensitive).
namepathrequired The exact stored name as listed by the leaderboard, URL-encoded, e.g. David%20Perdue.
datequeryoptional YYYY-MM-DD build date; defaults to the most recent build within 7 days.
curl "https://api.bottlebluellc.com/v1/government/portfolio/senate/David%20Perdue" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/government/portfolio/senate/David%20Perdue",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/government/portfolio/senate/David%20Perdue", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/government/portfolio/senate/David%20Perdue", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "dataset": "portfolio",
  "chamber": "senate",
  "name": "David Perdue",
  "date": "2026-07-30",
  "item": {
    "name": "David Perdue",
    "chamber": "Senate",
    "bioguide_id": "P000612",
    "positions": [
      {
        "ticker": "AAPL",
        "shares": 120.0,
        "cost_basis": 21400.0,
        "market_value": 24800.0,
        "unrealized_pnl": 3400.0,
        "unrealized_pct": 15.9
      },
      {
        "ticker": "JPM",
        "shares": 85.0,
        "cost_basis": 14875.0,
        "market_value": 16320.0,
        "unrealized_pnl": 1445.0,
        "unrealized_pct": 9.7
      }
    ],
    "open_positions": 14,
    "total_invested": 1476000.0,
    "market_value": 1660000.0,
    "realized_pnl": 52000.0,
    "unrealized_pnl": 132000.5,
    "total_pnl": 184000.5,
    "return_pct": 12.4,
    "disclosed_trades": 37,
    "last_trade_date": "2026-07-22"
  }
}
GETapi.bottlebluellc.com/labor/layoffs

WARN layoff notices, as published by state labor agencies, newest notice first.

ParameterInDescription
statequeryrequired Two-letter state code. Case does not matter. A state with no notices returns an empty list rather than an error.
fromqueryoptional Earliest notice date to return, as YYYY-MM-DD. Inclusive.
toqueryoptional Latest notice date to return, as YYYY-MM-DD. Inclusive.
limitqueryoptional Max rows, 1 to 500. Defaults to 100.
startKeyqueryoptional The nextKey from your previous response, to fetch the next page.

Dates are notice dates, not effective dates. notice_date is when the employer filed the notice and is what from, to and the ordering use. effective_date is when the separations take effect and is usually later. Filter on the one you mean.

Fields vary by state. Each agency publishes its own columns, so rows are returned as filed rather than flattened into one shape. state, notice_date, notice_id, employer and employees are on every row; location is city in some states and county or address in others. Every row carries its own source and source_url, so the attribution travels with the data.

Paging. Every response carries truncated. When it is true a nextKey is returned: send that back as startKey for the next page, and stop when truncated is false. Treat nextKey as an opaque string. It is bound to the query that produced it, so a key from one state or date cannot resume another and is refused with 400.

Beta. This endpoint is in beta. The response carries "beta": true so a client can detect it without reading this page. Coverage is still growing and the shape of a row may gain fields, so read fields by name and ignore ones you do not recognise. It will not lose the fields documented here while it is in beta.

curl "https://api.bottlebluellc.com/v1/labor/layoffs?state=CA&limit=50" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/labor/layoffs?state=CA&limit=50",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/labor/layoffs?state=CA&limit=50", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/labor/layoffs?state=CA&limit=50", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "dataset": "layoffs",
  "beta": true,
  "state": "CA",
  "count": 1,
  "truncated": true,
  "items": [
    {
      "state": "CA",
      "notice_date": "2026-10-06",
      "effective_date": "2026-12-07",
      "notice_id": "CA-d3e96c254ea7bd82",
      "employer": "Beachbody, LLC",
      "employees": "80",
      "county": "Los Angeles County",
      "address": "400 North Continental Blvd.  El Segundo CA 90245",
      "layoff_type": "Layoff Permanent",
      "industry": "54 Professional Scientific and Technical Services",
      "source": "California EDD",
      "source_url": "https://edd.ca.gov/siteassets/files/jobs_and_training/warn/warn_report1.xlsx"
    }
  ],
  "nextKey": "eyJzdGF0ZSI6ICJDQSIsICJub3RpY2VfZGF0ZSI6ICIyMDI2LTEwLTA2In0"
}
GETapi.bottlebluellc.com/labor/h1b

H-1B labor condition applications, as published by the US Department of Labor, for one decision date.

ParameterInDescription
datequeryrequired The decision date to return, as YYYY-MM-DD. There is no default and no "latest": ask for the day you want.
limitqueryoptional Max rows, 1 to 500. Defaults to 100.
startKeyqueryoptional The nextKey from your previous response, to fetch the next page.

The source publishes quarterly, so most dates hold nothing. Applications arrive in a quarterly disclosure file and are grouped by the date the Department of Labor decided them. A date with no decisions returns an empty list, which is a normal answer and not an error. A busy decision date can hold several thousand applications, so page rather than assuming one response is the whole day.

No personal contact details are served. The published file includes contact names, emails and phone numbers for employer representatives and attorneys. Those columns are discarded when the data is collected, so they are not stored and cannot be returned. Every row carries its own source and source_file, so the attribution travels with the data.

Paging. Every response carries truncated. When it is true a nextKey is returned: send that back as startKey for the next page, and stop when truncated is false. Treat nextKey as an opaque string. It is bound to the query that produced it, so a key from one state or date cannot resume another and is refused with 400.

Beta. This endpoint is in beta. The response carries "beta": true so a client can detect it without reading this page. Coverage is still growing and the shape of a row may gain fields, so read fields by name and ignore ones you do not recognise. It will not lose the fields documented here while it is in beta.

curl "https://api.bottlebluellc.com/v1/labor/h1b?date=2026-06-26&limit=50" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/labor/h1b?date=2026-06-26&limit=50",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/labor/h1b?date=2026-06-26&limit=50", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/labor/h1b?date=2026-06-26&limit=50", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "dataset": "h1b",
  "beta": true,
  "date": "2026-06-26",
  "count": 1,
  "truncated": true,
  "items": [
    {
      "CASE_NUMBER": "I-200-23124-443019",
      "CASE_STATUS": "Certified - Withdrawn",
      "VISA_CLASS": "H-1B",
      "DECISION_DATE": "2026-06-26",
      "RECEIVED_DATE": "2023-05-04",
      "BEGIN_DATE": "2023-08-10",
      "END_DATE": "2026-08-09",
      "EMPLOYER_NAME": "Univ of WI System/Eau Claire",
      "EMPLOYER_CITY": "Madison",
      "EMPLOYER_STATE": "WI",
      "EMPLOYER_FEIN": "39-6006492",
      "JOB_TITLE": "Assistant Professor",
      "SOC_CODE": "25-1121.00",
      "SOC_TITLE": "Art, Drama, and Music Teachers, Postsecondary",
      "FULL_TIME_POSITION": "Y",
      "TOTAL_WORKER_POSITIONS": "1",
      "WORKSITE_CITY": "Eau Claire",
      "WORKSITE_STATE": "WI",
      "WORKSITE_POSTAL_CODE": "54701",
      "WAGE_RATE_OF_PAY_FROM": "56000",
      "WAGE_UNIT_OF_PAY": "Year",
      "PREVAILING_WAGE": "43780",
      "PW_UNIT_OF_PAY": "Year",
      "H_1B_DEPENDENT": "No",
      "WILLFUL_VIOLATOR": "No",
      "source": "DOL OFLC",
      "source_file": "LCA_Disclosure_Data_FY2026_Q3.xlsx",
      "source_fiscal_year": "2026",
      "source_quarter": "3"
    }
  ],
  "nextKey": "eyJQSyI6ICJIMUJfTENBIzIwMjYtMDYtMjYiLCAiU0siOiAiQ0FTRSNJLTIwMC0yMzEyNC00NDMwMTkifQ"
}

Utilities

Base URL https://api.bottlebluellc.com/v1

POSTapi.bottlebluellc.com/math

Practical calculator with ten categories: area, volume, geometry, construction & DIY, health & fitness, finance, statistics, probability, algebra, and unit-aware percentages. Browse every calculator type below.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "finance", "params": {"kind": "compound_interest", "principal": 1000, "rate": 0.05, "time": 10}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'finance', 'params': {'kind': 'compound_interest', 'principal': 1000, 'rate': 0.05, 'time': 10}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "finance", "params": {"kind": "compound_interest", "principal": 1000, "rate": 0.05, "time": 10}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "finance", "params": {"kind": "compound_interest", "principal": 1000, "rate": 0.05, "time": 10}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 1628.89,
  "meta": {
    "category": "finance",
    "formula_id": "finance:compound_interest"
  }
}

Every request has the same shape regardless of category: a type (one of the ten categories below), a params object specific to the kind you pick within that category, and an optional options object (precision, explain, and for finance:amortization_schedule also limit_rows and totals_only).

Show the formula: options.explain

Set explain: true to get the formula name and the exact inputs it used back in meta.explain, useful for showing your own users how a result was derived.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "health", "params": {"kind": "bmr", "gender": "male", "weight_kg": 80, "height_cm": 178, "age": 32}, "options": {"precision": 2, "explain": true}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'health', 'params': {'kind': 'bmr', 'gender': 'male', 'weight_kg': 80, 'height_cm': 178, 'age': 32}, 'options': {'precision': 2, 'explain': True}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "health", "params": {"kind": "bmr", "gender": "male", "weight_kg": 80, "height_cm": 178, "age": 32}, "options": {"precision": 2, "explain": true}})
});
const data = await resp.json();
200 Response
{
  "result": 1757.5,
  "meta": {
    "category": "health",
    "formula_id": "health:bmr",
    "explain": {
      "formula": "Mifflin-St Jeor",
      "inputs": {
        "kind": "bmr",
        "gender": "male",
        "weight_kg": 80,
        "height_cm": 178,
        "age": 32
      }
    }
  }
}

Batch mode: multiple calculations in one call

Send an array under batch instead of a single type/params/options object, up to 100 operations per request. Each item can override the top-level options. A failed item returns {"error": "..."} in its slot instead of failing the whole batch.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"batch": [{"type": "health", "params": {"kind": "bmi", "weight_kg": 75, "height_cm": 180}, "options": {"precision": 2}}, {"type": "geometry", "params": {"kind": "roof_pitch_angle", "rise": 6, "run": 12}, "options": {"precision": 2}}, {"type": "percentage", "params": {"kind": "tip", "amount": 64.5, "percent": 18}}]}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'batch': [{'type': 'health', 'params': {'kind': 'bmi', 'weight_kg': 75, 'height_cm': 180}, 'options': {'precision': 2}}, {'type': 'geometry', 'params': {'kind': 'roof_pitch_angle', 'rise': 6, 'run': 12}, 'options': {'precision': 2}}, {'type': 'percentage', 'params': {'kind': 'tip', 'amount': 64.5, 'percent': 18}}]},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"batch": [{"type": "health", "params": {"kind": "bmi", "weight_kg": 75, "height_cm": 180}, "options": {"precision": 2}}, {"type": "geometry", "params": {"kind": "roof_pitch_angle", "rise": 6, "run": 12}, "options": {"precision": 2}}, {"type": "percentage", "params": {"kind": "tip", "amount": 64.5, "percent": 18}}]})
});
const data = await resp.json();
200 Response
[
  23.15,
  26.57,
  11.61
]

Algebra3

Linear equations, 2x2 systems, and proportions.

linearSolve ax + b = c for x.a, b, c

Solve ax + b = c for x.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "algebra", "params": {"kind": "linear", "a": 3, "b": 5, "c": 20}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'algebra', 'params': {'kind': 'linear', 'a': 3, 'b': 5, 'c': 20}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "algebra", "params": {"kind": "linear", "a": 3, "b": 5, "c": 20}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "algebra", "params": {"kind": "linear", "a": 3, "b": 5, "c": 20}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 5.0,
  "meta": {
    "category": "algebra",
    "formula_id": "algebra:linear"
  }
}
system_2x2Solve a 2x2 linear system with Cramer's rule.a1, b1, c1, a2, b2, c2

Solve a 2x2 linear system with Cramer's rule.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "algebra", "params": {"kind": "system_2x2", "a1": 2, "b1": 3, "c1": 13, "a2": 4, "b2": -1, "c2": 5}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'algebra', 'params': {'kind': 'system_2x2', 'a1': 2, 'b1': 3, 'c1': 13, 'a2': 4, 'b2': -1, 'c2': 5}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "algebra", "params": {"kind": "system_2x2", "a1": 2, "b1": 3, "c1": 13, "a2": 4, "b2": -1, "c2": 5}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "algebra", "params": {"kind": "system_2x2", "a1": 2, "b1": 3, "c1": 13, "a2": 4, "b2": -1, "c2": 5}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": {
    "x": 2.0,
    "y": 3.0
  },
  "meta": {
    "category": "algebra",
    "formula_id": "algebra:system_2x2"
  }
}
proportionSolve the missing term in a/b = c/d.any 3 of a, b, c, d

Solve the missing term in a/b = c/d.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "algebra", "params": {"kind": "proportion", "a": 3, "b": 4, "d": 20}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'algebra', 'params': {'kind': 'proportion', 'a': 3, 'b': 4, 'd': 20}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "algebra", "params": {"kind": "proportion", "a": 3, "b": 4, "d": 20}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "algebra", "params": {"kind": "proportion", "a": 3, "b": 4, "d": 20}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": {
    "c": 15.0
  },
  "meta": {
    "category": "algebra",
    "formula_id": "algebra:proportion"
  }
}

Area8

Flat-shape area formulas: rectangle, triangle, circle, trapezoid, ellipse, sector, segment, regular polygon.

rectangleRectangle area.length, width

Rectangle area.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "area", "params": {"shape": "rectangle", "length": 12, "width": 8}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'area', 'params': {'shape': 'rectangle', 'length': 12, 'width': 8}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "area", "params": {"shape": "rectangle", "length": 12, "width": 8}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "area", "params": {"shape": "rectangle", "length": 12, "width": 8}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 96,
  "meta": {
    "category": "area",
    "formula_id": "area:rectangle"
  }
}
triangleTriangle area from base and height.base, height

Triangle area from base and height.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "area", "params": {"shape": "triangle", "base": 10, "height": 6}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'area', 'params': {'shape': 'triangle', 'base': 10, 'height': 6}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "area", "params": {"shape": "triangle", "base": 10, "height": 6}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "area", "params": {"shape": "triangle", "base": 10, "height": 6}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 30.0,
  "meta": {
    "category": "area",
    "formula_id": "area:triangle"
  }
}
circleCircle area.radius

Circle area.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "area", "params": {"shape": "circle", "radius": 4}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'area', 'params': {'shape': 'circle', 'radius': 4}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "area", "params": {"shape": "circle", "radius": 4}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "area", "params": {"shape": "circle", "radius": 4}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 50.27,
  "meta": {
    "category": "area",
    "formula_id": "area:circle"
  }
}
trapezoidTrapezoid area.base1, base2, height

Trapezoid area.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "area", "params": {"shape": "trapezoid", "base1": 8, "base2": 12, "height": 5}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'area', 'params': {'shape': 'trapezoid', 'base1': 8, 'base2': 12, 'height': 5}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "area", "params": {"shape": "trapezoid", "base1": 8, "base2": 12, "height": 5}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "area", "params": {"shape": "trapezoid", "base1": 8, "base2": 12, "height": 5}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 50.0,
  "meta": {
    "category": "area",
    "formula_id": "area:trapezoid"
  }
}
ellipseEllipse area from semi-axes.a, b

Ellipse area from semi-axes.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "area", "params": {"shape": "ellipse", "a": 6, "b": 3}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'area', 'params': {'shape': 'ellipse', 'a': 6, 'b': 3}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "area", "params": {"shape": "ellipse", "a": 6, "b": 3}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "area", "params": {"shape": "ellipse", "a": 6, "b": 3}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 56.55,
  "meta": {
    "category": "area",
    "formula_id": "area:ellipse"
  }
}
sectorCircular sector area.radius, angle_deg

Circular sector area.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "area", "params": {"shape": "sector", "radius": 10, "angle_deg": 45}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'area', 'params': {'shape': 'sector', 'radius': 10, 'angle_deg': 45}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "area", "params": {"shape": "sector", "radius": 10, "angle_deg": 45}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "area", "params": {"shape": "sector", "radius": 10, "angle_deg": 45}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 39.27,
  "meta": {
    "category": "area",
    "formula_id": "area:sector"
  }
}
segmentCircular segment area.radius, angle_deg

Circular segment area.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "area", "params": {"shape": "segment", "radius": 10, "angle_deg": 60}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'area', 'params': {'shape': 'segment', 'radius': 10, 'angle_deg': 60}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "area", "params": {"shape": "segment", "radius": 10, "angle_deg": 60}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "area", "params": {"shape": "segment", "radius": 10, "angle_deg": 60}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 9.06,
  "meta": {
    "category": "area",
    "formula_id": "area:segment"
  }
}
regular_polygonRegular polygon area from side count and side length.n, side

Regular polygon area from side count and side length.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "area", "params": {"shape": "regular_polygon", "n": 6, "side": 4}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'area', 'params': {'shape': 'regular_polygon', 'n': 6, 'side': 4}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "area", "params": {"shape": "regular_polygon", "n": 6, "side": 4}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "area", "params": {"shape": "regular_polygon", "n": 6, "side": 4}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 41.57,
  "meta": {
    "category": "area",
    "formula_id": "area:regular_polygon"
  }
}

Construction & DIY8

Job-site material takeoffs: board feet, concrete pours, drywall, paint, stairs, roofing bundles.

board_feetBoard feet of lumber.thickness_in, width_in, length_ft, quantity

Board feet of lumber.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "construction", "params": {"kind": "board_feet", "thickness_in": 2, "width_in": 6, "length_ft": 8, "quantity": 10}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'construction', 'params': {'kind': 'board_feet', 'thickness_in': 2, 'width_in': 6, 'length_ft': 8, 'quantity': 10}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "construction", "params": {"kind": "board_feet", "thickness_in": 2, "width_in": 6, "length_ft": 8, "quantity": 10}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "construction", "params": {"kind": "board_feet", "thickness_in": 2, "width_in": 6, "length_ft": 8, "quantity": 10}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 80.0,
  "meta": {
    "category": "construction",
    "formula_id": "construction:board_feet"
  }
}
concrete_slabConcrete volume for a slab.length, width, thickness

Concrete volume for a slab.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "construction", "params": {"kind": "concrete_slab", "length": 20, "width": 15, "thickness": 0.33}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'construction', 'params': {'kind': 'concrete_slab', 'length': 20, 'width': 15, 'thickness': 0.33}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "construction", "params": {"kind": "concrete_slab", "length": 20, "width": 15, "thickness": 0.33}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "construction", "params": {"kind": "concrete_slab", "length": 20, "width": 15, "thickness": 0.33}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 99.0,
  "meta": {
    "category": "construction",
    "formula_id": "construction:concrete_slab"
  }
}
concrete_footingConcrete volume for a footing.length, width, depth

Concrete volume for a footing.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "construction", "params": {"kind": "concrete_footing", "length": 30, "width": 1.5, "depth": 1}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'construction', 'params': {'kind': 'concrete_footing', 'length': 30, 'width': 1.5, 'depth': 1}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "construction", "params": {"kind": "concrete_footing", "length": 30, "width": 1.5, "depth": 1}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "construction", "params": {"kind": "concrete_footing", "length": 30, "width": 1.5, "depth": 1}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 45.0,
  "meta": {
    "category": "construction",
    "formula_id": "construction:concrete_footing"
  }
}
concrete_sonotubeConcrete volume for cylindrical pier forms.diameter, height, quantity

Concrete volume for cylindrical pier forms.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "construction", "params": {"kind": "concrete_sonotube", "diameter": 1, "height": 4, "quantity": 6}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'construction', 'params': {'kind': 'concrete_sonotube', 'diameter': 1, 'height': 4, 'quantity': 6}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "construction", "params": {"kind": "concrete_sonotube", "diameter": 1, "height": 4, "quantity": 6}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "construction", "params": {"kind": "concrete_sonotube", "diameter": 1, "height": 4, "quantity": 6}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 18.85,
  "meta": {
    "category": "construction",
    "formula_id": "construction:concrete_sonotube"
  }
}
drywall_sheetsSheets of drywall needed, rounded up.area, coverage_per_sheet

Sheets of drywall needed, rounded up.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "construction", "params": {"kind": "drywall_sheets", "area": 480, "coverage_per_sheet": 32}, "options": {}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'construction', 'params': {'kind': 'drywall_sheets', 'area': 480, 'coverage_per_sheet': 32}, 'options': {}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "construction", "params": {"kind": "drywall_sheets", "area": 480, "coverage_per_sheet": 32}, "options": {}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "construction", "params": {"kind": "drywall_sheets", "area": 480, "coverage_per_sheet": 32}, "options": {}})
});
const data = await resp.json();
200 Response
{
  "result": 15,
  "meta": {
    "category": "construction",
    "formula_id": "construction:drywall_sheets"
  }
}
paint_gallonsGallons of paint needed.area, coverage_per_gallon

Gallons of paint needed.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "construction", "params": {"kind": "paint_gallons", "area": 900, "coverage_per_gallon": 350}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'construction', 'params': {'kind': 'paint_gallons', 'area': 900, 'coverage_per_gallon': 350}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "construction", "params": {"kind": "paint_gallons", "area": 900, "coverage_per_gallon": 350}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "construction", "params": {"kind": "paint_gallons", "area": 900, "coverage_per_gallon": 350}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 2.57,
  "meta": {
    "category": "construction",
    "formula_id": "construction:paint_gallons"
  }
}
stairsStep count, run, and stringer length.total_rise, riser_height, tread_depth

Step count, run, and stringer length.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "construction", "params": {"kind": "stairs", "total_rise": 108, "riser_height": 7.5, "tread_depth": 10}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'construction', 'params': {'kind': 'stairs', 'total_rise': 108, 'riser_height': 7.5, 'tread_depth': 10}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "construction", "params": {"kind": "stairs", "total_rise": 108, "riser_height": 7.5, "tread_depth": 10}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "construction", "params": {"kind": "stairs", "total_rise": 108, "riser_height": 7.5, "tread_depth": 10}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": {
    "steps": 15,
    "total_run": 150,
    "stringer_length": 187.5
  },
  "meta": {
    "category": "construction",
    "formula_id": "construction:stairs"
  }
}
roofing_bundlesActual roof area adjusted for pitch, and bundles needed.roof_area_plan, pitch_rise, pitch_run, coverage_per_bundle

Actual roof area adjusted for pitch, and bundles needed.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "construction", "params": {"kind": "roofing_bundles", "roof_area_plan": 1800, "pitch_rise": 6, "pitch_run": 12, "coverage_per_bundle": 33.3}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'construction', 'params': {'kind': 'roofing_bundles', 'roof_area_plan': 1800, 'pitch_rise': 6, 'pitch_run': 12, 'coverage_per_bundle': 33.3}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "construction", "params": {"kind": "roofing_bundles", "roof_area_plan": 1800, "pitch_rise": 6, "pitch_run": 12, "coverage_per_bundle": 33.3}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "construction", "params": {"kind": "roofing_bundles", "roof_area_plan": 1800, "pitch_rise": 6, "pitch_run": 12, "coverage_per_bundle": 33.3}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": {
    "actual_area": 2012.46,
    "bundles": 61
  },
  "meta": {
    "category": "construction",
    "formula_id": "construction:roofing_bundles"
  }
}

Finance10

Interest, loan payments, amortization, CAGR, margins, breakeven, and savings goals.

simple_interestSimple interest.principal, rate, time

Simple interest.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "finance", "params": {"kind": "simple_interest", "principal": 5000, "rate": 0.04, "time": 3}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'finance', 'params': {'kind': 'simple_interest', 'principal': 5000, 'rate': 0.04, 'time': 3}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "finance", "params": {"kind": "simple_interest", "principal": 5000, "rate": 0.04, "time": 3}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "finance", "params": {"kind": "simple_interest", "principal": 5000, "rate": 0.04, "time": 3}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 600.0,
  "meta": {
    "category": "finance",
    "formula_id": "finance:simple_interest"
  }
}
compound_interestCompound interest with optional compounding frequency.principal, rate, time, n?

Compound interest with optional compounding frequency.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "finance", "params": {"kind": "compound_interest", "principal": 1000, "rate": 0.05, "time": 10}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'finance', 'params': {'kind': 'compound_interest', 'principal': 1000, 'rate': 0.05, 'time': 10}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "finance", "params": {"kind": "compound_interest", "principal": 1000, "rate": 0.05, "time": 10}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "finance", "params": {"kind": "compound_interest", "principal": 1000, "rate": 0.05, "time": 10}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 1628.89,
  "meta": {
    "category": "finance",
    "formula_id": "finance:compound_interest"
  }
}
monthly_paymentFixed monthly loan payment.principal, annual_rate, years

Fixed monthly loan payment.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "finance", "params": {"kind": "monthly_payment", "principal": 250000, "annual_rate": 0.06, "years": 30}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'finance', 'params': {'kind': 'monthly_payment', 'principal': 250000, 'annual_rate': 0.06, 'years': 30}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "finance", "params": {"kind": "monthly_payment", "principal": 250000, "annual_rate": 0.06, "years": 30}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "finance", "params": {"kind": "monthly_payment", "principal": 250000, "annual_rate": 0.06, "years": 30}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 1498.88,
  "meta": {
    "category": "finance",
    "formula_id": "finance:monthly_payment"
  }
}
amortization_scheduleFull or truncated amortization schedule.principal, annual_rate, years, limit_rows?, totals_only?

Full or truncated amortization schedule.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "finance", "params": {"kind": "amortization_schedule", "principal": 20000, "annual_rate": 0.07, "years": 3, "limit_rows": 3, "totals_only": false}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'finance', 'params': {'kind': 'amortization_schedule', 'principal': 20000, 'annual_rate': 0.07, 'years': 3, 'limit_rows': 3, 'totals_only': False}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "finance", "params": {"kind": "amortization_schedule", "principal": 20000, "annual_rate": 0.07, "years": 3, "limit_rows": 3, "totals_only": false}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "finance", "params": {"kind": "amortization_schedule", "principal": 20000, "annual_rate": 0.07, "years": 3, "limit_rows": 3, "totals_only": false}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": {
    "payment": 617.54,
    "schedule": [
      {
        "month": 1,
        "payment": 617.54,
        "interest": 116.67,
        "principal": 500.88,
        "balance": 19499.12
      },
      {
        "month": 2,
        "payment": 617.54,
        "interest": 113.74,
        "principal": 503.8,
        "balance": 18995.33
      },
      {
        "month": 3,
        "payment": 617.54,
        "interest": 110.81,
        "principal": 506.74,
        "balance": 18488.59
      }
    ]
  },
  "meta": {
    "category": "finance",
    "formula_id": "finance:amortization_schedule"
  }
}
extra_payment_impactMonths and interest saved by paying extra each month.principal, annual_rate, years, extra_per_month

Months and interest saved by paying extra each month.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "finance", "params": {"kind": "extra_payment_impact", "principal": 20000, "annual_rate": 0.07, "years": 5, "extra_per_month": 100}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'finance', 'params': {'kind': 'extra_payment_impact', 'principal': 20000, 'annual_rate': 0.07, 'years': 5, 'extra_per_month': 100}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "finance", "params": {"kind": "extra_payment_impact", "principal": 20000, "annual_rate": 0.07, "years": 5, "extra_per_month": 100}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "finance", "params": {"kind": "extra_payment_impact", "principal": 20000, "annual_rate": 0.07, "years": 5, "extra_per_month": 100}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": {
    "new_months": 47,
    "saved_months": 13,
    "saved_interest": 893.72
  },
  "meta": {
    "category": "finance",
    "formula_id": "finance:extra_payment_impact"
  }
}
cagrCompound annual growth rate.start_value, end_value, years

Compound annual growth rate.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "finance", "params": {"kind": "cagr", "start_value": 10000, "end_value": 18000, "years": 5}, "options": {"precision": 4}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'finance', 'params': {'kind': 'cagr', 'start_value': 10000, 'end_value': 18000, 'years': 5}, 'options': {'precision': 4}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "finance", "params": {"kind": "cagr", "start_value": 10000, "end_value": 18000, "years": 5}, "options": {"precision": 4}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "finance", "params": {"kind": "cagr", "start_value": 10000, "end_value": 18000, "years": 5}, "options": {"precision": 4}})
});
const data = await resp.json();
200 Response
{
  "result": 0.1247,
  "meta": {
    "category": "finance",
    "formula_id": "finance:cagr"
  }
}
marginsGross and net margin as fractions of revenue.revenue, cogs, operating_expenses

Gross and net margin as fractions of revenue.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "finance", "params": {"kind": "margins", "revenue": 500000, "cogs": 300000, "operating_expenses": 100000}, "options": {"precision": 4}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'finance', 'params': {'kind': 'margins', 'revenue': 500000, 'cogs': 300000, 'operating_expenses': 100000}, 'options': {'precision': 4}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "finance", "params": {"kind": "margins", "revenue": 500000, "cogs": 300000, "operating_expenses": 100000}, "options": {"precision": 4}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "finance", "params": {"kind": "margins", "revenue": 500000, "cogs": 300000, "operating_expenses": 100000}, "options": {"precision": 4}})
});
const data = await resp.json();
200 Response
{
  "result": {
    "gross_margin": 0.4,
    "net_margin": 0.2
  },
  "meta": {
    "category": "finance",
    "formula_id": "finance:margins"
  }
}
breakevenBreakeven unit count and revenue.fixed_costs, price_per_unit, variable_cost_per_unit

Breakeven unit count and revenue.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "finance", "params": {"kind": "breakeven", "fixed_costs": 20000, "price_per_unit": 50, "variable_cost_per_unit": 30}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'finance', 'params': {'kind': 'breakeven', 'fixed_costs': 20000, 'price_per_unit': 50, 'variable_cost_per_unit': 30}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "finance", "params": {"kind": "breakeven", "fixed_costs": 20000, "price_per_unit": 50, "variable_cost_per_unit": 30}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "finance", "params": {"kind": "breakeven", "fixed_costs": 20000, "price_per_unit": 50, "variable_cost_per_unit": 30}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": {
    "units": 1000,
    "revenue": 50000
  },
  "meta": {
    "category": "finance",
    "formula_id": "finance:breakeven"
  }
}
savings_goal (MONTHS)Required monthly contribution, or time to reach a savings target.target, months (solve monthly) or monthly (solve months), monthly_rate?

Required monthly contribution, or time to reach a savings target.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "finance", "params": {"kind": "savings_goal", "target": 20000, "months": 24, "monthly_rate": 0.004}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'finance', 'params': {'kind': 'savings_goal', 'target': 20000, 'months': 24, 'monthly_rate': 0.004}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "finance", "params": {"kind": "savings_goal", "target": 20000, "months": 24, "monthly_rate": 0.004}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "finance", "params": {"kind": "savings_goal", "target": 20000, "months": 24, "monthly_rate": 0.004}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 795.64,
  "meta": {
    "category": "finance",
    "formula_id": "finance:savings_required_monthly"
  }
}
savings_goal (MONTHLY)Required monthly contribution, or time to reach a savings target.target, months (solve monthly) or monthly (solve months), monthly_rate?

Required monthly contribution, or time to reach a savings target.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "finance", "params": {"kind": "savings_goal", "target": 20000, "monthly": 750, "monthly_rate": 0.004}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'finance', 'params': {'kind': 'savings_goal', 'target': 20000, 'monthly': 750, 'monthly_rate': 0.004}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "finance", "params": {"kind": "savings_goal", "target": 20000, "monthly": 750, "monthly_rate": 0.004}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "finance", "params": {"kind": "savings_goal", "target": 20000, "monthly": 750, "monthly_rate": 0.004}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 25.39,
  "meta": {
    "category": "finance",
    "formula_id": "finance:savings_time_to_goal"
  }
}

Geometry9

Right triangles, slope, roof pitch, circles, and oblique-triangle solving (law of sines / law of cosines).

pythagoreanHypotenuse from two legs.a, b

Hypotenuse from two legs.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "geometry", "params": {"kind": "pythagorean", "a": 3, "b": 4}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'geometry', 'params': {'kind': 'pythagorean', 'a': 3, 'b': 4}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "geometry", "params": {"kind": "pythagorean", "a": 3, "b": 4}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "geometry", "params": {"kind": "pythagorean", "a": 3, "b": 4}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 5.0,
  "meta": {
    "category": "geometry",
    "formula_id": "geometry:pythagorean"
  }
}
slopeLine slope through two points.x1, y1, x2, y2

Line slope through two points.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "geometry", "params": {"kind": "slope", "x1": 1, "y1": 2, "x2": 5, "y2": 14}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'geometry', 'params': {'kind': 'slope', 'x1': 1, 'y1': 2, 'x2': 5, 'y2': 14}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "geometry", "params": {"kind": "slope", "x1": 1, "y1": 2, "x2": 5, "y2": 14}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "geometry", "params": {"kind": "slope", "x1": 1, "y1": 2, "x2": 5, "y2": 14}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 3.0,
  "meta": {
    "category": "geometry",
    "formula_id": "geometry:slope"
  }
}
roof_pitch_angleRoof pitch as an angle in degrees.rise, run

Roof pitch as an angle in degrees.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "geometry", "params": {"kind": "roof_pitch_angle", "rise": 6, "run": 12}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'geometry', 'params': {'kind': 'roof_pitch_angle', 'rise': 6, 'run': 12}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "geometry", "params": {"kind": "roof_pitch_angle", "rise": 6, "run": 12}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "geometry", "params": {"kind": "roof_pitch_angle", "rise": 6, "run": 12}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 26.57,
  "meta": {
    "category": "geometry",
    "formula_id": "geometry:roof_pitch_angle"
  }
}
circumferenceCircle circumference.radius

Circle circumference.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "geometry", "params": {"kind": "circumference", "radius": 7}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'geometry', 'params': {'kind': 'circumference', 'radius': 7}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "geometry", "params": {"kind": "circumference", "radius": 7}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "geometry", "params": {"kind": "circumference", "radius": 7}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 43.98,
  "meta": {
    "category": "geometry",
    "formula_id": "geometry:circumference"
  }
}
arc_lengthArc length for a given central angle.radius, angle_deg

Arc length for a given central angle.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "geometry", "params": {"kind": "arc_length", "radius": 7, "angle_deg": 90}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'geometry', 'params': {'kind': 'arc_length', 'radius': 7, 'angle_deg': 90}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "geometry", "params": {"kind": "arc_length", "radius": 7, "angle_deg": 90}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "geometry", "params": {"kind": "arc_length", "radius": 7, "angle_deg": 90}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 11.0,
  "meta": {
    "category": "geometry",
    "formula_id": "geometry:arc_length"
  }
}
law_of_sines_SSA (SSA)Solve a triangle given side-side-angle (may return an ambiguous-case pair).a, A, b or c

Solve a triangle given side-side-angle (may return an ambiguous-case pair).

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "geometry", "params": {"kind": "law_of_sines_SSA", "a": 10, "A": 30, "b": 14}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'geometry', 'params': {'kind': 'law_of_sines_SSA', 'a': 10, 'A': 30, 'b': 14}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "geometry", "params": {"kind": "law_of_sines_SSA", "a": 10, "A": 30, "b": 14}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "geometry", "params": {"kind": "law_of_sines_SSA", "a": 10, "A": 30, "b": 14}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": [
    {
      "A": 30,
      "B": 44.43,
      "C": 105.57,
      "a": 10,
      "b": 14,
      "c": 19.27
    },
    {
      "A": 30,
      "B": 135.57,
      "C": 14.43,
      "a": 10,
      "b": 14,
      "c": 4.98
    }
  ],
  "meta": {
    "category": "geometry",
    "formula_id": "geometry:law_of_sines"
  }
}
law_of_sines_ASA (ASA)Solve a triangle given angle-side-angle.A, B, one side

Solve a triangle given angle-side-angle.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "geometry", "params": {"kind": "law_of_sines_ASA", "A": 40, "B": 60, "a": 12}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'geometry', 'params': {'kind': 'law_of_sines_ASA', 'A': 40, 'B': 60, 'a': 12}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "geometry", "params": {"kind": "law_of_sines_ASA", "A": 40, "B": 60, "a": 12}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "geometry", "params": {"kind": "law_of_sines_ASA", "A": 40, "B": 60, "a": 12}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": {
    "A": 40,
    "B": 60,
    "C": 80.0,
    "a": 12,
    "b": 16.17,
    "c": 18.39
  },
  "meta": {
    "category": "geometry",
    "formula_id": "geometry:law_of_sines"
  }
}
law_of_cosines (SSS)Solve a triangle's angles from three sides, or its third side from two sides and the included angle.a, b, c (SSS) or a, b, C (SAS)

Solve a triangle's angles from three sides, or its third side from two sides and the included angle.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "geometry", "params": {"kind": "law_of_cosines", "a": 7, "b": 8, "c": 9}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'geometry', 'params': {'kind': 'law_of_cosines', 'a': 7, 'b': 8, 'c': 9}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "geometry", "params": {"kind": "law_of_cosines", "a": 7, "b": 8, "c": 9}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "geometry", "params": {"kind": "law_of_cosines", "a": 7, "b": 8, "c": 9}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": {
    "A": 48.19,
    "B": 58.41,
    "C": 73.4
  },
  "meta": {
    "category": "geometry",
    "formula_id": "geometry:law_of_cosines"
  }
}
law_of_cosines (SAS)Solve a triangle's angles from three sides, or its third side from two sides and the included angle.a, b, c (SSS) or a, b, C (SAS)

Solve a triangle's angles from three sides, or its third side from two sides and the included angle.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "geometry", "params": {"kind": "law_of_cosines", "a": 7, "b": 8, "C": 60}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'geometry', 'params': {'kind': 'law_of_cosines', 'a': 7, 'b': 8, 'C': 60}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "geometry", "params": {"kind": "law_of_cosines", "a": 7, "b": 8, "C": 60}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "geometry", "params": {"kind": "law_of_cosines", "a": 7, "b": 8, "C": 60}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": {
    "c": 7.55
  },
  "meta": {
    "category": "geometry",
    "formula_id": "geometry:law_of_cosines"
  }
}

Health & fitness6

BMI, BMR, TDEE, calorie goals, Karvonen heart-rate zones, and body surface area.

bmiBody mass index.weight_kg, height_cm

Body mass index.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "health", "params": {"kind": "bmi", "weight_kg": 75, "height_cm": 180}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'health', 'params': {'kind': 'bmi', 'weight_kg': 75, 'height_cm': 180}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "health", "params": {"kind": "bmi", "weight_kg": 75, "height_cm": 180}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "health", "params": {"kind": "bmi", "weight_kg": 75, "height_cm": 180}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 23.15,
  "meta": {
    "category": "health",
    "formula_id": "health:bmi"
  }
}
bmrBasal metabolic rate (Mifflin-St Jeor).gender, weight_kg, height_cm, age

Basal metabolic rate (Mifflin-St Jeor).

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "health", "params": {"kind": "bmr", "gender": "male", "weight_kg": 80, "height_cm": 178, "age": 32}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'health', 'params': {'kind': 'bmr', 'gender': 'male', 'weight_kg': 80, 'height_cm': 178, 'age': 32}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "health", "params": {"kind": "bmr", "gender": "male", "weight_kg": 80, "height_cm": 178, "age": 32}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "health", "params": {"kind": "bmr", "gender": "male", "weight_kg": 80, "height_cm": 178, "age": 32}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 1757.5,
  "meta": {
    "category": "health",
    "formula_id": "health:bmr"
  }
}
tdeeTotal daily energy expenditure from a BMR and an activity factor.bmr, activity_factor

Total daily energy expenditure from a BMR and an activity factor.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "health", "params": {"kind": "tdee", "bmr": 1780, "activity_factor": 1.55}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'health', 'params': {'kind': 'tdee', 'bmr': 1780, 'activity_factor': 1.55}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "health", "params": {"kind": "tdee", "bmr": 1780, "activity_factor": 1.55}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "health", "params": {"kind": "tdee", "bmr": 1780, "activity_factor": 1.55}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 2759.0,
  "meta": {
    "category": "health",
    "formula_id": "health:tdee"
  }
}
calorie_goalDaily calorie target for cut, bulk, or maintain.tdee, goal

Daily calorie target for cut, bulk, or maintain.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "health", "params": {"kind": "calorie_goal", "tdee": 2759, "goal": "cut"}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'health', 'params': {'kind': 'calorie_goal', 'tdee': 2759, 'goal': 'cut'}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "health", "params": {"kind": "calorie_goal", "tdee": 2759, "goal": "cut"}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "health", "params": {"kind": "calorie_goal", "tdee": 2759, "goal": "cut"}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 2259,
  "meta": {
    "category": "health",
    "formula_id": "health:calorie_goal"
  }
}
heart_rate_zonesKarvonen heart-rate training zones.age, resting_hr

Karvonen heart-rate training zones.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "health", "params": {"kind": "heart_rate_zones", "age": 35, "resting_hr": 60}, "options": {}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'health', 'params': {'kind': 'heart_rate_zones', 'age': 35, 'resting_hr': 60}, 'options': {}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "health", "params": {"kind": "heart_rate_zones", "age": 35, "resting_hr": 60}, "options": {}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "health", "params": {"kind": "heart_rate_zones", "age": 35, "resting_hr": 60}, "options": {}})
});
const data = await resp.json();
200 Response
{
  "result": {
    "zone2": 135,
    "zone3": 148,
    "zone4": 160,
    "zone5": 172,
    "max_hr": 185
  },
  "meta": {
    "category": "health",
    "formula_id": "health:heart_rate_zones"
  }
}
bsa_mostellerBody surface area (Mosteller formula).height_cm, weight_kg

Body surface area (Mosteller formula).

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "health", "params": {"kind": "bsa_mosteller", "height_cm": 180, "weight_kg": 80}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'health', 'params': {'kind': 'bsa_mosteller', 'height_cm': 180, 'weight_kg': 80}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "health", "params": {"kind": "bsa_mosteller", "height_cm": 180, "weight_kg": 80}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "health", "params": {"kind": "bsa_mosteller", "height_cm": 180, "weight_kg": 80}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 2.0,
  "meta": {
    "category": "health",
    "formula_id": "health:bsa_mosteller"
  }
}

Percentage6

Percent change, discounts, tips, portions, and unit-aware bill splitting.

increasePercent increase.original, new

Percent increase.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "percentage", "params": {"kind": "increase", "original": 80, "new": 100}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'percentage', 'params': {'kind': 'increase', 'original': 80, 'new': 100}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "percentage", "params": {"kind": "increase", "original": 80, "new": 100}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "percentage", "params": {"kind": "increase", "original": 80, "new": 100}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 25.0,
  "meta": {
    "category": "percent",
    "formula_id": "percent:increase"
  }
}
decreasePercent decrease.original, new

Percent decrease.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "percentage", "params": {"kind": "decrease", "original": 100, "new": 80}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'percentage', 'params': {'kind': 'decrease', 'original': 100, 'new': 80}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "percentage", "params": {"kind": "decrease", "original": 100, "new": 80}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "percentage", "params": {"kind": "decrease", "original": 100, "new": 80}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 20.0,
  "meta": {
    "category": "percent",
    "formula_id": "percent:decrease"
  }
}
discountDiscounted price.price, percent

Discounted price.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "percentage", "params": {"kind": "discount", "price": 89.99, "percent": 20}, "options": {}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'percentage', 'params': {'kind': 'discount', 'price': 89.99, 'percent': 20}, 'options': {}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "percentage", "params": {"kind": "discount", "price": 89.99, "percent": 20}, "options": {}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "percentage", "params": {"kind": "discount", "price": 89.99, "percent": 20}, "options": {}})
});
const data = await resp.json();
200 Response
{
  "result": 71.99,
  "meta": {
    "category": "percent",
    "formula_id": "percent:discount"
  }
}
tipTip amount.amount, percent

Tip amount.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "percentage", "params": {"kind": "tip", "amount": 64.5, "percent": 18}, "options": {}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'percentage', 'params': {'kind': 'tip', 'amount': 64.5, 'percent': 18}, 'options': {}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "percentage", "params": {"kind": "tip", "amount": 64.5, "percent": 18}, "options": {}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "percentage", "params": {"kind": "tip", "amount": 64.5, "percent": 18}, "options": {}})
});
const data = await resp.json();
200 Response
{
  "result": 11.61,
  "meta": {
    "category": "percent",
    "formula_id": "percent:tip"
  }
}
portionValue of a percent of a total.total, percent

Value of a percent of a total.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "percentage", "params": {"kind": "portion", "total": 250, "percent": 15}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'percentage', 'params': {'kind': 'portion', 'total': 250, 'percent': 15}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "percentage", "params": {"kind": "portion", "total": 250, "percent": 15}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "percentage", "params": {"kind": "portion", "total": 250, "percent": 15}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 37.5,
  "meta": {
    "category": "percent",
    "formula_id": "percent:portion"
  }
}
split_billTotal and even per-person share, tip and tax included.subtotal, tip_percent, tax_percent, people

Total and even per-person share, tip and tax included.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "percentage", "params": {"kind": "split_bill", "subtotal": 120, "tip_percent": 20, "tax_percent": 8.5, "people": 4}, "options": {}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'percentage', 'params': {'kind': 'split_bill', 'subtotal': 120, 'tip_percent': 20, 'tax_percent': 8.5, 'people': 4}, 'options': {}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "percentage", "params": {"kind": "split_bill", "subtotal": 120, "tip_percent": 20, "tax_percent": 8.5, "people": 4}, "options": {}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "percentage", "params": {"kind": "split_bill", "subtotal": 120, "tip_percent": 20, "tax_percent": 8.5, "people": 4}, "options": {}})
});
const data = await resp.json();
200 Response
{
  "result": {
    "total": 154.2,
    "per_person": 38.55
  },
  "meta": {
    "category": "percent",
    "formula_id": "percent:split_bill"
  }
}

Probability6

Combinatorics and the normal / binomial distributions.

nCrCombinations (n choose r).n, r

Combinations (n choose r).

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "probability", "params": {"kind": "nCr", "n": 10, "r": 3}, "options": {}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'probability', 'params': {'kind': 'nCr', 'n': 10, 'r': 3}, 'options': {}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "probability", "params": {"kind": "nCr", "n": 10, "r": 3}, "options": {}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "probability", "params": {"kind": "nCr", "n": 10, "r": 3}, "options": {}})
});
const data = await resp.json();
200 Response
{
  "result": 120,
  "meta": {
    "category": "probability",
    "formula_id": "probability:nCr"
  }
}
nPrPermutations.n, r

Permutations.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "probability", "params": {"kind": "nPr", "n": 10, "r": 3}, "options": {}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'probability', 'params': {'kind': 'nPr', 'n': 10, 'r': 3}, 'options': {}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "probability", "params": {"kind": "nPr", "n": 10, "r": 3}, "options": {}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "probability", "params": {"kind": "nPr", "n": 10, "r": 3}, "options": {}})
});
const data = await resp.json();
200 Response
{
  "result": 720,
  "meta": {
    "category": "probability",
    "formula_id": "probability:nPr"
  }
}
binomial_pmfBinomial probability mass function, P(X=k).n, k, p

Binomial probability mass function, P(X=k).

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "probability", "params": {"kind": "binomial_pmf", "n": 10, "k": 4, "p": 0.3}, "options": {"precision": 4}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'probability', 'params': {'kind': 'binomial_pmf', 'n': 10, 'k': 4, 'p': 0.3}, 'options': {'precision': 4}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "probability", "params": {"kind": "binomial_pmf", "n": 10, "k": 4, "p": 0.3}, "options": {"precision": 4}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "probability", "params": {"kind": "binomial_pmf", "n": 10, "k": 4, "p": 0.3}, "options": {"precision": 4}})
});
const data = await resp.json();
200 Response
{
  "result": 0.2001,
  "meta": {
    "category": "probability",
    "formula_id": "probability:binomial_pmf"
  }
}
binomial_cdfBinomial cumulative distribution, P(X<=k).n, k, p

Binomial cumulative distribution, P(X<=k).

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "probability", "params": {"kind": "binomial_cdf", "n": 10, "k": 4, "p": 0.3}, "options": {"precision": 4}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'probability', 'params': {'kind': 'binomial_cdf', 'n': 10, 'k': 4, 'p': 0.3}, 'options': {'precision': 4}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "probability", "params": {"kind": "binomial_cdf", "n": 10, "k": 4, "p": 0.3}, "options": {"precision": 4}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "probability", "params": {"kind": "binomial_cdf", "n": 10, "k": 4, "p": 0.3}, "options": {"precision": 4}})
});
const data = await resp.json();
200 Response
{
  "result": 0.8497,
  "meta": {
    "category": "probability",
    "formula_id": "probability:binomial_cdf"
  }
}
normal_cdfNormal cumulative distribution.x, mean, stddev

Normal cumulative distribution.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "probability", "params": {"kind": "normal_cdf", "x": 68, "mean": 70, "stddev": 3}, "options": {"precision": 4}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'probability', 'params': {'kind': 'normal_cdf', 'x': 68, 'mean': 70, 'stddev': 3}, 'options': {'precision': 4}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "probability", "params": {"kind": "normal_cdf", "x": 68, "mean": 70, "stddev": 3}, "options": {"precision": 4}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "probability", "params": {"kind": "normal_cdf", "x": 68, "mean": 70, "stddev": 3}, "options": {"precision": 4}})
});
const data = await resp.json();
200 Response
{
  "result": 0.2525,
  "meta": {
    "category": "probability",
    "formula_id": "probability:normal_cdf"
  }
}
normal_quantileInverse normal CDF (value at a percentile).p, mean, stddev

Inverse normal CDF (value at a percentile).

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "probability", "params": {"kind": "normal_quantile", "p": 0.95, "mean": 100, "stddev": 15}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'probability', 'params': {'kind': 'normal_quantile', 'p': 0.95, 'mean': 100, 'stddev': 15}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "probability", "params": {"kind": "normal_quantile", "p": 0.95, "mean": 100, "stddev": 15}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "probability", "params": {"kind": "normal_quantile", "p": 0.95, "mean": 100, "stddev": 15}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 124.67,
  "meta": {
    "category": "probability",
    "formula_id": "probability:normal_quantile"
  }
}

Statistics8

Descriptive statistics and simple linear regression over a data array.

meanArithmetic mean.data[]

Arithmetic mean.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "stats", "params": {"kind": "mean", "data": [12, 15, 9, 20, 14]}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'stats', 'params': {'kind': 'mean', 'data': [12, 15, 9, 20, 14]}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "stats", "params": {"kind": "mean", "data": [12, 15, 9, 20, 14]}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "stats", "params": {"kind": "mean", "data": [12, 15, 9, 20, 14]}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 14.0,
  "meta": {
    "category": "stats",
    "formula_id": "stats:mean"
  }
}
medianMedian.data[]

Median.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "stats", "params": {"kind": "median", "data": [12, 15, 9, 20, 14]}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'stats', 'params': {'kind': 'median', 'data': [12, 15, 9, 20, 14]}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "stats", "params": {"kind": "median", "data": [12, 15, 9, 20, 14]}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "stats", "params": {"kind": "median", "data": [12, 15, 9, 20, 14]}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 14,
  "meta": {
    "category": "stats",
    "formula_id": "stats:median"
  }
}
modeMode(s) and their frequency.data[]

Mode(s) and their frequency.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "stats", "params": {"kind": "mode", "data": [4, 4, 6, 7, 7, 7, 9]}, "options": {}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'stats', 'params': {'kind': 'mode', 'data': [4, 4, 6, 7, 7, 7, 9]}, 'options': {}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "stats", "params": {"kind": "mode", "data": [4, 4, 6, 7, 7, 7, 9]}, "options": {}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "stats", "params": {"kind": "mode", "data": [4, 4, 6, 7, 7, 7, 9]}, "options": {}})
});
const data = await resp.json();
200 Response
{
  "result": {
    "modes": [
      7
    ],
    "frequency": 3
  },
  "meta": {
    "category": "stats",
    "formula_id": "stats:mode"
  }
}
rangeRange (max minus min).data[]

Range (max minus min).

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "stats", "params": {"kind": "range", "data": [12, 15, 9, 20, 14]}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'stats', 'params': {'kind': 'range', 'data': [12, 15, 9, 20, 14]}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "stats", "params": {"kind": "range", "data": [12, 15, 9, 20, 14]}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "stats", "params": {"kind": "range", "data": [12, 15, 9, 20, 14]}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 11,
  "meta": {
    "category": "stats",
    "formula_id": "stats:range"
  }
}
variance_stdVariance and standard deviation, sample or population.data[], sample?

Variance and standard deviation, sample or population.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "stats", "params": {"kind": "variance_std", "data": [12, 15, 9, 20, 14], "sample": true}, "options": {"precision": 4}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'stats', 'params': {'kind': 'variance_std', 'data': [12, 15, 9, 20, 14], 'sample': True}, 'options': {'precision': 4}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "stats", "params": {"kind": "variance_std", "data": [12, 15, 9, 20, 14], "sample": true}, "options": {"precision": 4}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "stats", "params": {"kind": "variance_std", "data": [12, 15, 9, 20, 14], "sample": true}, "options": {"precision": 4}})
});
const data = await resp.json();
200 Response
{
  "result": {
    "variance": 16.5,
    "stddev": 4.062
  },
  "meta": {
    "category": "stats",
    "formula_id": "stats:variance_std"
  }
}
z_scoreZ-score of a value.x, mean, stddev

Z-score of a value.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "stats", "params": {"kind": "z_score", "x": 82, "mean": 75, "stddev": 6}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'stats', 'params': {'kind': 'z_score', 'x': 82, 'mean': 75, 'stddev': 6}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "stats", "params": {"kind": "z_score", "x": 82, "mean": 75, "stddev": 6}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "stats", "params": {"kind": "z_score", "x": 82, "mean": 75, "stddev": 6}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 1.17,
  "meta": {
    "category": "stats",
    "formula_id": "stats:z_score"
  }
}
percentile_from_zPercentile (CDF) for a z-score.z

Percentile (CDF) for a z-score.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "stats", "params": {"kind": "percentile_from_z", "z": 1.5}, "options": {"precision": 4}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'stats', 'params': {'kind': 'percentile_from_z', 'z': 1.5}, 'options': {'precision': 4}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "stats", "params": {"kind": "percentile_from_z", "z": 1.5}, "options": {"precision": 4}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "stats", "params": {"kind": "percentile_from_z", "z": 1.5}, "options": {"precision": 4}})
});
const data = await resp.json();
200 Response
{
  "result": 0.9332,
  "meta": {
    "category": "stats",
    "formula_id": "stats:percentile_from_z"
  }
}
linear_regressionLeast-squares slope, intercept, and correlation.x[], y[]

Least-squares slope, intercept, and correlation.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "stats", "params": {"kind": "linear_regression", "x": [1, 2, 3, 4, 5], "y": [2.1, 4.0, 5.9, 8.1, 9.9]}, "options": {"precision": 4}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'stats', 'params': {'kind': 'linear_regression', 'x': [1, 2, 3, 4, 5], 'y': [2.1, 4.0, 5.9, 8.1, 9.9]}, 'options': {'precision': 4}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "stats", "params": {"kind": "linear_regression", "x": [1, 2, 3, 4, 5], "y": [2.1, 4.0, 5.9, 8.1, 9.9]}, "options": {"precision": 4}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "stats", "params": {"kind": "linear_regression", "x": [1, 2, 3, 4, 5], "y": [2.1, 4.0, 5.9, 8.1, 9.9]}, "options": {"precision": 4}})
});
const data = await resp.json();
200 Response
{
  "result": {
    "slope": 1.97,
    "intercept": 0.09,
    "r": 0.9996
  },
  "meta": {
    "category": "stats",
    "formula_id": "stats:linear_regression"
  }
}

Volume6

Solid-shape volume formulas: cube, rectangular prism, cylinder, sphere, cone, frustum cone.

cubeCube volume.side

Cube volume.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "volume", "params": {"shape": "cube", "side": 5}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'volume', 'params': {'shape': 'cube', 'side': 5}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "volume", "params": {"shape": "cube", "side": 5}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "volume", "params": {"shape": "cube", "side": 5}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 125,
  "meta": {
    "category": "volume",
    "formula_id": "volume:cube"
  }
}
rectangular_prismRectangular prism (box) volume.length, width, height

Rectangular prism (box) volume.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "volume", "params": {"shape": "rectangular_prism", "length": 10, "width": 4, "height": 3}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'volume', 'params': {'shape': 'rectangular_prism', 'length': 10, 'width': 4, 'height': 3}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "volume", "params": {"shape": "rectangular_prism", "length": 10, "width": 4, "height": 3}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "volume", "params": {"shape": "rectangular_prism", "length": 10, "width": 4, "height": 3}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 120,
  "meta": {
    "category": "volume",
    "formula_id": "volume:rectangular_prism"
  }
}
cylinderCylinder volume.radius, height

Cylinder volume.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "volume", "params": {"shape": "cylinder", "radius": 3, "height": 10}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'volume', 'params': {'shape': 'cylinder', 'radius': 3, 'height': 10}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "volume", "params": {"shape": "cylinder", "radius": 3, "height": 10}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "volume", "params": {"shape": "cylinder", "radius": 3, "height": 10}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 282.74,
  "meta": {
    "category": "volume",
    "formula_id": "volume:cylinder"
  }
}
sphereSphere volume.radius

Sphere volume.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "volume", "params": {"shape": "sphere", "radius": 6}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'volume', 'params': {'shape': 'sphere', 'radius': 6}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "volume", "params": {"shape": "sphere", "radius": 6}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "volume", "params": {"shape": "sphere", "radius": 6}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 904.78,
  "meta": {
    "category": "volume",
    "formula_id": "volume:sphere"
  }
}
coneCone volume.radius, height

Cone volume.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "volume", "params": {"shape": "cone", "radius": 4, "height": 9}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'volume', 'params': {'shape': 'cone', 'radius': 4, 'height': 9}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "volume", "params": {"shape": "cone", "radius": 4, "height": 9}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "volume", "params": {"shape": "cone", "radius": 4, "height": 9}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 150.8,
  "meta": {
    "category": "volume",
    "formula_id": "volume:cone"
  }
}
frustum_coneFrustum of a cone (truncated cone) volume.r1, r2, height

Frustum of a cone (truncated cone) volume.

curl https://api.bottlebluellc.com/v1/math \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "volume", "params": {"shape": "frustum_cone", "r1": 5, "r2": 3, "height": 8}, "options": {"precision": 2}}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/math",
    headers={"x-api-key": API_KEY},
    json={'type': 'volume', 'params': {'shape': 'frustum_cone', 'r1': 5, 'r2': 3, 'height': 8}, 'options': {'precision': 2}},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "volume", "params": {"shape": "frustum_cone", "r1": 5, "r2": 3, "height": 8}, "options": {"precision": 2}})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/math", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"type": "volume", "params": {"shape": "frustum_cone", "r1": 5, "r2": 3, "height": 8}, "options": {"precision": 2}})
});
const data = await resp.json();
200 Response
{
  "result": 410.5,
  "meta": {
    "category": "volume",
    "formula_id": "volume:frustum_cone"
  }
}
GETapi.bottlebluellc.com/ntp

Time sync. No parameters. Returns utc_iso, epoch_s (float), epoch_ms (int), epoch_ns (int), lambda_request_id. Sends Cache-Control: no-store.

curl https://api.bottlebluellc.com/v1/ntp \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/ntp",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/ntp", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/ntp", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "utc_iso": "2026-07-07T12:34:56.789Z",
  "epoch_s": 1752237296.789,
  "epoch_ms": 1752237296789,
  "epoch_ns": 1752237296789000000,
  "lambda_request_id": "abc123-def456"
}
GETapi.bottlebluellc.com/rng

Latest hourly random draw (256-bit), served in every stored format. A fresh draw is published each hour; responses carry a private, browser-only Cache-Control tuned to the next draw.

ParameterInDescription
formatqueryoptional Return a single representation instead of all of them. One of uuid4, hex, base64url, integer, bits.
curl https://api.bottlebluellc.com/v1/rng \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/rng",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/rng", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/rng", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "uuid4": "79ef559c-774f-4f46-80f5-f7921036ed97",
  "hex": "9e1f5e43ebec47deb6f4450c934c4e10d9522a36525dc26adafe54d714655229",
  "base64url": "nh9eQ-vsR9629EUMk0xOENlSKjZSXcJq2v5U1xRlUik",
  "integer": "71520852930482833090887825723307774133283085537548162634608622221865433518633",
  "bits": 256,
  "timestamp": "2026-07-17T14:45:58.957846+00:00",
  "type": "RngDraw",
  "next_update_at": "2026-07-17T15:45:58.957846+00:00"
}
GETapi.bottlebluellc.com/poetry

Recent auto-generated poems (a daily creative feed).

curl https://api.bottlebluellc.com/v1/poetry \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/poetry",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/poetry", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/poetry", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "haikus": [
    {
      "timestamp": "2026-07-08T09:12:41.382910+00:00",
      "haiku": "heat bends the sidewalk\na sprinkler ticks through the hush\nswallows stitch the dusk",
      "headlines": [
        "Heat advisory stretches into a fourth day across the Upper Midwest",
        "City opens cooling centers as temperatures climb",
        "Utilities urge conservation during evening peak"
      ]
    },
    {
      "timestamp": "2026-07-07T09:11:58.104227+00:00",
      "haiku": "rain maps the window\nmarkets wobble, then settle\na moth finds the light",
      "headlines": [
        "Stocks drift lower ahead of earnings season",
        "Thunderstorms sweep the plains overnight",
        "Transit ridership hits a post-pandemic high"
      ]
    },
    {
      "timestamp": "2026-07-06T09:13:22.771034+00:00",
      "haiku": "flags fold in the wind\nleftover fireworks crackle\nthe lake keeps its calm",
      "headlines": [
        "Holiday travel wraps up with few delays",
        "Fireworks cleanup begins along the riverfront",
        "Farmers welcome a stretch of dry weather"
      ]
    }
  ],
  "trends": {
    "heat": 5,
    "river": 4,
    "storm": 3,
    "market": 3,
    "light": 2,
    "summer": 2
  },
  "vibes": "Everyone is outside pretending the humidity is fine.",
  "world_haiku": [
    {
      "timestamp": "2026-07-08T09:14:05.220481+00:00",
      "haiku": "monsoon on the coast\nnegotiators break for tea\nsomewhere, a bell rings",
      "date": "July 08, 2026",
      "headlines": [
        "Monsoon rains arrive early across South Asia",
        "Trade talks resume in Geneva",
        "Global heat record tied for June"
      ]
    },
    {
      "timestamp": "2026-07-07T09:13:47.918356+00:00",
      "haiku": "old ice calves at dawn\nheadlines argue over waves\ngulls vote with their wings",
      "date": "July 07, 2026",
      "headlines": [
        "Glacier study revises sea-level forecasts",
        "Shipping lanes reroute around storm system",
        "Summit ends without a joint statement"
      ]
    }
  ],
  "world_vibe": "The world is holding meetings about the weather it caused.",
  "world_trends": {
    "monsoon": 4,
    "summit": 3,
    "coast": 3,
    "ice": 2,
    "trade": 2
  }
}
GETapi.bottlebluellc.com/seconds/midnight

Milliseconds remaining until local midnight.

curl https://api.bottlebluellc.com/v1/seconds/midnight?timezone=UTC \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/seconds/midnight?timezone=UTC",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/seconds/midnight?timezone=UTC", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/seconds/midnight?timezone=UTC", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "timezone": "UTC",
  "remainingMilliseconds": 16070000
}
GETapi.bottlebluellc.com/seconds/season

The current astronomical season.

curl https://api.bottlebluellc.com/v1/seconds/season \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/seconds/season",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/seconds/season", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/seconds/season", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "date": "2026-07-08",
  "season": "Summer"
}
GETapi.bottlebluellc.com/seconds/sunset

Milliseconds remaining until sunset.

curl https://api.bottlebluellc.com/v1/seconds/sunset \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/seconds/sunset",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/seconds/sunset", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/seconds/sunset", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "millisecondsUntilSunset": 23390000,
  "current_time": "2026-07-08 14:32:10",
  "sunset_time": "2026-07-08 21:02:00"
}
POSTapi.bottlebluellc.com/units

Convert a value between units of measure. Send the parameters as a JSON body (the endpoint reads the request body, and CloudFront rejects a GET that carries one).

ParameterInDescription
valuebodyrequired Numeric value to convert.
frombodyrequired Source unit code, e.g. km.
tobodyrequired Target unit code, e.g. mi.
curl https://api.bottlebluellc.com/v1/units \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"value": 100, "from": "km", "to": "mi"}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/units",
    headers={"x-api-key": API_KEY, "Content-Type": "application/json"},
    json={'value': 100, 'from': 'km', 'to': 'mi'},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/units", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"value": 100, "from": "km", "to": "mi"})
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/units", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"value": 100, "from": "km", "to": "mi"})
});
const data = await resp.json();
200 Response
{
  "input": {
    "value": 100,
    "from": "km",
    "to": "mi"
  },
  "result": 62.137273664980675,
  "formula": "100 * 1000.0 / 1609.34"
}

Notes

Base URL https://api.bottlebluellc.com/v1

GETapi.bottlebluellc.com/notes

List the caller's notes.

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/notes \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be "*" (read across every platform) or a Bottle Blue product slug:
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl "https://api.bottlebluellc.com/v1/notes?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.bottlebluellc.com/v1/notes",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.bottlebluellc.com/v1/notes",
    headers={"x-api-key": API_KEY},
    # platform can also be "*" (read across every platform) or a Bottle Blue product slug:
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/notes", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform) or a Bottle Blue product slug:
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/notes?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/notes", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform) or a Bottle Blue product slug:
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/notes?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "items": [
    {
      "note_id": "9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21",
      "title": "Ideas",
      "content": "Plant tomatoes in May",
      "created_at": "2026-07-08T09:15:42.123456",
      "updated_at": "2026-07-08T09:15:42.123456"
    },
    {
      "note_id": "5d8e7a94-2c1b-4f6d-a3e9-8b4c6d2f1a70",
      "title": "Watering schedule",
      "content": "Basil every 2 days, rosemary weekly",
      "created_at": "2026-07-07T18:02:11.904312",
      "updated_at": "2026-07-08T07:41:05.238771"
    }
  ]
}
POSTapi.bottlebluellc.com/notes

Create a note.

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/notes \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Ideas", "content": "Plant tomatoes in May"}'

# Scoped to one platform
# platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl https://api.bottlebluellc.com/v1/notes \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Ideas", "content": "Plant tomatoes in May", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.post(
    "https://api.bottlebluellc.com/v1/notes",
    headers={"x-api-key": API_KEY},
    json={'title': 'Ideas', 'content': 'Plant tomatoes in May'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.post(
    "https://api.bottlebluellc.com/v1/notes",
    headers={"x-api-key": API_KEY},
    # platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    json={'title': 'Ideas', 'content': 'Plant tomatoes in May', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/notes", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"title": "Ideas", "content": "Plant tomatoes in May"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/notes", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"title": "Ideas", "content": "Plant tomatoes in May", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/notes", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"title": "Ideas", "content": "Plant tomatoes in May"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/notes", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"title": "Ideas", "content": "Plant tomatoes in May", "platform": "my_garden"})
});
200 Response
{
  "message": "Note created",
  "note_id": "9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21"
}
GETapi.bottlebluellc.com/notes/{note_id}

Get a single note. Supports ?expand=links to inline this record's links in the same call - see Link System.

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/notes/9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21 \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform must match where the item lives: a project name or a Bottle Blue product slug:
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl "https://api.bottlebluellc.com/v1/notes/9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.bottlebluellc.com/v1/notes/9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.bottlebluellc.com/v1/notes/9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21",
    headers={"x-api-key": API_KEY},
    # platform must match where the item lives: a project name or a Bottle Blue product slug:
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/notes/9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform must match where the item lives: a project name or a Bottle Blue product slug:
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/notes/9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/notes/9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform must match where the item lives: a project name or a Bottle Blue product slug:
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/notes/9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "note_id": "9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21",
  "title": "Ideas",
  "content": "Plant tomatoes in May",
  "created_at": "2026-07-08T09:15:42.123456",
  "updated_at": "2026-07-08T09:15:42.123456"
}
PUTapi.bottlebluellc.com/notes/{note_id}

Update a note.

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/notes/9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21 \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Ideas", "content": "Plant tomatoes in May, start seeds indoors in April"}'

# Scoped to one platform
# platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl https://api.bottlebluellc.com/v1/notes/9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21 \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Ideas", "content": "Plant tomatoes in May, start seeds indoors in April", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.put(
    "https://api.bottlebluellc.com/v1/notes/9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21",
    headers={"x-api-key": API_KEY},
    json={'title': 'Ideas', 'content': 'Plant tomatoes in May, start seeds indoors in April'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.put(
    "https://api.bottlebluellc.com/v1/notes/9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21",
    headers={"x-api-key": API_KEY},
    # platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    json={'title': 'Ideas', 'content': 'Plant tomatoes in May, start seeds indoors in April', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/notes/9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"title": "Ideas", "content": "Plant tomatoes in May, start seeds indoors in April"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/notes/9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"title": "Ideas", "content": "Plant tomatoes in May, start seeds indoors in April", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/notes/9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"title": "Ideas", "content": "Plant tomatoes in May, start seeds indoors in April"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/notes/9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"title": "Ideas", "content": "Plant tomatoes in May, start seeds indoors in April", "platform": "my_garden"})
});
200 Response
{
  "message": "Note updated"
}
PATCHapi.bottlebluellc.com/notes/{note_id}

Update a note (the handler treats PATCH like PUT).

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/notes/9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21 \
  -X PATCH \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"content": "Plant tomatoes in May, start seeds indoors in April"}'

# Scoped to one platform
# platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl https://api.bottlebluellc.com/v1/notes/9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21 \
  -X PATCH \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"content": "Plant tomatoes in May, start seeds indoors in April", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.patch(
    "https://api.bottlebluellc.com/v1/notes/9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21",
    headers={"x-api-key": API_KEY},
    json={'content': 'Plant tomatoes in May, start seeds indoors in April'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.patch(
    "https://api.bottlebluellc.com/v1/notes/9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21",
    headers={"x-api-key": API_KEY},
    # platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    json={'content': 'Plant tomatoes in May, start seeds indoors in April', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/notes/9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"content": "Plant tomatoes in May, start seeds indoors in April"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/notes/9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"content": "Plant tomatoes in May, start seeds indoors in April", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/notes/9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"content": "Plant tomatoes in May, start seeds indoors in April"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/notes/9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"content": "Plant tomatoes in May, start seeds indoors in April", "platform": "my_garden"})
});
200 Response
{
  "message": "Note updated"
}
DELETEapi.bottlebluellc.com/notes/{note_id}

Delete a note.

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/notes/9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21 \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl "https://api.bottlebluellc.com/v1/notes/9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21?platform=my_garden" \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.delete(
    "https://api.bottlebluellc.com/v1/notes/9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.delete(
    "https://api.bottlebluellc.com/v1/notes/9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21",
    headers={"x-api-key": API_KEY},
    # platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/notes/9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/notes/9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/notes/9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/notes/9b1c2f6e-4a3d-4c8b-b2e1-7f0d9a5c3e21?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "message": "Note deleted"
}
POSTapi.bottlebluellc.com/links

Link two records together. Also available on api.aquabotany.com.

ParameterInDescription
frombodyrequired Ref of the record the link starts at.
tobodyrequired Ref of the record the link points at.
typebodyoptional Relationship verb, your own vocabulary (letters, digits, and _.- , max 64). Defaults to related.
platformbodyoptional Namespace both refs must name. Defaults to default; * is not allowed on writes.
curl https://api.bottlebluellc.com/v1/links \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"from": "bb:event:default:xyz789", "to": "bb:task:default:abc123", "type": "has_task"}'

# Scoped to one platform (both refs must name the same one)
curl https://api.bottlebluellc.com/v1/links \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"from": "bb:event:my_garden:xyz789", "to": "bb:task:my_garden:abc123", "type": "has_task", "platform": "my_garden"}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/links",
    headers={"x-api-key": API_KEY},
    json={
        "from": "bb:event:default:xyz789",
        "to": "bb:task:default:abc123",
        "type": "has_task",
    },
)
print(resp.status_code, resp.json())

# Scoped to one platform (both refs must name the same one)
resp = requests.post(
    "https://api.bottlebluellc.com/v1/links",
    headers={"x-api-key": API_KEY},
    json={
        "from": "bb:event:my_garden:xyz789",
        "to": "bb:task:my_garden:abc123",
        "type": "has_task",
        "platform": "my_garden",
    },
)
const resp = await fetch("https://api.bottlebluellc.com/v1/links", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({
    from: "bb:event:default:xyz789",
    to: "bb:task:default:abc123",
    type: "has_task"
  })
});
const data = await resp.json();

// Scoped to one platform (both refs must name the same one)
const scoped = await fetch("https://api.bottlebluellc.com/v1/links", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({
    from: "bb:event:my_garden:xyz789",
    to: "bb:task:my_garden:abc123",
    type: "has_task",
    platform: "my_garden"
  })
});
const resp = await fetch("https://api.bottlebluellc.com/v1/links", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({
    from: "bb:event:default:xyz789",
    to: "bb:task:default:abc123",
    type: "has_task"
  })
});
const data = await resp.json();

// Scoped to one platform (both refs must name the same one)
const scoped = await fetch("https://api.bottlebluellc.com/v1/links", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({
    from: "bb:event:my_garden:xyz789",
    to: "bb:task:my_garden:abc123",
    type: "has_task",
    platform: "my_garden"
  })
});
200 Response
{
  "message": "Link created",
  "created": true,
  "link": {
    "from": "bb:event:default:xyz789",
    "to": "bb:task:default:abc123",
    "type": "has_task",
    "platform": "default",
    "created_at": "2026-07-19T14:02:11.482913"
  }
}

Lists

Base URL https://api.bottlebluellc.com/v1

The list lifecycle

A list is a named container; its entries are separate item records that point back at it. You build one in two steps - create the list, then add its items - so a full walkthrough is: (1) create a list and keep the list_id it returns, (2) add items with that same list_id, and (3) read the list back with its items attached. The per-operation reference for each call follows below.

# 1. Create a list. Only "name" is required; "description" is optional.
curl https://api.bottlebluellc.com/v1/list \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Spring shopping", "description": "For planting day"}'
# -> {"status": "created", "list_id": "lst_3f2c9a", "name": "Spring shopping", "platform": "default"}

# 2. Add items, reusing the list_id from step 1. One item per call; only "value" is required.
curl https://api.bottlebluellc.com/v1/list/lst_3f2c9a/item \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"value": "Potting soil", "notes": "2 bags"}'
# -> {"status": "item added", "item_id": "itm_c4a8f1", "platform": "default"}

curl https://api.bottlebluellc.com/v1/list/lst_3f2c9a/item \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"value": "Mulch"}'

# 3. Read the list back, its items included.
curl https://api.bottlebluellc.com/v1/list/lst_3f2c9a \
  -H "x-api-key: $BBL_API_KEY"
import requests

BB = "https://api.bottlebluellc.com/v1"
H = {"x-api-key": API_KEY}

# 1. Create a list. Only "name" is required; "description" is optional.
list_id = requests.post(f"{BB}/list", headers=H, json={
    "name": "Spring shopping", "description": "For planting day"}).json()["list_id"]

# 2. Add items, reusing list_id. One item per call; only "value" is required.
for value in ["Potting soil", "Mulch"]:
    requests.post(f"{BB}/list/{list_id}/item", headers=H, json={"value": value})

# 3. Read the list back, its items included.
detail = requests.get(f"{BB}/list/{list_id}", headers=H).json()
# detail["items"] -> [{"item_id": ..., "value": "Potting soil", "notes": "2 bags", ...}, ...]
const BB = "https://api.bottlebluellc.com/v1";
const H = { "x-api-key": API_KEY, "Content-Type": "application/json" };

// 1. Create a list. Only "name" is required; "description" is optional.
const { list_id } = await (await fetch(`${BB}/list`, {
  method: "POST", headers: H,
  body: JSON.stringify({ name: "Spring shopping", description: "For planting day" })
})).json();

// 2. Add items, reusing list_id. One item per call; only "value" is required.
for (const value of ["Potting soil", "Mulch"]) {
  await fetch(`${BB}/list/${list_id}/item`, {
    method: "POST", headers: H, body: JSON.stringify({ value })
  });
}

// 3. Read the list back, its items included.
const detail = await (await fetch(`${BB}/list/${list_id}`, { headers: H })).json();

There is no create-with-items shortcut. POST /list only accepts name and description; items are always added one at a time with POST /list/{listId}/item. The list must exist first - adding an item to an unknown list_id returns 404. Lists and their items are capped per account (a create past the cap returns 409 LIMIT_REACHED).

GETapi.bottlebluellc.com/list

Named lists (to-do, shopping, etc.).

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/list \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be "*" (read across every platform) or a Bottle Blue product slug:
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl "https://api.bottlebluellc.com/v1/list?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.bottlebluellc.com/v1/list",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.bottlebluellc.com/v1/list",
    headers={"x-api-key": API_KEY},
    # platform can also be "*" (read across every platform) or a Bottle Blue product slug:
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/list", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform) or a Bottle Blue product slug:
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/list?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/list", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform) or a Bottle Blue product slug:
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/list?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
[
  {
    "list_id": "3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42",
    "name": "Shopping",
    "description": null,
    "created_at": "2026-07-08T09:20:31.482910+00:00",
    "updated_at": "2026-07-08T09:20:31.482910+00:00"
  },
  {
    "list_id": "b7e1d4c8-05f2-49a6-8d3b-2e9c7a1f6540",
    "name": "Garden to-do",
    "description": "Spring planting tasks",
    "created_at": "2026-07-06T15:44:09.117203+00:00",
    "updated_at": "2026-07-07T11:02:54.660318+00:00"
  }
]
POSTapi.bottlebluellc.com/list

Named lists (to-do, shopping, etc.).

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/list \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Shopping"}'

# Scoped to one platform
# platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl https://api.bottlebluellc.com/v1/list \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Shopping", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.post(
    "https://api.bottlebluellc.com/v1/list",
    headers={"x-api-key": API_KEY},
    json={'name': 'Shopping'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.post(
    "https://api.bottlebluellc.com/v1/list",
    headers={"x-api-key": API_KEY},
    # platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    json={'name': 'Shopping', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/list", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"name": "Shopping"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/list", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"name": "Shopping", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/list", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"name": "Shopping"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/list", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"name": "Shopping", "platform": "my_garden"})
});
200 Response
{
  "status": "created",
  "list_id": "3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42",
  "name": "Shopping"
}
GETapi.bottlebluellc.com/list/{listId}

Read or delete a single list. Supports ?expand=links to inline this record's links in the same call - see Link System.

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42 \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform must match where the item lives: a project name or a Bottle Blue product slug:
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl "https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42",
    headers={"x-api-key": API_KEY},
    # platform must match where the item lives: a project name or a Bottle Blue product slug:
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform must match where the item lives: a project name or a Bottle Blue product slug:
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform must match where the item lives: a project name or a Bottle Blue product slug:
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "list_id": "3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42",
  "name": "Shopping",
  "description": null,
  "items": [
    {
      "item_id": "c4a8f1d2-6e3b-4c97-a5d8-0b2f9e7c4a13",
      "value": "Potting soil",
      "notes": null,
      "created_at": "2026-07-08T09:25:07.318842+00:00",
      "updated_at": "2026-07-08T09:25:07.318842+00:00"
    }
  ]
}
DELETEapi.bottlebluellc.com/list/{listId}

Read or delete a single list.

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42 \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl "https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42?platform=my_garden" \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.delete(
    "https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.delete(
    "https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42",
    headers={"x-api-key": API_KEY},
    # platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "status": "list deleted",
  "list_id": "3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42",
  "deleted": 2
}
POSTapi.bottlebluellc.com/list/{listId}/item

Add an item to a list.

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42/item \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"value": "Potting soil"}'

# Scoped to one platform
# platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42/item \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"value": "Potting soil", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.post(
    "https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42/item",
    headers={"x-api-key": API_KEY},
    json={'value': 'Potting soil'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.post(
    "https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42/item",
    headers={"x-api-key": API_KEY},
    # platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    json={'value': 'Potting soil', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42/item", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"value": "Potting soil"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42/item", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"value": "Potting soil", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42/item", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"value": "Potting soil"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42/item", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"value": "Potting soil", "platform": "my_garden"})
});
200 Response
{
  "status": "item added",
  "item_id": "c4a8f1d2-6e3b-4c97-a5d8-0b2f9e7c4a13"
}
PUTapi.bottlebluellc.com/list/{listId}/item/{itemId}

Update or remove a list item.

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42/item/c4a8f1d2-6e3b-4c97-a5d8-0b2f9e7c4a13 \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"value": "Rosemary plant", "notes": "Move to sunlight"}'

# Scoped to one platform
# platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42/item/c4a8f1d2-6e3b-4c97-a5d8-0b2f9e7c4a13 \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"value": "Rosemary plant", "notes": "Move to sunlight", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.put(
    "https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42/item/c4a8f1d2-6e3b-4c97-a5d8-0b2f9e7c4a13",
    headers={"x-api-key": API_KEY},
    json={'value': 'Rosemary plant', 'notes': 'Move to sunlight'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.put(
    "https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42/item/c4a8f1d2-6e3b-4c97-a5d8-0b2f9e7c4a13",
    headers={"x-api-key": API_KEY},
    # platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    json={'value': 'Rosemary plant', 'notes': 'Move to sunlight', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42/item/c4a8f1d2-6e3b-4c97-a5d8-0b2f9e7c4a13", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"value": "Rosemary plant", "notes": "Move to sunlight"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42/item/c4a8f1d2-6e3b-4c97-a5d8-0b2f9e7c4a13", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"value": "Rosemary plant", "notes": "Move to sunlight", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42/item/c4a8f1d2-6e3b-4c97-a5d8-0b2f9e7c4a13", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"value": "Rosemary plant", "notes": "Move to sunlight"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42/item/c4a8f1d2-6e3b-4c97-a5d8-0b2f9e7c4a13", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"value": "Rosemary plant", "notes": "Move to sunlight", "platform": "my_garden"})
});
200 Response
{
  "status": "updated",
  "item_id": "c4a8f1d2-6e3b-4c97-a5d8-0b2f9e7c4a13"
}
PATCHapi.bottlebluellc.com/list/{listId}/item/{itemId}

Update or remove a list item.

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42/item/c4a8f1d2-6e3b-4c97-a5d8-0b2f9e7c4a13 \
  -X PATCH \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"notes": "Move to sunlight"}'

# Scoped to one platform
# platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42/item/c4a8f1d2-6e3b-4c97-a5d8-0b2f9e7c4a13 \
  -X PATCH \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"notes": "Move to sunlight", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.patch(
    "https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42/item/c4a8f1d2-6e3b-4c97-a5d8-0b2f9e7c4a13",
    headers={"x-api-key": API_KEY},
    json={'notes': 'Move to sunlight'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.patch(
    "https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42/item/c4a8f1d2-6e3b-4c97-a5d8-0b2f9e7c4a13",
    headers={"x-api-key": API_KEY},
    # platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    json={'notes': 'Move to sunlight', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42/item/c4a8f1d2-6e3b-4c97-a5d8-0b2f9e7c4a13", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"notes": "Move to sunlight"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42/item/c4a8f1d2-6e3b-4c97-a5d8-0b2f9e7c4a13", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"notes": "Move to sunlight", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42/item/c4a8f1d2-6e3b-4c97-a5d8-0b2f9e7c4a13", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"notes": "Move to sunlight"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42/item/c4a8f1d2-6e3b-4c97-a5d8-0b2f9e7c4a13", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"notes": "Move to sunlight", "platform": "my_garden"})
});
200 Response
{
  "status": "updated",
  "item_id": "c4a8f1d2-6e3b-4c97-a5d8-0b2f9e7c4a13"
}
DELETEapi.bottlebluellc.com/list/{listId}/item/{itemId}

Update or remove a list item.

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42/item/c4a8f1d2-6e3b-4c97-a5d8-0b2f9e7c4a13 \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl "https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42/item/c4a8f1d2-6e3b-4c97-a5d8-0b2f9e7c4a13?platform=my_garden" \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.delete(
    "https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42/item/c4a8f1d2-6e3b-4c97-a5d8-0b2f9e7c4a13",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.delete(
    "https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42/item/c4a8f1d2-6e3b-4c97-a5d8-0b2f9e7c4a13",
    headers={"x-api-key": API_KEY},
    # platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42/item/c4a8f1d2-6e3b-4c97-a5d8-0b2f9e7c4a13", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42/item/c4a8f1d2-6e3b-4c97-a5d8-0b2f9e7c4a13?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42/item/c4a8f1d2-6e3b-4c97-a5d8-0b2f9e7c4a13", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/list/3f2c9a71-8b4e-4d2a-9c6e-1f5a7b3d8e42/item/c4a8f1d2-6e3b-4c97-a5d8-0b2f9e7c4a13?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "status": "deleted",
  "item_id": "c4a8f1d2-6e3b-4c97-a5d8-0b2f9e7c4a13"
}

Queues

Base URL https://api.bottlebluellc.com/v1

GETapi.bottlebluellc.com/queue/{queueId}

Read a queue's contents, or purge it.

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/queue/deploys \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be "*" (read across every platform) or a Bottle Blue product slug:
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl "https://api.bottlebluellc.com/v1/queue/deploys?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.bottlebluellc.com/v1/queue/deploys",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.bottlebluellc.com/v1/queue/deploys",
    headers={"x-api-key": API_KEY},
    # platform can also be "*" (read across every platform) or a Bottle Blue product slug:
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/queue/deploys", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform) or a Bottle Blue product slug:
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/queue/deploys?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/queue/deploys", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform) or a Bottle Blue product slug:
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/queue/deploys?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "queue_id": "QUEUE_ID",
  "items": [
    {
      "queue_id": "QUEUE_ID",
      "item_id": "0b6f1a3e-9d2c-4f5a-8e71-2c9d4b6a1f30",
      "user_id": "u123",
      "priority": 100,
      "payload": {
        "any": "json"
      },
      "state": "pending",
      "created_at": "2026-07-08T14:02:11.517301+00:00",
      "claimed_at": null,
      "lease_until": null,
      "claimed_by": null
    },
    {
      "queue_id": "QUEUE_ID",
      "item_id": "7d4c2b19-5e8f-4a06-b3d2-91c07e5a48f6",
      "user_id": "u456",
      "priority": 100,
      "payload": {
        "any": "json"
      },
      "state": "claimed",
      "created_at": "2026-07-08T13:55:40.102938+00:00",
      "claimed_at": "2026-07-08T14:01:02.884211+00:00",
      "lease_until": "2026-07-08T14:02:02.884211+00:00",
      "claimed_by": "u_9f3a1c7b8e2d0a4f"
    }
  ]
}
DELETEapi.bottlebluellc.com/queue/{queueId}

Read a queue's contents, or purge it.

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/queue/deploys \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl "https://api.bottlebluellc.com/v1/queue/deploys?platform=my_garden" \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.delete(
    "https://api.bottlebluellc.com/v1/queue/deploys",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.delete(
    "https://api.bottlebluellc.com/v1/queue/deploys",
    headers={"x-api-key": API_KEY},
    # platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/queue/deploys", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/queue/deploys?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/queue/deploys", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/queue/deploys?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "status": "purged",
  "queue_id": "QUEUE_ID",
  "deleted": 2
}
POSTapi.bottlebluellc.com/queue/{queueId}/claim

Claim the next item (leased for processing).

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/queue/deploys/claim \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"lease_seconds": 60}'

# Scoped to one platform
# platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl https://api.bottlebluellc.com/v1/queue/deploys/claim \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"lease_seconds": 60, "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.post(
    "https://api.bottlebluellc.com/v1/queue/deploys/claim",
    headers={"x-api-key": API_KEY},
    json={'lease_seconds': 60},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.post(
    "https://api.bottlebluellc.com/v1/queue/deploys/claim",
    headers={"x-api-key": API_KEY},
    # platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    json={'lease_seconds': 60, 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/queue/deploys/claim", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"lease_seconds": 60})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/queue/deploys/claim", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"lease_seconds": 60, "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/queue/deploys/claim", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"lease_seconds": 60})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/queue/deploys/claim", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"lease_seconds": 60, "platform": "my_garden"})
});
200 Response
{
  "status": "claimed",
  "queue_id": "QUEUE_ID",
  "item": {
    "queue_id": "QUEUE_ID",
    "item_id": "0b6f1a3e-9d2c-4f5a-8e71-2c9d4b6a1f30",
    "user_id": "u123",
    "priority": 100,
    "payload": {
      "any": "json"
    },
    "state": "claimed",
    "created_at": "2026-07-08T14:02:11.517301+00:00",
    "claimed_at": "2026-07-08T14:05:27.331904+00:00",
    "lease_until": "2026-07-08T14:06:27.331904+00:00",
    "claimed_by": "u_9f3a1c7b8e2d0a4f"
  },
  "claim_token": "5a1f0c8d-73b2-4e69-a4d5-6e0b9c2f18d7"
}
POSTapi.bottlebluellc.com/queue/{queueId}/join

Enqueue: add an item to the queue.

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/queue/deploys/join \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"user_id": "u123", "priority": 100, "payload": {"any": "json"}}'

# Scoped to one platform
# platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl https://api.bottlebluellc.com/v1/queue/deploys/join \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"user_id": "u123", "priority": 100, "payload": {"any": "json"}, "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.post(
    "https://api.bottlebluellc.com/v1/queue/deploys/join",
    headers={"x-api-key": API_KEY},
    json={'user_id': 'u123', 'priority': 100, 'payload': {'any': 'json'}},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.post(
    "https://api.bottlebluellc.com/v1/queue/deploys/join",
    headers={"x-api-key": API_KEY},
    # platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    json={'user_id': 'u123', 'priority': 100, 'payload': {'any': 'json'}, 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/queue/deploys/join", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"user_id": "u123", "priority": 100, "payload": {"any": "json"}})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/queue/deploys/join", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"user_id": "u123", "priority": 100, "payload": {"any": "json"}, "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/queue/deploys/join", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"user_id": "u123", "priority": 100, "payload": {"any": "json"}})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/queue/deploys/join", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"user_id": "u123", "priority": 100, "payload": {"any": "json"}, "platform": "my_garden"})
});
200 Response
{
  "status": "enqueued",
  "queue_id": "QUEUE_ID",
  "item_id": "0b6f1a3e-9d2c-4f5a-8e71-2c9d4b6a1f30"
}
GETapi.bottlebluellc.com/queue/{queueId}/next

Peek at the next item without claiming it.

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/queue/deploys/next \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be "*" (read across every platform) or a Bottle Blue product slug:
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl "https://api.bottlebluellc.com/v1/queue/deploys/next?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.bottlebluellc.com/v1/queue/deploys/next",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.bottlebluellc.com/v1/queue/deploys/next",
    headers={"x-api-key": API_KEY},
    # platform can also be "*" (read across every platform) or a Bottle Blue product slug:
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/queue/deploys/next", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform) or a Bottle Blue product slug:
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/queue/deploys/next?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/queue/deploys/next", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform) or a Bottle Blue product slug:
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/queue/deploys/next?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "queue_id": "QUEUE_ID",
  "item": {
    "queue_id": "QUEUE_ID",
    "item_id": "0b6f1a3e-9d2c-4f5a-8e71-2c9d4b6a1f30",
    "user_id": "u123",
    "priority": 100,
    "payload": {
      "any": "json"
    },
    "state": "pending",
    "created_at": "2026-07-08T14:02:11.517301+00:00",
    "claimed_at": null,
    "lease_until": null,
    "claimed_by": null
  }
}
POSTapi.bottlebluellc.com/queue/{queueId}/renew

Renew the lease on a claimed item.

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/queue/deploys/renew \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"item_id": "0b6f1a3e-9d2c-4f5a-8e71-2c9d4b6a1f30", "claim_token": "5a1f0c8d-73b2-4e69-a4d5-6e0b9c2f18d7", "lease_seconds": 60}'

# Scoped to one platform
# platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl https://api.bottlebluellc.com/v1/queue/deploys/renew \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"item_id": "0b6f1a3e-9d2c-4f5a-8e71-2c9d4b6a1f30", "claim_token": "5a1f0c8d-73b2-4e69-a4d5-6e0b9c2f18d7", "lease_seconds": 60, "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.post(
    "https://api.bottlebluellc.com/v1/queue/deploys/renew",
    headers={"x-api-key": API_KEY},
    json={'item_id': '0b6f1a3e-9d2c-4f5a-8e71-2c9d4b6a1f30', 'claim_token': '5a1f0c8d-73b2-4e69-a4d5-6e0b9c2f18d7', 'lease_seconds': 60},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.post(
    "https://api.bottlebluellc.com/v1/queue/deploys/renew",
    headers={"x-api-key": API_KEY},
    # platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    json={'item_id': '0b6f1a3e-9d2c-4f5a-8e71-2c9d4b6a1f30', 'claim_token': '5a1f0c8d-73b2-4e69-a4d5-6e0b9c2f18d7', 'lease_seconds': 60, 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/queue/deploys/renew", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"item_id": "0b6f1a3e-9d2c-4f5a-8e71-2c9d4b6a1f30", "claim_token": "5a1f0c8d-73b2-4e69-a4d5-6e0b9c2f18d7", "lease_seconds": 60})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/queue/deploys/renew", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"item_id": "0b6f1a3e-9d2c-4f5a-8e71-2c9d4b6a1f30", "claim_token": "5a1f0c8d-73b2-4e69-a4d5-6e0b9c2f18d7", "lease_seconds": 60, "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/queue/deploys/renew", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"item_id": "0b6f1a3e-9d2c-4f5a-8e71-2c9d4b6a1f30", "claim_token": "5a1f0c8d-73b2-4e69-a4d5-6e0b9c2f18d7", "lease_seconds": 60})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/queue/deploys/renew", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"item_id": "0b6f1a3e-9d2c-4f5a-8e71-2c9d4b6a1f30", "claim_token": "5a1f0c8d-73b2-4e69-a4d5-6e0b9c2f18d7", "lease_seconds": 60, "platform": "my_garden"})
});
200 Response
{
  "status": "renewed",
  "queue_id": "QUEUE_ID",
  "item": {
    "queue_id": "QUEUE_ID",
    "item_id": "0b6f1a3e-9d2c-4f5a-8e71-2c9d4b6a1f30",
    "user_id": "u123",
    "priority": 100,
    "payload": {
      "any": "json"
    },
    "state": "claimed",
    "created_at": "2026-07-08T14:02:11.517301+00:00",
    "claimed_at": "2026-07-08T14:05:27.331904+00:00",
    "lease_until": "2026-07-08T14:07:12.908844+00:00",
    "claimed_by": "u_9f3a1c7b8e2d0a4f"
  }
}
DELETEapi.bottlebluellc.com/queue/{queueId}/{itemId}

Delete a specific queued item.

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/queue/deploys/0b6f1a3e-9d2c-4f5a-8e71-2c9d4b6a1f30 \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl "https://api.bottlebluellc.com/v1/queue/deploys/0b6f1a3e-9d2c-4f5a-8e71-2c9d4b6a1f30?platform=my_garden" \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.delete(
    "https://api.bottlebluellc.com/v1/queue/deploys/0b6f1a3e-9d2c-4f5a-8e71-2c9d4b6a1f30",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.delete(
    "https://api.bottlebluellc.com/v1/queue/deploys/0b6f1a3e-9d2c-4f5a-8e71-2c9d4b6a1f30",
    headers={"x-api-key": API_KEY},
    # platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/queue/deploys/0b6f1a3e-9d2c-4f5a-8e71-2c9d4b6a1f30", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/queue/deploys/0b6f1a3e-9d2c-4f5a-8e71-2c9d4b6a1f30?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/queue/deploys/0b6f1a3e-9d2c-4f5a-8e71-2c9d4b6a1f30", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/queue/deploys/0b6f1a3e-9d2c-4f5a-8e71-2c9d4b6a1f30?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "status": "deleted",
  "forced": false,
  "queue_id": "QUEUE_ID",
  "item_id": "ITEM_ID"
}
POSTapi.bottlebluellc.com/queue/{queueId}/{itemId}/ack

Acknowledge (complete) a claimed item.

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/queue/deploys/0b6f1a3e-9d2c-4f5a-8e71-2c9d4b6a1f30/ack \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"claim_token": "5a1f0c8d-73b2-4e69-a4d5-6e0b9c2f18d7"}'

# Scoped to one platform
# platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl https://api.bottlebluellc.com/v1/queue/deploys/0b6f1a3e-9d2c-4f5a-8e71-2c9d4b6a1f30/ack \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"claim_token": "5a1f0c8d-73b2-4e69-a4d5-6e0b9c2f18d7", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.post(
    "https://api.bottlebluellc.com/v1/queue/deploys/0b6f1a3e-9d2c-4f5a-8e71-2c9d4b6a1f30/ack",
    headers={"x-api-key": API_KEY},
    json={'claim_token': '5a1f0c8d-73b2-4e69-a4d5-6e0b9c2f18d7'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.post(
    "https://api.bottlebluellc.com/v1/queue/deploys/0b6f1a3e-9d2c-4f5a-8e71-2c9d4b6a1f30/ack",
    headers={"x-api-key": API_KEY},
    # platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    json={'claim_token': '5a1f0c8d-73b2-4e69-a4d5-6e0b9c2f18d7', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/queue/deploys/0b6f1a3e-9d2c-4f5a-8e71-2c9d4b6a1f30/ack", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"claim_token": "5a1f0c8d-73b2-4e69-a4d5-6e0b9c2f18d7"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/queue/deploys/0b6f1a3e-9d2c-4f5a-8e71-2c9d4b6a1f30/ack", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"claim_token": "5a1f0c8d-73b2-4e69-a4d5-6e0b9c2f18d7", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/queue/deploys/0b6f1a3e-9d2c-4f5a-8e71-2c9d4b6a1f30/ack", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"claim_token": "5a1f0c8d-73b2-4e69-a4d5-6e0b9c2f18d7"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/queue/deploys/0b6f1a3e-9d2c-4f5a-8e71-2c9d4b6a1f30/ack", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"claim_token": "5a1f0c8d-73b2-4e69-a4d5-6e0b9c2f18d7", "platform": "my_garden"})
});
200 Response
{
  "status": "acknowledged",
  "queue_id": "QUEUE_ID",
  "item_id": "ITEM_ID"
}

Streaks

Base URL https://api.bottlebluellc.com/v1

GETapi.bottlebluellc.com/streaks/{userId}

Read, update, or delete a user's streak. Supports ?expand=links to inline this record's links in the same call - see Link System.

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/streaks/u123 \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be a Bottle Blue product slug (streaks do not support "*"):
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl "https://api.bottlebluellc.com/v1/streaks/u123?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.bottlebluellc.com/v1/streaks/u123",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.bottlebluellc.com/v1/streaks/u123",
    headers={"x-api-key": API_KEY},
    # platform can also be a Bottle Blue product slug (streaks do not support "*"):
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/streaks/u123", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be a Bottle Blue product slug (streaks do not support "*"):
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/streaks/u123?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/streaks/u123", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be a Bottle Blue product slug (streaks do not support "*"):
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/streaks/u123?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "user_id": "USER_ID",
  "current_streak": 5,
  "best_streak": 12,
  "last_active_date": "2026-07-08",
  "version": 17,
  "updated_at": "2026-07-08T14:05:27.331904"
}
PUTapi.bottlebluellc.com/streaks/{userId}

Read, update, or delete a user's streak.

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/streaks/u123 \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"current_streak": 5, "best_streak": 12, "last_active_date": "2026-07-07", "version": 17}'

# Scoped to one platform
# platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl https://api.bottlebluellc.com/v1/streaks/u123 \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"current_streak": 5, "best_streak": 12, "last_active_date": "2026-07-07", "version": 17, "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.put(
    "https://api.bottlebluellc.com/v1/streaks/u123",
    headers={"x-api-key": API_KEY},
    json={'current_streak': 5, 'best_streak': 12, 'last_active_date': '2026-07-07', 'version': 17},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.put(
    "https://api.bottlebluellc.com/v1/streaks/u123",
    headers={"x-api-key": API_KEY},
    # platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    json={'current_streak': 5, 'best_streak': 12, 'last_active_date': '2026-07-07', 'version': 17, 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/streaks/u123", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"current_streak": 5, "best_streak": 12, "last_active_date": "2026-07-07", "version": 17})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/streaks/u123", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"current_streak": 5, "best_streak": 12, "last_active_date": "2026-07-07", "version": 17, "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/streaks/u123", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"current_streak": 5, "best_streak": 12, "last_active_date": "2026-07-07", "version": 17})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/streaks/u123", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"current_streak": 5, "best_streak": 12, "last_active_date": "2026-07-07", "version": 17, "platform": "my_garden"})
});
200 Response
{
  "status": "ok",
  "user_id": "USER_ID",
  "current_streak": 5,
  "best_streak": 12,
  "last_active_date": "2026-07-07",
  "version": 18,
  "updated_at": "2026-07-08T14:06:03.118220"
}
PATCHapi.bottlebluellc.com/streaks/{userId}

Read, update, or delete a user's streak.

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/streaks/u123 \
  -X PATCH \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"best_streak": 12}'

# Scoped to one platform
# platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl https://api.bottlebluellc.com/v1/streaks/u123 \
  -X PATCH \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"best_streak": 12, "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.patch(
    "https://api.bottlebluellc.com/v1/streaks/u123",
    headers={"x-api-key": API_KEY},
    json={'best_streak': 12},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.patch(
    "https://api.bottlebluellc.com/v1/streaks/u123",
    headers={"x-api-key": API_KEY},
    # platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    json={'best_streak': 12, 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/streaks/u123", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"best_streak": 12})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/streaks/u123", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"best_streak": 12, "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/streaks/u123", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"best_streak": 12})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/streaks/u123", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"best_streak": 12, "platform": "my_garden"})
});
200 Response
{
  "status": "ok",
  "user_id": "USER_ID",
  "current_streak": 5,
  "best_streak": 12,
  "last_active_date": "2026-07-07",
  "version": 18,
  "updated_at": "2026-07-08T14:06:03.118220"
}
DELETEapi.bottlebluellc.com/streaks/{userId}

Read, update, or delete a user's streak.

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/streaks/u123 \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl "https://api.bottlebluellc.com/v1/streaks/u123?platform=my_garden" \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.delete(
    "https://api.bottlebluellc.com/v1/streaks/u123",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.delete(
    "https://api.bottlebluellc.com/v1/streaks/u123",
    headers={"x-api-key": API_KEY},
    # platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/streaks/u123", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/streaks/u123?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/streaks/u123", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/streaks/u123?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "status": "cleared",
  "user_id": "USER_ID",
  "deleted": 31
}
POSTapi.bottlebluellc.com/streaks/{userId}/checkin

Record a daily check-in, advancing the streak.

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/streaks/u123/checkin \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"date": "2026-07-08"}'

# Scoped to one platform
# platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl https://api.bottlebluellc.com/v1/streaks/u123/checkin \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"date": "2026-07-08", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.post(
    "https://api.bottlebluellc.com/v1/streaks/u123/checkin",
    headers={"x-api-key": API_KEY},
    json={'date': '2026-07-08'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.post(
    "https://api.bottlebluellc.com/v1/streaks/u123/checkin",
    headers={"x-api-key": API_KEY},
    # platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    json={'date': '2026-07-08', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/streaks/u123/checkin", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"date": "2026-07-08"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/streaks/u123/checkin", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"date": "2026-07-08", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/streaks/u123/checkin", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"date": "2026-07-08"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/streaks/u123/checkin", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"date": "2026-07-08", "platform": "my_garden"})
});
200 Response
{
  "status": "ok",
  "user_id": "USER_ID",
  "date": "2026-07-08",
  "current_streak": 6,
  "best_streak": 12,
  "last_active_date": "2026-07-08",
  "version": 18
}
GETapi.bottlebluellc.com/streaks/{userId}/history

The user's streak history.

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/streaks/u123/history \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be a Bottle Blue product slug (streaks do not support "*"):
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl "https://api.bottlebluellc.com/v1/streaks/u123/history?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.bottlebluellc.com/v1/streaks/u123/history",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.bottlebluellc.com/v1/streaks/u123/history",
    headers={"x-api-key": API_KEY},
    # platform can also be a Bottle Blue product slug (streaks do not support "*"):
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/streaks/u123/history", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be a Bottle Blue product slug (streaks do not support "*"):
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/streaks/u123/history?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/streaks/u123/history", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be a Bottle Blue product slug (streaks do not support "*"):
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/streaks/u123/history?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "user_id": "USER_ID",
  "start": "2026-06-09",
  "end": "2026-07-08",
  "days": [
    "2026-07-03",
    "2026-07-04",
    "2026-07-05",
    "2026-07-06",
    "2026-07-07",
    "2026-07-08"
  ]
}

Leaderboards

Base URL https://api.bottlebluellc.com/v1

POSTapi.bottlebluellc.com/leaderboard/score

Submit a score to a leaderboard. On your own platform you may submit your own score; a Bottle Blue product-platform board is company-managed.

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/leaderboard/score \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"score": 4200}'

# Scoped to one platform
# platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
# product-platform boards are company-managed
curl https://api.bottlebluellc.com/v1/leaderboard/score \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"score": 4200, "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.post(
    "https://api.bottlebluellc.com/v1/leaderboard/score",
    headers={"x-api-key": API_KEY},
    json={'score': 4200},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.post(
    "https://api.bottlebluellc.com/v1/leaderboard/score",
    headers={"x-api-key": API_KEY},
    # platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    # product-platform boards are company-managed
    json={'score': 4200, 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/leaderboard/score", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"score": 4200})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/leaderboard/score", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  // product-platform boards are company-managed
  body: JSON.stringify({"score": 4200, "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/leaderboard/score", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"score": 4200})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/leaderboard/score", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  // product-platform boards are company-managed
  body: JSON.stringify({"score": 4200, "platform": "my_garden"})
});
200 Response
{
  "message": "Score submitted",
  "user_id": "u_9f3a1c7b8e2d0a4f", "isYou": true,
  "score": 4200
}
GETapi.bottlebluellc.com/leaderboard/top

Top scores on a leaderboard. On a Bottle Blue product board this is the shared, public ranking; on your own project platform the board is private, so it returns only your own entry.

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/leaderboard/top \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be "*" (read across every platform) or a Bottle Blue product slug:
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl "https://api.bottlebluellc.com/v1/leaderboard/top?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.bottlebluellc.com/v1/leaderboard/top",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.bottlebluellc.com/v1/leaderboard/top",
    headers={"x-api-key": API_KEY},
    # platform can also be "*" (read across every platform) or a Bottle Blue product slug:
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/leaderboard/top", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform) or a Bottle Blue product slug:
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/leaderboard/top?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/leaderboard/top", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform) or a Bottle Blue product slug:
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/leaderboard/top?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
[
  {
    "rank": 1,
    "user_id": "u_9f3a1c7b8e2d0a4f", "isYou": true,
    "score": 4200,
    "updated_at": "2026-07-08T14:07:45.220145"
  },
  {
    "rank": 2,
    "user_id": "u_2b3c4d5e6f7a8b9c", "isYou": false,
    "score": 3100,
    "updated_at": "2026-07-07T21:33:02.994310"
  },
  {
    "rank": 3,
    "user_id": "u_3c4d5e6f7a8b9c0d", "isYou": false,
    "score": 2875,
    "updated_at": "2026-07-06T18:12:59.410877"
  }
]
GETapi.bottlebluellc.com/leaderboard/{userId}

Read or remove a user's leaderboard entry. Supports ?expand=links to inline this record's links in the same call - see Link System.

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/leaderboard/you@example.com \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform must match where the item lives: a project name or a Bottle Blue product slug:
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl "https://api.bottlebluellc.com/v1/leaderboard/you@example.com?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.bottlebluellc.com/v1/leaderboard/you@example.com",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.bottlebluellc.com/v1/leaderboard/you@example.com",
    headers={"x-api-key": API_KEY},
    # platform must match where the item lives: a project name or a Bottle Blue product slug:
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/leaderboard/you@example.com", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform must match where the item lives: a project name or a Bottle Blue product slug:
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/leaderboard/you@example.com?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/leaderboard/you@example.com", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform must match where the item lives: a project name or a Bottle Blue product slug:
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/leaderboard/you@example.com?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "user_id": "u_9f3a1c7b8e2d0a4f", "isYou": true,
  "score": 4200,
  "updated_at": "2026-07-08T14:07:45.220145"
}
DELETEapi.bottlebluellc.com/leaderboard/{userId}

Read or remove a user's leaderboard entry.

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/leaderboard/you@example.com \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
# product-platform boards are company-managed
curl "https://api.bottlebluellc.com/v1/leaderboard/you@example.com?platform=my_garden" \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.delete(
    "https://api.bottlebluellc.com/v1/leaderboard/you@example.com",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.delete(
    "https://api.bottlebluellc.com/v1/leaderboard/you@example.com",
    headers={"x-api-key": API_KEY},
    # platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    # product-platform boards are company-managed
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/leaderboard/you@example.com", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
// product-platform boards are company-managed
const scoped = await fetch("https://api.bottlebluellc.com/v1/leaderboard/you@example.com?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/leaderboard/you@example.com", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
// product-platform boards are company-managed
const scoped = await fetch("https://api.bottlebluellc.com/v1/leaderboard/you@example.com?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "message": "Score deleted for you@example.com"
}

Scoreboards

Base URL https://api.bottlebluellc.com/v1

Scoreboard visibility. On a Bottle Blue product platform a scoreboard is a shared live board: anyone may read it and post team scores. On your own project platform it is private to its owner (the first account to score the match): only the owner can read or score it, and another account reusing the same match id and platform sees an empty board and gets 403 NOT_MATCH_OWNER on a write.
GETapi.bottlebluellc.com/scoreboard/{matchId}

Read or delete a live scoreboard for a match. Supports ?expand=links to inline this record's links in the same call - see Link System.

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/scoreboard/match-001 \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be "*" (read across every platform) or a Bottle Blue product slug:
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl "https://api.bottlebluellc.com/v1/scoreboard/match-001?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.bottlebluellc.com/v1/scoreboard/match-001",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.bottlebluellc.com/v1/scoreboard/match-001",
    headers={"x-api-key": API_KEY},
    # platform can also be "*" (read across every platform) or a Bottle Blue product slug:
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/scoreboard/match-001", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform) or a Bottle Blue product slug:
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/scoreboard/match-001?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/scoreboard/match-001", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform) or a Bottle Blue product slug:
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/scoreboard/match-001?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "match_id": "MATCH_ID",
  "teams": [
    {
      "team_id": "TEAM_ID",
      "score": 21,
      "updated_at": "2026-07-08T14:09:12.554873"
    },
    {
      "team_id": "team-blue",
      "score": 14,
      "updated_at": "2026-07-08T14:08:41.220981"
    }
  ]
}
DELETEapi.bottlebluellc.com/scoreboard/{matchId}

Read or delete a live scoreboard for a match.

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/scoreboard/match-001 \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl "https://api.bottlebluellc.com/v1/scoreboard/match-001?platform=my_garden" \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.delete(
    "https://api.bottlebluellc.com/v1/scoreboard/match-001",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.delete(
    "https://api.bottlebluellc.com/v1/scoreboard/match-001",
    headers={"x-api-key": API_KEY},
    # platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/scoreboard/match-001", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/scoreboard/match-001?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/scoreboard/match-001", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/scoreboard/match-001?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "status": "cleared",
  "match_id": "MATCH_ID",
  "deleted": 2
}
PUTapi.bottlebluellc.com/scoreboard/{matchId}/team/{teamId}

Set or update a team's score.

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/scoreboard/match-001/team/team-red \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"score": 21}'

# Scoped to one platform
# platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl https://api.bottlebluellc.com/v1/scoreboard/match-001/team/team-red \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"score": 21, "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.put(
    "https://api.bottlebluellc.com/v1/scoreboard/match-001/team/team-red",
    headers={"x-api-key": API_KEY},
    json={'score': 21},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.put(
    "https://api.bottlebluellc.com/v1/scoreboard/match-001/team/team-red",
    headers={"x-api-key": API_KEY},
    # platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    json={'score': 21, 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/scoreboard/match-001/team/team-red", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"score": 21})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/scoreboard/match-001/team/team-red", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"score": 21, "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/scoreboard/match-001/team/team-red", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"score": 21})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/scoreboard/match-001/team/team-red", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"score": 21, "platform": "my_garden"})
});
200 Response
{
  "status": "ok",
  "mode": "set",
  "match_id": "MATCH_ID",
  "team_id": "TEAM_ID",
  "score": 21
}
PATCHapi.bottlebluellc.com/scoreboard/{matchId}/team/{teamId}

Set or update a team's score.

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/scoreboard/match-001/team/team-red \
  -X PATCH \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"delta": 3}'

# Scoped to one platform
# platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl https://api.bottlebluellc.com/v1/scoreboard/match-001/team/team-red \
  -X PATCH \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"delta": 3, "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.patch(
    "https://api.bottlebluellc.com/v1/scoreboard/match-001/team/team-red",
    headers={"x-api-key": API_KEY},
    json={'delta': 3},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.patch(
    "https://api.bottlebluellc.com/v1/scoreboard/match-001/team/team-red",
    headers={"x-api-key": API_KEY},
    # platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    json={'delta': 3, 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/scoreboard/match-001/team/team-red", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"delta": 3})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/scoreboard/match-001/team/team-red", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"delta": 3, "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/scoreboard/match-001/team/team-red", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"delta": 3})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/scoreboard/match-001/team/team-red", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"delta": 3, "platform": "my_garden"})
});
200 Response
{
  "status": "ok",
  "mode": "delta",
  "match_id": "MATCH_ID",
  "team_id": "TEAM_ID",
  "score": 24
}
POSTapi.bottlebluellc.com/scoreboard/{matchId}/team/{teamId}/increment

Increment a team's score.

# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/scoreboard/match-001/team/team-red/increment \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"delta": 1}'

# Scoped to one platform
# platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl https://api.bottlebluellc.com/v1/scoreboard/match-001/team/team-red/increment \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"delta": 1, "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.post(
    "https://api.bottlebluellc.com/v1/scoreboard/match-001/team/team-red/increment",
    headers={"x-api-key": API_KEY},
    json={'delta': 1},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.post(
    "https://api.bottlebluellc.com/v1/scoreboard/match-001/team/team-red/increment",
    headers={"x-api-key": API_KEY},
    # platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    json={'delta': 1, 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/scoreboard/match-001/team/team-red/increment", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"delta": 1})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/scoreboard/match-001/team/team-red/increment", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"delta": 1, "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/scoreboard/match-001/team/team-red/increment", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"delta": 1})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/scoreboard/match-001/team/team-red/increment", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"delta": 1, "platform": "my_garden"})
});
200 Response
{
  "status": "ok",
  "match_id": "MATCH_ID",
  "team_id": "TEAM_ID",
  "score": 25
}

Assets

Base URL https://api.bottlebluellc.com/v1

GETapi.bottlebluellc.com/assets

List a user's assets.

Each asset carries its reservation fields (reservedFor, reservedBy, reservedAt, reservedFrom, reservedUntil) only while a hold is in place.

ParameterInDescription
orgIdqueryOptional. Scope to a shared organization's asset instead of your own account - see Organizations. Any member role can list.
# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/assets \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be "*" (read across every platform) or a Bottle Blue product slug:
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl "https://api.bottlebluellc.com/v1/assets?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.bottlebluellc.com/v1/assets",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.bottlebluellc.com/v1/assets",
    headers={"x-api-key": API_KEY},
    # platform can also be "*" (read across every platform) or a Bottle Blue product slug:
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/assets", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform) or a Bottle Blue product slug:
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/assets?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/assets", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform) or a Bottle Blue product slug:
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/assets?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "assets": [
    {
      "entityType": "asset",
      "assetId": "0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07",
      "name": "Garden shed",
      "type": "structure",
      "assetType": "structure",
      "status": "available",
      "platform": "default",
      "createdAt": "2026-07-08T09:30:12Z",
      "createdBy": "owner@acme.com",
      "metadata": {
        "color": "green"
      }
    }
  ]
}
POSTapi.bottlebluellc.com/assets

Create an asset.

ParameterInDescription
orgIdbodyOptional. Scope to a shared organization's asset instead of your own account - see Organizations. Any member role can create.
# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/assets \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Garden shed", "type": "structure", "metadata": {"color": "green"}}'

# Scoped to one platform
# platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl https://api.bottlebluellc.com/v1/assets \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Garden shed", "type": "structure", "metadata": {"color": "green"}, "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.post(
    "https://api.bottlebluellc.com/v1/assets",
    headers={"x-api-key": API_KEY},
    json={'name': 'Garden shed', 'type': 'structure', 'metadata': {'color': 'green'}},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.post(
    "https://api.bottlebluellc.com/v1/assets",
    headers={"x-api-key": API_KEY},
    # platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    json={'name': 'Garden shed', 'type': 'structure', 'metadata': {'color': 'green'}, 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/assets", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"name": "Garden shed", "type": "structure", "metadata": {"color": "green"}})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/assets", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"name": "Garden shed", "type": "structure", "metadata": {"color": "green"}, "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/assets", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"name": "Garden shed", "type": "structure", "metadata": {"color": "green"}})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/assets", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"name": "Garden shed", "type": "structure", "metadata": {"color": "green"}, "platform": "my_garden"})
});
201 Response
{
  "assetId": "0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07"
}
GETapi.bottlebluellc.com/assets/{id}

Get a single asset. Supports ?expand=links to inline this record's links in the same call - see Link System.

The reservation fields reservedFor, reservedBy, reservedAt, reservedFrom and reservedUntil are present only while a hold is in place.

ParameterInDescription
idpathrequired Asset id.
orgIdqueryOptional. Scope to a shared organization's asset instead of your own account - see Organizations. Any member role can read.
# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07 \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform must match where the item lives: a project name or a Bottle Blue product slug:
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl "https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07",
    headers={"x-api-key": API_KEY},
    # platform must match where the item lives: a project name or a Bottle Blue product slug:
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform must match where the item lives: a project name or a Bottle Blue product slug:
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform must match where the item lives: a project name or a Bottle Blue product slug:
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "entityType": "asset",
  "assetId": "0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07",
  "name": "Garden shed",
  "type": "structure",
  "assetType": "structure",
  "status": "available",
  "platform": "default",
  "createdAt": "2026-07-08T09:30:12Z",
  "createdBy": "owner@acme.com",
  "metadata": {
    "color": "green"
  },
  "reservedFor": "Sam Rivera",
  "reservedBy": "owner@acme.com",
  "reservedAt": "2026-09-05T10:42:00Z",
  "reservedFrom": "2026-09-08T08:00:00Z",
  "reservedUntil": "2026-09-09T17:00:00Z"
}
PATCHapi.bottlebluellc.com/assets/{id}

Update an asset's name, category, subtype, status, or metadata.

Every body field is optional, but at least one must be present: a request that would change nothing returns 400 {"error": "No fields to update"}, so a mistyped field name cannot look like a success. metadata is merged into the stored object rather than replacing it, so keys you leave out keep their current values, and omitting metadata entirely leaves it alone.

checked_out is not settable here and returns 400. That status is reachable only through check-out, which is what keeps it from ever disagreeing with checkedOutTo, checkedOutBy and checkedOutAt. Changing status while the asset is checked out returns 409 CHECKED_OUT; every other field can still be patched while it is out.

A status change appends a status_change event to the asset's history. A rename or a metadata edit does not: the history is a custody log, not a field-level audit trail.

ParameterInDescription
idpathrequired Asset id.
namebodyOptional. New display name. Non-empty string; surrounding whitespace is trimmed.
typebodyOptional. The asset's category: tools, vehicles, electronics, components or other. Non-empty string.
assetTypebodyOptional. Free-text subtype, for detail the fixed category list does not carry (for example cordless drill). Non-empty string.
statusbodyOptional. One of available, maintenance or lost. checked_out is rejected here.
metadatabodyOptional. An object of your own fields, merged into the existing metadata rather than replacing it.
orgIdbodyOptional. Scope to a shared organization's asset instead of your own account - see Organizations. Any member role can edit.
# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07 \
  -X PATCH \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Garden shed", "status": "maintenance", "metadata": {"notes": "Repainted this spring"}}'

# Scoped to one platform
# platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07 \
  -X PATCH \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"metadata": {"notes": "Repainted this spring"}, "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.patch(
    "https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07",
    headers={"x-api-key": API_KEY},
    json={'name': 'Garden shed', 'status': 'maintenance', 'metadata': {'notes': 'Repainted this spring'}},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.patch(
    "https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07",
    headers={"x-api-key": API_KEY},
    # platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    json={'metadata': {'notes': 'Repainted this spring'}, 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"name": "Garden shed", "status": "maintenance", "metadata": {"notes": "Repainted this spring"}})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"metadata": {"notes": "Repainted this spring"}, "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"name": "Garden shed", "status": "maintenance", "metadata": {"notes": "Repainted this spring"}})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
  // "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
  body: JSON.stringify({"metadata": {"notes": "Repainted this spring"}, "platform": "my_garden"})
});
200 Response
{
  "message": "Asset updated"
}
400 Response
{
  "error": "No fields to update"
}
409 Response
{
  "error": "Check the asset in before changing its status",
  "code": "CHECKED_OUT"
}
DELETEapi.bottlebluellc.com/assets/{id}

Delete an asset.

ParameterInDescription
idpathrequired Asset id.
orgIdbodyOptional. Scope to a shared organization's asset instead of your own account - see Organizations. Requires the admin role or higher (deleting inventory outright is more destructive than checking it in or out).
# Default namespace (platform omitted)
curl https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07 \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
# "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
curl "https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07?platform=my_garden" \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.delete(
    "https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.delete(
    "https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07",
    headers={"x-api-key": API_KEY},
    # platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
    # "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be a Bottle Blue product slug ("*" is not allowed on writes):
// "bb_aquabotany" Aqua Botany, "bb_pocketquant" PocketQuant, "bb_bottleblue" Bottle Blue App, "bb_apparatus" Apparatus Ready, "bb_trackless" Trackless
const scoped = await fetch("https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "message": "Asset deleted"
}
POSTapi.bottlebluellc.com/assets/{id}/checkout

Check an asset out to someone. Sets status to checked_out and appends an event to the asset's history.

If the asset is reserved for someone whose name does not match checkedOutTo, the call returns 409 RESERVED_FOR_OTHER unless you pass override. The two names are compared with surrounding whitespace stripped and letter case ignored, since both sides are free text a person typed. A successful check-out consumes any reservation, clearing reservedFor, reservedBy, reservedAt, reservedFrom and reservedUntil.

ParameterInDescription
idpathrequired Asset id.
checkedOutTobodyrequired Free-text name of the person or place the asset is going to. No login is required for the recipient.
dueBackAtbodyOptional ISO-8601 timestamp.
notesbodyOptional free text, stored on the history event.
overridebodyOptional boolean. Check the asset out even though it is reserved for someone else.
orgIdbodyOptional. Scope to a shared organization's asset instead of your own - see Organizations. Any member role can check out.
curl https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07/checkout \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"checkedOutTo": "Jane Doe", "dueBackAt": "2026-09-06T00:00:00Z", "orgId": "3f1c9e2a7d614b839e5a3f8b1c6d2e07"}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07/checkout",
    headers={"x-api-key": API_KEY},
    json={
        'checkedOutTo': 'Jane Doe',
        'dueBackAt': '2026-09-06T00:00:00Z',
        'orgId': '3f1c9e2a7d614b839e5a3f8b1c6d2e07',  # omit for a personal asset
    },
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07/checkout", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({
    checkedOutTo: "Jane Doe",
    dueBackAt: "2026-09-06T00:00:00Z",
    orgId: "3f1c9e2a7d614b839e5a3f8b1c6d2e07" // omit for a personal asset
  })
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07/checkout", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({
    checkedOutTo: "Jane Doe",
    dueBackAt: "2026-09-06T00:00:00Z",
    orgId: "3f1c9e2a7d614b839e5a3f8b1c6d2e07" // omit for a personal asset
  })
});
const data = await resp.json();
200 Response
{
  "assetId": "0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07",
  "status": "checked_out",
  "checkedOutTo": "Jane Doe",
  "checkedOutBy": "owner@acme.com",
  "checkedOutAt": "2026-08-30T12:15:00Z",
  "dueBackAt": "2026-09-06T00:00:00Z"
}
409 Response
{
  "error": "Asset is already checked out",
  "code": "ALREADY_CHECKED_OUT"
}
409 Response
{
  "error": "Asset is reserved for Sam Rivera; pass override to check it out anyway",
  "code": "RESERVED_FOR_OTHER"
}
POSTapi.bottlebluellc.com/assets/{id}/checkin

Check an asset back in. Sets status back to available, clears the checkout fields, and appends an event to the asset's history.

ParameterInDescription
idpathrequired Asset id.
notesbodyOptional free text, stored on the history event.
orgIdbodyOptional. Same organization scoping as check-out.
curl https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07/checkin \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"notes": "returned in good condition", "orgId": "3f1c9e2a7d614b839e5a3f8b1c6d2e07"}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07/checkin",
    headers={"x-api-key": API_KEY},
    json={'notes': 'returned in good condition', 'orgId': '3f1c9e2a7d614b839e5a3f8b1c6d2e07'},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07/checkin", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({ notes: "returned in good condition", orgId: "3f1c9e2a7d614b839e5a3f8b1c6d2e07" })
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07/checkin", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({ notes: "returned in good condition", orgId: "3f1c9e2a7d614b839e5a3f8b1c6d2e07" })
});
const data = await resp.json();
200 Response
{
  "assetId": "0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07",
  "status": "available",
  "checkedInBy": "owner@acme.com",
  "checkedInAt": "2026-09-02T09:00:00Z"
}
409 Response
{
  "error": "Asset is not checked out",
  "code": "NOT_CHECKED_OUT"
}
POSTapi.bottlebluellc.com/assets/{id}/reserve

Hold an asset for a named person.

A reservation is a separate axis from status, not another status value. The common case is holding something that is currently checked out to someone else, which a single status field cannot express, so reserving never changes status and never blocks a check-in.

Returns 400 if reservedFor is missing, empty, or longer than 200 characters, 404 if there is no such asset, and 409 ALREADY_RESERVED if the asset is already held. Release a hold with POST /assets/{id}/unreserve; a successful check-out consumes it instead. The reservation fields appear on GET /assets/{id} and GET /assets while the hold is in place, and the hold is recorded as a reserve event in the asset's history.

ParameterInDescription
idpathrequired Asset id.
reservedForbodyrequired Free-text name of the person the asset is being held for. Maximum 200 characters. No login is required for that person.
reservedFrombodyOptional ISO-8601 timestamp for when the hold starts.
reservedUntilbodyOptional ISO-8601 timestamp for when the hold ends.
notesbodyOptional free text, stored on the history event.
orgIdbodyOptional. Scope to a shared organization's asset instead of your own account (see Organizations). Any member role can reserve.
curl https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07/reserve \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"reservedFor": "Sam Rivera", "reservedFrom": "2026-09-08T08:00:00Z", "reservedUntil": "2026-09-09T17:00:00Z", "orgId": "3f1c9e2a7d614b839e5a3f8b1c6d2e07"}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07/reserve",
    headers={"x-api-key": API_KEY},
    json={
        'reservedFor': 'Sam Rivera',
        'reservedFrom': '2026-09-08T08:00:00Z',
        'reservedUntil': '2026-09-09T17:00:00Z',
        'orgId': '3f1c9e2a7d614b839e5a3f8b1c6d2e07',  # omit for a personal asset
    },
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07/reserve", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({
    reservedFor: "Sam Rivera",
    reservedFrom: "2026-09-08T08:00:00Z",
    reservedUntil: "2026-09-09T17:00:00Z",
    orgId: "3f1c9e2a7d614b839e5a3f8b1c6d2e07" // omit for a personal asset
  })
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07/reserve", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({
    reservedFor: "Sam Rivera",
    reservedFrom: "2026-09-08T08:00:00Z",
    reservedUntil: "2026-09-09T17:00:00Z",
    orgId: "3f1c9e2a7d614b839e5a3f8b1c6d2e07" // omit for a personal asset
  })
});
const data = await resp.json();
200 Response
{
  "assetId": "0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07",
  "reservedFor": "Sam Rivera",
  "reservedBy": "owner@acme.com",
  "reservedAt": "2026-09-05T10:42:00Z",
  "reservedFrom": "2026-09-08T08:00:00Z",
  "reservedUntil": "2026-09-09T17:00:00Z"
}
409 Response
{
  "error": "Asset is already reserved",
  "code": "ALREADY_RESERVED"
}
POSTapi.bottlebluellc.com/assets/{id}/unreserve

Release an asset's reservation.

Any member may release a hold, matching check-in: a reservation is a shared signal about who is next in line, not a private lock. Returns 404 if there is no such asset and 409 NOT_RESERVED if nothing is holding it. Clearing a hold appends an unreserve event to the asset's history.

ParameterInDescription
idpathrequired Asset id.
notesbodyOptional free text, stored on the history event.
orgIdbodyOptional. Same organization scoping as reserve.
curl https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07/unreserve \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"notes": "install rescheduled", "orgId": "3f1c9e2a7d614b839e5a3f8b1c6d2e07"}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07/unreserve",
    headers={"x-api-key": API_KEY},
    json={'notes': 'install rescheduled', 'orgId': '3f1c9e2a7d614b839e5a3f8b1c6d2e07'},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07/unreserve", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({ notes: "install rescheduled", orgId: "3f1c9e2a7d614b839e5a3f8b1c6d2e07" })
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07/unreserve", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({ notes: "install rescheduled", orgId: "3f1c9e2a7d614b839e5a3f8b1c6d2e07" })
});
const data = await resp.json();
200 Response
{
  "assetId": "0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07",
  "reservationReleasedBy": "owner@acme.com"
}
409 Response
{
  "error": "Asset is not reserved",
  "code": "NOT_RESERVED"
}
GETapi.bottlebluellc.com/assets/{id}/history

The asset's full custody history, newest first.

Every event records who did it and when. action is one of checkout, checkin, reserve, unreserve or status_change; a status_change event also carries fromStatus and toStatus. source is "manual" on every event today and exists as a hook for future automated check-in/out: a camera-driven check-in (for example, a Roost camera) will show up here with "source": "roost_camera" instead, with no other change to this endpoint.

ParameterInDescription
idpathrequired Asset id.
orgIdqueryOptional. Same organization scoping as check-out.
curl "https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07/history?orgId=3f1c9e2a7d614b839e5a3f8b1c6d2e07" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07/history",
    headers={"x-api-key": API_KEY},
    params={"orgId": "3f1c9e2a7d614b839e5a3f8b1c6d2e07"},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07/history?orgId=3f1c9e2a7d614b839e5a3f8b1c6d2e07", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/assets/0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07/history?orgId=3f1c9e2a7d614b839e5a3f8b1c6d2e07", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "assetId": "0c9f4e2a-7d61-4b83-9e5a-3f8b1c6d2e07",
  "events": [
    { "action": "status_change", "by": "owner@acme.com", "at": "2026-09-03T11:20:00Z", "fromStatus": "available", "toStatus": "maintenance", "notes": null, "source": "manual" },
    { "action": "unreserve", "by": "owner@acme.com", "at": "2026-09-02T10:05:00Z", "to": "Sam Rivera", "notes": "install rescheduled", "source": "manual" },
    { "action": "checkin", "by": "owner@acme.com", "at": "2026-09-02T09:00:00Z", "to": null, "notes": "returned in good condition", "source": "manual" },
    { "action": "reserve", "by": "owner@acme.com", "at": "2026-08-31T08:40:00Z", "to": "Sam Rivera", "notes": null, "source": "manual" },
    { "action": "checkout", "by": "owner@acme.com", "at": "2026-08-30T12:15:00Z", "to": "Jane Doe", "notes": null, "source": "manual" }
  ]
}

Calculators

Base URL https://api.bottlebluellc.com/v1

GETapi.bottlebluellc.com/calculate

Discovery: catalog of every finance / fitness / real-estate calculator + its parameters.

curl "https://api.bottlebluellc.com/v1/calculate" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/calculate",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/calculate", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/calculate", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "domains": {
    "finance": { "mortgage": { "description": "Monthly mortgage payment + total interest", "params": ["principal", "annual_rate_pct", "years"] }, "compound-interest": { "...": "..." } },
    "fitness": { "bmr": { "...": "..." } },
    "realestate": { "cap-rate": { "...": "..." } }
  },
  "enums": { "activity": ["active", "light", "moderate", "sedentary", "very_active"], "sex": ["female", "male"] }
}
GETapi.bottlebluellc.com/finance/calculate/mortgage?principal=300000&annual_rate_pct=6.5&years=30

Finance calculators (pure-compute): mortgage, compound-interest, loan-payment, roi, savings-goal, inflation-adjust. Use /finance/calculate/catalog to list all.

curl "https://api.bottlebluellc.com/v1/finance/calculate/mortgage?principal=300000&annual_rate_pct=6.5&years=30" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/finance/calculate/mortgage?principal=300000&annual_rate_pct=6.5&years=30",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/finance/calculate/mortgage?principal=300000&annual_rate_pct=6.5&years=30", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/finance/calculate/mortgage?principal=300000&annual_rate_pct=6.5&years=30", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "domain": "finance",
  "type": "mortgage",
  "inputs": {
    "principal": "300000",
    "annual_rate_pct": "6.5",
    "years": "30"
  },
  "result": {
    "monthly_payment": 1896.2,
    "number_of_payments": 360,
    "total_paid": 682633.47,
    "total_interest": 382633.47
  }
}

All six finance calculators, each as a full request/response example:

mortgageMonthly mortgage payment plus total interest over the loan.principal, annual_rate_pct, years

Monthly mortgage payment plus total interest over the loan.

curl "https://api.bottlebluellc.com/v1/finance/calculate/mortgage?principal=300000&annual_rate_pct=6.5&years=30" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/finance/calculate/mortgage?principal=300000&annual_rate_pct=6.5&years=30",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/finance/calculate/mortgage?principal=300000&annual_rate_pct=6.5&years=30", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/finance/calculate/mortgage?principal=300000&annual_rate_pct=6.5&years=30", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "domain": "finance",
  "type": "mortgage",
  "inputs": {
    "principal": "300000",
    "annual_rate_pct": "6.5",
    "years": "30"
  },
  "result": {
    "monthly_payment": 1896.2,
    "number_of_payments": 360,
    "total_paid": 682633.47,
    "total_interest": 382633.47
  }
}
compound-interestFuture value with optional monthly contributions.principal, annual_rate_pct, years, compounds_per_year?, monthly_contribution?

Future value with optional monthly contributions.

curl "https://api.bottlebluellc.com/v1/finance/calculate/compound-interest?principal=10000&annual_rate_pct=7&years=20&compounds_per_year=12&monthly_contribution=200" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/finance/calculate/compound-interest?principal=10000&annual_rate_pct=7&years=20&compounds_per_year=12&monthly_contribution=200",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/finance/calculate/compound-interest?principal=10000&annual_rate_pct=7&years=20&compounds_per_year=12&monthly_contribution=200", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/finance/calculate/compound-interest?principal=10000&annual_rate_pct=7&years=20&compounds_per_year=12&monthly_contribution=200", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "domain": "finance",
  "type": "compound-interest",
  "inputs": {
    "principal": "10000",
    "annual_rate_pct": "7",
    "years": "20",
    "compounds_per_year": "12",
    "monthly_contribution": "200"
  },
  "result": {
    "future_value": 144572.72,
    "total_contributions": 58000.0,
    "total_interest": 86572.72
  }
}
loan-paymentAmortizing loan payment (same math as mortgage, generic wording).principal, annual_rate_pct, years

Amortizing loan payment (same math as mortgage, generic wording).

curl "https://api.bottlebluellc.com/v1/finance/calculate/loan-payment?principal=35000&annual_rate_pct=8&years=5" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/finance/calculate/loan-payment?principal=35000&annual_rate_pct=8&years=5",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/finance/calculate/loan-payment?principal=35000&annual_rate_pct=8&years=5", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/finance/calculate/loan-payment?principal=35000&annual_rate_pct=8&years=5", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "domain": "finance",
  "type": "loan-payment",
  "inputs": {
    "principal": "35000",
    "annual_rate_pct": "8",
    "years": "5"
  },
  "result": {
    "monthly_payment": 709.67,
    "number_of_payments": 60,
    "total_paid": 42580.43,
    "total_interest": 7580.43
  }
}
roiReturn on investment, plus annualized CAGR when years is given.initial_value, final_value, years?

Return on investment, plus annualized CAGR when years is given.

curl "https://api.bottlebluellc.com/v1/finance/calculate/roi?initial_value=15000&final_value=24000&years=4" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/finance/calculate/roi?initial_value=15000&final_value=24000&years=4",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/finance/calculate/roi?initial_value=15000&final_value=24000&years=4", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/finance/calculate/roi?initial_value=15000&final_value=24000&years=4", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "domain": "finance",
  "type": "roi",
  "inputs": {
    "initial_value": "15000",
    "final_value": "24000",
    "years": "4"
  },
  "result": {
    "roi_pct": 60.0,
    "net_gain": 9000.0,
    "annualized_return_pct": 12.47
  }
}
savings-goalMonthly contribution required to reach a savings goal.goal_amount, years, annual_rate_pct?, current_savings?

Monthly contribution required to reach a savings goal.

curl "https://api.bottlebluellc.com/v1/finance/calculate/savings-goal?goal_amount=20000&years=3&annual_rate_pct=4&current_savings=2000" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/finance/calculate/savings-goal?goal_amount=20000&years=3&annual_rate_pct=4&current_savings=2000",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/finance/calculate/savings-goal?goal_amount=20000&years=3&annual_rate_pct=4&current_savings=2000", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/finance/calculate/savings-goal?goal_amount=20000&years=3&annual_rate_pct=4&current_savings=2000", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "domain": "finance",
  "type": "savings-goal",
  "inputs": {
    "goal_amount": "20000",
    "years": "3",
    "annual_rate_pct": "4",
    "current_savings": "2000"
  },
  "result": {
    "monthly_contribution_required": 464.77,
    "months": 36,
    "projected_from_current_savings": 2254.54
  }
}
inflation-adjustFuture value needed to match today's buying power, and today's buying power of a future amount.amount, annual_inflation_pct, years

Future value needed to match today's buying power, and today's buying power of a future amount.

curl "https://api.bottlebluellc.com/v1/finance/calculate/inflation-adjust?amount=10000&annual_inflation_pct=3.2&years=10" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/finance/calculate/inflation-adjust?amount=10000&annual_inflation_pct=3.2&years=10",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/finance/calculate/inflation-adjust?amount=10000&annual_inflation_pct=3.2&years=10", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/finance/calculate/inflation-adjust?amount=10000&annual_inflation_pct=3.2&years=10", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "domain": "finance",
  "type": "inflation-adjust",
  "inputs": {
    "amount": "10000",
    "annual_inflation_pct": "3.2",
    "years": "10"
  },
  "result": {
    "future_value_needed_to_match": 13702.41,
    "todays_buying_power_of_amount_in_future": 7297.99
  }
}
GETapi.bottlebluellc.com/finance/calculate/catalog

Per-domain catalog: every finance calculator with its description and parameters.

The same pattern works for the other domains: /fitness/calculate/catalog and /realestate/calculate/catalog. Parameters suffixed ? are optional. For the cross-domain catalog plus parameter enums, use /calculate.

curl "https://api.bottlebluellc.com/v1/finance/calculate/catalog" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/finance/calculate/catalog",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/finance/calculate/catalog", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/finance/calculate/catalog", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "domain": "finance",
  "calculators": {
    "mortgage": {
      "description": "Monthly mortgage payment + total interest",
      "params": ["principal", "annual_rate_pct", "years"]
    },
    "compound-interest": {
      "description": "Future value with optional monthly contributions",
      "params": ["principal", "annual_rate_pct", "years", "compounds_per_year?", "monthly_contribution?"]
    },
    "loan-payment": {
      "description": "Amortizing loan payment",
      "params": ["principal", "annual_rate_pct", "years"]
    },
    "roi": {
      "description": "Return on investment (+ annualized CAGR if years given)",
      "params": ["initial_value", "final_value", "years?"]
    },
    "savings-goal": {
      "description": "Monthly contribution to reach a savings goal",
      "params": ["goal_amount", "years", "annual_rate_pct?", "current_savings?"]
    },
    "inflation-adjust": {
      "description": "Inflation-adjusted value over time",
      "params": ["amount", "annual_inflation_pct", "years"]
    }
  }
}
GETapi.bottlebluellc.com/fitness/calculate/tdee?weight_kg=80&height_cm=180&age=30&sex=male&activity=moderate

Fitness calculators (pure-compute): bmi, bmr, tdee, body-fat, macros, one-rep-max, calories-burned, ideal-weight. Use /fitness/calculate/catalog to list all.

curl "https://api.bottlebluellc.com/v1/fitness/calculate/tdee?weight_kg=80&height_cm=180&age=30&sex=male&activity=moderate" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/fitness/calculate/tdee?weight_kg=80&height_cm=180&age=30&sex=male&activity=moderate",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/fitness/calculate/tdee?weight_kg=80&height_cm=180&age=30&sex=male&activity=moderate", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/fitness/calculate/tdee?weight_kg=80&height_cm=180&age=30&sex=male&activity=moderate", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "domain": "fitness",
  "type": "tdee",
  "inputs": {
    "weight_kg": "80",
    "height_cm": "180",
    "age": "30",
    "sex": "male",
    "activity": "moderate"
  },
  "result": {
    "bmr_kcal_per_day": 1780.0,
    "tdee_kcal_per_day": 2759.0,
    "activity_multiplier": 1.55
  }
}

All eight fitness calculators, each as a full request/response example:

bmiBody mass index and weight category.weight_kg, height_cm

Body mass index and weight category.

curl "https://api.bottlebluellc.com/v1/fitness/calculate/bmi?weight_kg=80&height_cm=180" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/fitness/calculate/bmi?weight_kg=80&height_cm=180",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/fitness/calculate/bmi?weight_kg=80&height_cm=180", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/fitness/calculate/bmi?weight_kg=80&height_cm=180", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "domain": "fitness",
  "type": "bmi",
  "inputs": {
    "weight_kg": "80",
    "height_cm": "180"
  },
  "result": {
    "bmi": 24.7,
    "category": "normal"
  }
}
bmrBasal metabolic rate (Mifflin-St Jeor).weight_kg, height_cm, age, sex

Basal metabolic rate (Mifflin-St Jeor).

curl "https://api.bottlebluellc.com/v1/fitness/calculate/bmr?weight_kg=80&height_cm=180&age=30&sex=male" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/fitness/calculate/bmr?weight_kg=80&height_cm=180&age=30&sex=male",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/fitness/calculate/bmr?weight_kg=80&height_cm=180&age=30&sex=male", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/fitness/calculate/bmr?weight_kg=80&height_cm=180&age=30&sex=male", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "domain": "fitness",
  "type": "bmr",
  "inputs": {
    "weight_kg": "80",
    "height_cm": "180",
    "age": "30",
    "sex": "male"
  },
  "result": {
    "bmr_kcal_per_day": 1780.0
  }
}
tdeeTotal daily energy expenditure.weight_kg, height_cm, age, sex, activity

Total daily energy expenditure.

curl "https://api.bottlebluellc.com/v1/fitness/calculate/tdee?weight_kg=80&height_cm=180&age=30&sex=male&activity=moderate" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/fitness/calculate/tdee?weight_kg=80&height_cm=180&age=30&sex=male&activity=moderate",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/fitness/calculate/tdee?weight_kg=80&height_cm=180&age=30&sex=male&activity=moderate", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/fitness/calculate/tdee?weight_kg=80&height_cm=180&age=30&sex=male&activity=moderate", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "domain": "fitness",
  "type": "tdee",
  "inputs": {
    "weight_kg": "80",
    "height_cm": "180",
    "age": "30",
    "sex": "male",
    "activity": "moderate"
  },
  "result": {
    "bmr_kcal_per_day": 1780.0,
    "tdee_kcal_per_day": 2759.0,
    "activity_multiplier": 1.55
  }
}
body-fatBody-fat percentage, US Navy circumference method.sex, height_cm, neck_cm, waist_cm, hip_cm (female only)

Body-fat percentage, US Navy circumference method.

curl "https://api.bottlebluellc.com/v1/fitness/calculate/body-fat?sex=male&height_cm=180&neck_cm=38&waist_cm=86" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/fitness/calculate/body-fat?sex=male&height_cm=180&neck_cm=38&waist_cm=86",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/fitness/calculate/body-fat?sex=male&height_cm=180&neck_cm=38&waist_cm=86", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/fitness/calculate/body-fat?sex=male&height_cm=180&neck_cm=38&waist_cm=86", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "domain": "fitness",
  "type": "body-fat",
  "inputs": {
    "sex": "male",
    "height_cm": "180",
    "neck_cm": "38",
    "waist_cm": "86"
  },
  "result": {
    "body_fat_pct": 16.9,
    "method": "US Navy circumference"
  }
}
macrosDaily calorie and macro targets for a goal.weight_kg, height_cm, age, sex, activity, goal?

Daily calorie and macro targets for a goal.

curl "https://api.bottlebluellc.com/v1/fitness/calculate/macros?weight_kg=80&height_cm=180&age=30&sex=male&activity=moderate&goal=cut" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/fitness/calculate/macros?weight_kg=80&height_cm=180&age=30&sex=male&activity=moderate&goal=cut",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/fitness/calculate/macros?weight_kg=80&height_cm=180&age=30&sex=male&activity=moderate&goal=cut", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/fitness/calculate/macros?weight_kg=80&height_cm=180&age=30&sex=male&activity=moderate&goal=cut", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "domain": "fitness",
  "type": "macros",
  "inputs": {
    "weight_kg": "80",
    "height_cm": "180",
    "age": "30",
    "sex": "male",
    "activity": "moderate",
    "goal": "cut"
  },
  "result": {
    "target_calories": 2207.0,
    "protein_g": 160.0,
    "fat_g": 61.0,
    "carbs_g": 254.0,
    "goal": "cut"
  }
}
one-rep-maxEstimated one-rep max (Epley and Brzycki).weight, reps

Estimated one-rep max (Epley and Brzycki).

curl "https://api.bottlebluellc.com/v1/fitness/calculate/one-rep-max?weight=185&reps=5" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/fitness/calculate/one-rep-max?weight=185&reps=5",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/fitness/calculate/one-rep-max?weight=185&reps=5", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/fitness/calculate/one-rep-max?weight=185&reps=5", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "domain": "fitness",
  "type": "one-rep-max",
  "inputs": {
    "weight": "185",
    "reps": "5"
  },
  "result": {
    "one_rep_max_epley": 215.83,
    "one_rep_max_brzycki": 208.12,
    "estimate": 211.98
  }
}
calories-burnedCalories burned for an activity (MET-based).weight_kg, minutes, activity

Calories burned for an activity (MET-based).

curl "https://api.bottlebluellc.com/v1/fitness/calculate/calories-burned?weight_kg=80&minutes=45&activity=running" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/fitness/calculate/calories-burned?weight_kg=80&minutes=45&activity=running",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/fitness/calculate/calories-burned?weight_kg=80&minutes=45&activity=running", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/fitness/calculate/calories-burned?weight_kg=80&minutes=45&activity=running", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "domain": "fitness",
  "type": "calories-burned",
  "inputs": {
    "weight_kg": "80",
    "minutes": "45",
    "activity": "running"
  },
  "result": {
    "calories_burned": 588.0,
    "met": 9.8,
    "activity": "running"
  }
}
ideal-weightHealthy weight range for a height (BMI 18.5 to 24.9).height_cm

Healthy weight range for a height (BMI 18.5 to 24.9).

curl "https://api.bottlebluellc.com/v1/fitness/calculate/ideal-weight?height_cm=180" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/fitness/calculate/ideal-weight?height_cm=180",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/fitness/calculate/ideal-weight?height_cm=180", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/fitness/calculate/ideal-weight?height_cm=180", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "domain": "fitness",
  "type": "ideal-weight",
  "inputs": {
    "height_cm": "180"
  },
  "result": {
    "healthy_weight_kg_min": 59.9,
    "healthy_weight_kg_max": 80.7,
    "basis": "BMI 18.5-24.9"
  }
}
GETapi.bottlebluellc.com/realestate/calculate/cap-rate?property_value=400000&noi=24000

Real-estate calculators (pure-compute): cap-rate, cash-on-cash, noi, gross-rent-multiplier, dscr, affordability, price-per-sqft, rent-vs-buy. Use /realestate/calculate/catalog to list all.

curl "https://api.bottlebluellc.com/v1/realestate/calculate/cap-rate?property_value=400000&noi=24000" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/realestate/calculate/cap-rate?property_value=400000&noi=24000",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/realestate/calculate/cap-rate?property_value=400000&noi=24000", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/realestate/calculate/cap-rate?property_value=400000&noi=24000", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "domain": "realestate",
  "type": "cap-rate",
  "inputs": {
    "property_value": "400000",
    "noi": "24000"
  },
  "result": { "cap_rate_pct": 6.0, "noi": 24000.0 }
}

All eight real-estate calculators, each as a full request/response example:

cap-rateCapitalization rate.property_value, noi (or gross_income + operating_expenses)

Capitalization rate.

curl "https://api.bottlebluellc.com/v1/realestate/calculate/cap-rate?property_value=400000&noi=24000" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/realestate/calculate/cap-rate?property_value=400000&noi=24000",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/realestate/calculate/cap-rate?property_value=400000&noi=24000", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/realestate/calculate/cap-rate?property_value=400000&noi=24000", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "domain": "realestate",
  "type": "cap-rate",
  "inputs": {
    "property_value": "400000",
    "noi": "24000"
  },
  "result": {
    "cap_rate_pct": 6.0,
    "noi": 24000.0
  }
}
cash-on-cashCash-on-cash return.annual_cash_flow, cash_invested

Cash-on-cash return.

curl "https://api.bottlebluellc.com/v1/realestate/calculate/cash-on-cash?annual_cash_flow=9600&cash_invested=80000" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/realestate/calculate/cash-on-cash?annual_cash_flow=9600&cash_invested=80000",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/realestate/calculate/cash-on-cash?annual_cash_flow=9600&cash_invested=80000", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/realestate/calculate/cash-on-cash?annual_cash_flow=9600&cash_invested=80000", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "domain": "realestate",
  "type": "cash-on-cash",
  "inputs": {
    "annual_cash_flow": "9600",
    "cash_invested": "80000"
  },
  "result": {
    "cash_on_cash_pct": 12.0
  }
}
noiNet operating income and expense ratio.gross_income, operating_expenses

Net operating income and expense ratio.

curl "https://api.bottlebluellc.com/v1/realestate/calculate/noi?gross_income=60000&operating_expenses=22000" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/realestate/calculate/noi?gross_income=60000&operating_expenses=22000",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/realestate/calculate/noi?gross_income=60000&operating_expenses=22000", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/realestate/calculate/noi?gross_income=60000&operating_expenses=22000", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "domain": "realestate",
  "type": "noi",
  "inputs": {
    "gross_income": "60000",
    "operating_expenses": "22000"
  },
  "result": {
    "noi": 38000.0,
    "expense_ratio_pct": 36.67
  }
}
gross-rent-multiplierGross rent multiplier.property_price, annual_gross_rent

Gross rent multiplier.

curl "https://api.bottlebluellc.com/v1/realestate/calculate/gross-rent-multiplier?property_price=350000&annual_gross_rent=30000" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/realestate/calculate/gross-rent-multiplier?property_price=350000&annual_gross_rent=30000",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/realestate/calculate/gross-rent-multiplier?property_price=350000&annual_gross_rent=30000", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/realestate/calculate/gross-rent-multiplier?property_price=350000&annual_gross_rent=30000", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "domain": "realestate",
  "type": "gross-rent-multiplier",
  "inputs": {
    "property_price": "350000",
    "annual_gross_rent": "30000"
  },
  "result": {
    "gross_rent_multiplier": 11.67
  }
}
dscrDebt service coverage ratio.annual_debt_service, noi (or gross_income + operating_expenses)

Debt service coverage ratio.

curl "https://api.bottlebluellc.com/v1/realestate/calculate/dscr?gross_income=60000&operating_expenses=22000&annual_debt_service=28000" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/realestate/calculate/dscr?gross_income=60000&operating_expenses=22000&annual_debt_service=28000",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/realestate/calculate/dscr?gross_income=60000&operating_expenses=22000&annual_debt_service=28000", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/realestate/calculate/dscr?gross_income=60000&operating_expenses=22000&annual_debt_service=28000", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "domain": "realestate",
  "type": "dscr",
  "inputs": {
    "gross_income": "60000",
    "operating_expenses": "22000",
    "annual_debt_service": "28000"
  },
  "result": {
    "dscr": 1.36,
    "lender_view": "pass (>=1.25)"
  }
}
affordabilityMax affordable home price using the 28/36 rule.annual_income, annual_rate_pct, monthly_debt?, down_payment?, years?

Max affordable home price using the 28/36 rule.

curl "https://api.bottlebluellc.com/v1/realestate/calculate/affordability?annual_income=95000&annual_rate_pct=6.5&monthly_debt=300&down_payment=40000&years=30" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/realestate/calculate/affordability?annual_income=95000&annual_rate_pct=6.5&monthly_debt=300&down_payment=40000&years=30",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/realestate/calculate/affordability?annual_income=95000&annual_rate_pct=6.5&monthly_debt=300&down_payment=40000&years=30", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/realestate/calculate/affordability?annual_income=95000&annual_rate_pct=6.5&monthly_debt=300&down_payment=40000&years=30", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "domain": "realestate",
  "type": "affordability",
  "inputs": {
    "annual_income": "95000",
    "annual_rate_pct": "6.5",
    "monthly_debt": "300",
    "down_payment": "40000",
    "years": "30"
  },
  "result": {
    "max_monthly_housing_payment": 2216.67,
    "max_loan_amount": 350700.65,
    "max_home_price": 390700.65,
    "rule": "28/36 front/back-end ratios"
  }
}
price-per-sqftPrice per square foot.price, square_feet

Price per square foot.

curl "https://api.bottlebluellc.com/v1/realestate/calculate/price-per-sqft?price=425000&square_feet=1850" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/realestate/calculate/price-per-sqft?price=425000&square_feet=1850",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/realestate/calculate/price-per-sqft?price=425000&square_feet=1850", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/realestate/calculate/price-per-sqft?price=425000&square_feet=1850", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "domain": "realestate",
  "type": "price-per-sqft",
  "inputs": {
    "price": "425000",
    "square_feet": "1850"
  },
  "result": {
    "price_per_sqft": 229.73
  }
}
rent-vs-buyMonthly cost comparison of owning versus renting.home_price, annual_rate_pct, monthly_rent, down_payment?, years?

Monthly cost comparison of owning versus renting.

curl "https://api.bottlebluellc.com/v1/realestate/calculate/rent-vs-buy?home_price=400000&down_payment=40000&annual_rate_pct=6.5&years=30&monthly_rent=2200" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/realestate/calculate/rent-vs-buy?home_price=400000&down_payment=40000&annual_rate_pct=6.5&years=30&monthly_rent=2200",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/realestate/calculate/rent-vs-buy?home_price=400000&down_payment=40000&annual_rate_pct=6.5&years=30&monthly_rent=2200", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/realestate/calculate/rent-vs-buy?home_price=400000&down_payment=40000&annual_rate_pct=6.5&years=30&monthly_rent=2200", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "domain": "realestate",
  "type": "rent-vs-buy",
  "inputs": {
    "home_price": "400000",
    "down_payment": "40000",
    "annual_rate_pct": "6.5",
    "years": "30",
    "monthly_rent": "2200"
  },
  "result": {
    "estimated_monthly_cost_to_own": 2775.44,
    "monthly_rent": 2200.0,
    "monthly_difference_own_minus_rent": 575.44,
    "note": "owning cost approximates mortgage + 1.5%/yr of price for tax/insurance/maintenance; excludes equity build & appreciation"
  }
}

Market analysis

Base URL https://api.bottlebluellc.com/v1

GETapi.bottlebluellc.com/market/econ-summary

One-call macro dashboard: latest interest rates, economic indicators, national debt, and market sentiment.

curl "https://api.bottlebluellc.com/v1/market/econ-summary" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/market/econ-summary",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/market/econ-summary", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/market/econ-summary", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "summary": {
    "rates": { "as_of": "2026-07-11", "count": 20, "items": [ { "id": "DFF", "label": "Federal Funds Rate", "value": "4.33", "unit": "%" } ] },
    "economy": { "as_of": "2026-07-11", "items": [ { "id": "CPIAUCSL", "label": "CPI (All Items)", "value": "333.979", "unit": "index" } ] },
    "fiscal": { "items": [ { "id": "TOTAL", "label": "US Total Public Debt Outstanding" } ] },
    "sentiment": { "items": [ { "id": "CRYPTO_FEAR_GREED", "value": 26, "classification": "Fear" } ] }
  },
  "generated_at": "2026-07-11T20:00:00Z"
}
GETapi.bottlebluellc.com/market/technicals/^GSPC

Technical indicators (SMA/EMA/RSI/MACD/Bollinger/ROC + 52-week range + signals) for a ticker. Tracked instruments are everything the daily pipeline stores a close price for: every instrument in the indices, global-indices, sectors, crypto, commodities, fx, and bonds market categories (see /market/{category} for the current instrument list in each). Those resolve against our own history; any other symbol resolves through free upstreams.

curl "https://api.bottlebluellc.com/v1/market/technicals/^GSPC" \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/market/technicals/^GSPC",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/market/technicals/^GSPC", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/market/technicals/^GSPC", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "ticker": "^GSPC",
  "points_used": 273,
  "indicators": {
    "latest_close": 7575.39, "sma_20": 7480.1, "sma_50": 7301.5, "sma_200": 6650.2,
    "rsi_14": 60.02, "macd": 61.2, "macd_signal": 55.4, "macd_histogram": 5.8,
    "bollinger_upper": 7702.0, "bollinger_lower": 7258.2, "high_52w": 7620.0, "low_52w": 5100.0
  },
  "signals": { "trend": "bullish", "rsi_zone": "neutral", "macd": "bullish" }
}

Organizations (shared, multi-user access)

An organization is a shared team: several different Bottle Blue accounts can jointly own the same data, with one of three roles - owner, admin, or member. Every other endpoint in this API (including a plain /assets call with no orgId) stays private to one account; organizations are the one way to share data across accounts. Any building block that supports shared ownership accepts an optional orgId to opt into it - today that is Assets.

How this differs from Roles & permissions (RBAC)

Roles & permissions is a separate, operator-only tool Bottle Blue LLC uses internally: gated by the x-admin-key header, with one flat role and member list per entire product platform. Organizations are the customer-facing opposite - any authenticated account can create one and invite other accounts into it with a normal API key or signed-in session, and every organization is its own isolated team. Two different organizations never see each other's members or data.

Roles

  • owner - full control, including deleting the organization and managing any member, including other owners. Every organization always has at least one owner; removing or demoting the last owner is rejected with 409 LAST_OWNER.
  • admin - can invite and remove members and change roles, but cannot touch an owner and cannot delete the organization.
  • member - can read and write the organization's shared data (for example, checking an asset in or out), but cannot manage membership.

The platform field on an organization

An organization's platform works exactly like every other building block's: omit it for the default namespace, send your own project name, or send a registered Bottle Blue product slug such as bb_trackless. The bb_ prefix is reserved - you can never claim a bb_ name for your own project, so a project can never collide with a current or future Bottle Blue product. See Data platforms for the full rule set.

Deleting an organization that still holds data (for example, assets created under it) is refused by default with 409 ORG_HAS_DATA. Pass ?force=true to delete the organization AND everything stored under it in one call.

Organizations

Base URL https://api.bottlebluellc.com/v1

POSTapi.bottlebluellc.com/organizations

Create an organization. The caller becomes its owner.

curl https://api.bottlebluellc.com/v1/organizations \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Acme Corp", "platform": "bb_trackless"}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/organizations",
    headers={"x-api-key": API_KEY},
    json={'name': 'Acme Corp', 'platform': 'bb_trackless'},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/organizations", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({ name: "Acme Corp", platform: "bb_trackless" })
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/organizations", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({ name: "Acme Corp", platform: "bb_trackless" })
});
const data = await resp.json();
201 Response
{
  "orgId": "3f1c9e2a7d614b839e5a3f8b1c6d2e07",
  "name": "Acme Corp",
  "role": "owner",
  "platform": "bb_trackless",
  "createdAt": "2026-08-30T12:00:00Z"
}
GETapi.bottlebluellc.com/organizations

List the organizations you belong to.

ParameterInDescription
platformqueryOptional. Scope to one platform, or * for every platform you belong to an organization in. Omit for the default namespace.
curl https://api.bottlebluellc.com/v1/organizations?platform=bb_trackless \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/organizations",
    headers={"x-api-key": API_KEY},
    params={"platform": "bb_trackless"},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/organizations?platform=bb_trackless", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/organizations?platform=bb_trackless", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "organizations": [
    {
      "orgId": "3f1c9e2a7d614b839e5a3f8b1c6d2e07",
      "name": "Acme Corp",
      "role": "owner",
      "joinedAt": "2026-08-30T12:00:00Z"
    }
  ]
}
GETapi.bottlebluellc.com/organizations/{orgId}

Get a single organization. Requires membership.

ParameterInDescription
orgIdpathrequired Organization id.
curl https://api.bottlebluellc.com/v1/organizations/3f1c9e2a7d614b839e5a3f8b1c6d2e07 \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/organizations/3f1c9e2a7d614b839e5a3f8b1c6d2e07",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/organizations/3f1c9e2a7d614b839e5a3f8b1c6d2e07", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/organizations/3f1c9e2a7d614b839e5a3f8b1c6d2e07", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "orgId": "3f1c9e2a7d614b839e5a3f8b1c6d2e07",
  "name": "Acme Corp",
  "platform": "bb_trackless",
  "ownerEmail": "owner@acme.com",
  "createdAt": "2026-08-30T12:00:00Z",
  "updatedAt": "2026-08-30T12:00:00Z",
  "role": "owner"
}
403 Response
{
  "error": "You are not a member of this organization",
  "code": "NOT_ORG_MEMBER"
}
PATCHapi.bottlebluellc.com/organizations/{orgId}

Rename an organization. Requires the admin or owner role.

ParameterInDescription
orgIdpathrequired Organization id.
curl https://api.bottlebluellc.com/v1/organizations/3f1c9e2a7d614b839e5a3f8b1c6d2e07 \
  -X PATCH \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Acme Corporation"}'
import requests

resp = requests.patch(
    "https://api.bottlebluellc.com/v1/organizations/3f1c9e2a7d614b839e5a3f8b1c6d2e07",
    headers={"x-api-key": API_KEY},
    json={'name': 'Acme Corporation'},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/organizations/3f1c9e2a7d614b839e5a3f8b1c6d2e07", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({ name: "Acme Corporation" })
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/organizations/3f1c9e2a7d614b839e5a3f8b1c6d2e07", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({ name: "Acme Corporation" })
});
const data = await resp.json();
200 Response
{
  "orgId": "3f1c9e2a7d614b839e5a3f8b1c6d2e07",
  "name": "Acme Corporation",
  "updatedAt": "2026-08-30T12:05:00Z"
}
DELETEapi.bottlebluellc.com/organizations/{orgId}

Delete an organization. Requires the owner role.

ParameterInDescription
orgIdpathrequired Organization id.
forcequeryOptional, default false. If the organization still holds data (for example, assets), the delete is refused with 409 ORG_HAS_DATA unless force=true is passed, in which case that data is deleted too.
curl https://api.bottlebluellc.com/v1/organizations/3f1c9e2a7d614b839e5a3f8b1c6d2e07 \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"

# Also delete every asset stored under this organization
curl "https://api.bottlebluellc.com/v1/organizations/3f1c9e2a7d614b839e5a3f8b1c6d2e07?force=true" \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.delete(
    "https://api.bottlebluellc.com/v1/organizations/3f1c9e2a7d614b839e5a3f8b1c6d2e07",
    headers={"x-api-key": API_KEY},
    # params={"force": "true"},  # also delete the organization's data
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/organizations/3f1c9e2a7d614b839e5a3f8b1c6d2e07", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/organizations/3f1c9e2a7d614b839e5a3f8b1c6d2e07", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "orgId": "3f1c9e2a7d614b839e5a3f8b1c6d2e07",
  "deleted": true,
  "dataRowsDeleted": 0
}
409 Response
{
  "error": "Organization still has 12 data row(s) (e.g. assets); pass ?force=true to delete the organization AND its data",
  "code": "ORG_HAS_DATA",
  "dataRowCount": 12
}
GETapi.bottlebluellc.com/organizations/{orgId}/members

List an organization's members. Requires membership.

ParameterInDescription
orgIdpathrequired Organization id.
curl https://api.bottlebluellc.com/v1/organizations/3f1c9e2a7d614b839e5a3f8b1c6d2e07/members \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.bottlebluellc.com/v1/organizations/3f1c9e2a7d614b839e5a3f8b1c6d2e07/members",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/organizations/3f1c9e2a7d614b839e5a3f8b1c6d2e07/members", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/organizations/3f1c9e2a7d614b839e5a3f8b1c6d2e07/members", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "orgId": "3f1c9e2a7d614b839e5a3f8b1c6d2e07",
  "members": [
    { "email": "owner@acme.com", "role": "owner", "joinedAt": "2026-08-30T12:00:00Z", "invitedBy": "owner@acme.com" },
    { "email": "jane@acme.com", "role": "member", "joinedAt": "2026-08-30T12:10:00Z", "invitedBy": "owner@acme.com" }
  ]
}
POSTapi.bottlebluellc.com/organizations/{orgId}/members

Add a member to an organization. Requires the admin or owner role. Only an owner can grant the owner role.

ParameterInDescription
orgIdpathrequired Organization id.
curl https://api.bottlebluellc.com/v1/organizations/3f1c9e2a7d614b839e5a3f8b1c6d2e07/members \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email": "jane@acme.com", "role": "member"}'
import requests

resp = requests.post(
    "https://api.bottlebluellc.com/v1/organizations/3f1c9e2a7d614b839e5a3f8b1c6d2e07/members",
    headers={"x-api-key": API_KEY},
    json={'email': 'jane@acme.com', 'role': 'member'},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/organizations/3f1c9e2a7d614b839e5a3f8b1c6d2e07/members", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({ email: "jane@acme.com", role: "member" })
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/organizations/3f1c9e2a7d614b839e5a3f8b1c6d2e07/members", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({ email: "jane@acme.com", role: "member" })
});
const data = await resp.json();
201 Response
{
  "orgId": "3f1c9e2a7d614b839e5a3f8b1c6d2e07",
  "email": "jane@acme.com",
  "role": "member",
  "joinedAt": "2026-08-30T12:10:00Z"
}
409 Response
{
  "error": "This account is already a member of the organization",
  "code": "MEMBER_EXISTS"
}
PATCHapi.bottlebluellc.com/organizations/{orgId}/members

Change a member's role. Requires the admin or owner role. Only an owner can change an owner's role or grant it, and the last owner can never be demoted.

ParameterInDescription
orgIdpathrequired Organization id.
curl https://api.bottlebluellc.com/v1/organizations/3f1c9e2a7d614b839e5a3f8b1c6d2e07/members \
  -X PATCH \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email": "jane@acme.com", "role": "admin"}'
import requests

resp = requests.patch(
    "https://api.bottlebluellc.com/v1/organizations/3f1c9e2a7d614b839e5a3f8b1c6d2e07/members",
    headers={"x-api-key": API_KEY},
    json={'email': 'jane@acme.com', 'role': 'admin'},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/organizations/3f1c9e2a7d614b839e5a3f8b1c6d2e07/members", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({ email: "jane@acme.com", role: "admin" })
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/organizations/3f1c9e2a7d614b839e5a3f8b1c6d2e07/members", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({ email: "jane@acme.com", role: "admin" })
});
const data = await resp.json();
200 Response
{
  "orgId": "3f1c9e2a7d614b839e5a3f8b1c6d2e07",
  "email": "jane@acme.com",
  "role": "admin"
}
409 Response
{
  "error": "Cannot remove the last owner; promote another member to owner first",
  "code": "LAST_OWNER"
}
DELETEapi.bottlebluellc.com/organizations/{orgId}/members

Remove a member from an organization, or leave it yourself. Removing someone else requires the admin or owner role; leaving is always allowed for your own account, subject to the last-owner rule.

ParameterInDescription
orgIdpathrequired Organization id.
curl https://api.bottlebluellc.com/v1/organizations/3f1c9e2a7d614b839e5a3f8b1c6d2e07/members \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email": "jane@acme.com"}'
import requests

resp = requests.delete(
    "https://api.bottlebluellc.com/v1/organizations/3f1c9e2a7d614b839e5a3f8b1c6d2e07/members",
    headers={"x-api-key": API_KEY},
    json={'email': 'jane@acme.com'},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.bottlebluellc.com/v1/organizations/3f1c9e2a7d614b839e5a3f8b1c6d2e07/members", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({ email: "jane@acme.com" })
});
const data = await resp.json();
const resp = await fetch("https://api.bottlebluellc.com/v1/organizations/3f1c9e2a7d614b839e5a3f8b1c6d2e07/members", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({ email: "jane@acme.com" })
});
const data = await resp.json();
200 Response
{
  "orgId": "3f1c9e2a7d614b839e5a3f8b1c6d2e07",
  "email": "jane@acme.com",
  "removed": true
}

Weather

Base URL https://api.aquabotany.com/v1

GETapi.aquabotany.com/weather

Current conditions plus a 48-hour and 8-day forecast for a coordinate. Units are metric: °C, m/s, hPa, metres, and epoch-second timestamps. The stats block adds derived daylight figures. Sourced from Open-Meteo since 2026-08-02 (previously OpenWeather); the field layout is unchanged. Government weather warnings are not included here. Use /weather/alerts, which serves them from the National Weather Service.

ParameterInDescription
latqueryrequired Latitude.
lonqueryrequired Longitude.
curl https://api.aquabotany.com/v1/weather?lat=44.98&lon=-93.26 \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/weather?lat=44.98&lon=-93.26",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/weather?lat=44.98&lon=-93.26", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/weather?lat=44.98&lon=-93.26", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "lat": 44.98,
  "lon": -93.26,
  "timezone": "America/Chicago",
  "timezone_offset": -18000,
  "current": {
    "dt": 1751897400,
    "sunrise": 1751885400,
    "sunset": 1751939700,
    "temp": 22.4,
    "feels_like": 22.0,
    "pressure": 1013.2,
    "humidity": 58,
    "dew_point": 13.9,
    "uvi": 5.0,
    "clouds": 40,
    "visibility": 24000,
    "wind_speed": 4.1,
    "wind_deg": 200,
    "wind_gust": 8.2,
    "weather": [
      { "id": 802, "main": "Clouds", "description": "partly cloudy", "icon": "03d" }
    ]
  },
  "hourly": [ { "dt": 1751897400, "temp": 22.4, "pop": 0.4, "weather": [] } ],
  "daily":  [ { "dt": 1751864400, "temp": { "min": 12.0, "max": 24.0 }, "pop": 0.8, "weather": [] } ],
  "stats": {
    "current_time_utc": "2026-07-07 14:30:00",
    "sunrise_time_utc": "2026-07-07 11:10:00",
    "sunset_time_utc": "2026-07-08 02:15:00",
    "total_daylight_hours": 15.08,
    "daylight_remaining_seconds": 42300,
    "night_duration_seconds": 32100,
    "night_remaining_seconds": 0
  }
}
GETapi.aquabotany.com/weather/alerts

Active NWS watches, warnings, and advisories covering a point.

ParameterInDescription
latqueryrequired Latitude.
lonqueryrequired Longitude.
curl https://api.aquabotany.com/v1/weather/alerts?lat=44.98&lon=-93.26 \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/weather/alerts?lat=44.98&lon=-93.26",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/weather/alerts?lat=44.98&lon=-93.26", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/weather/alerts?lat=44.98&lon=-93.26", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "location": {
    "lat": 44.98,
    "lon": -93.26
  },
  "count": 1,
  "alerts": [
    {
      "id": "urn:oid:2.49.0.1.840.0.7f3a9c1e5d2b4a6f8c0e1d3b5a7f9c2e4d6b8a0f.001.1",
      "event": "Heat Advisory",
      "severity": "Moderate",
      "urgency": "Expected",
      "certainty": "Likely",
      "headline": "Heat Advisory issued July 8 at 3:12AM CDT until July 9 at 8:00PM CDT by NWS Twin Cities/Chanhassen MN",
      "area": "Hennepin; Ramsey; Anoka",
      "onset": "2026-07-08T12:00:00-05:00",
      "expires": "2026-07-09T20:00:00-05:00",
      "description": "* WHAT...Heat index values up to 102 expected.\n\n* WHERE...The Twin Cities metro area.\n\n* WHEN...From noon Wednesday to 8 PM CDT Thursday.\n\n* IMPACTS...Hot temperatures and high humidity may cause heat illnesses to occur.",
      "instruction": "Drink plenty of fluids, stay in an air-conditioned room, stay out of the sun, and check up on relatives and neighbors."
    }
  ]
}
GETapi.aquabotany.com/weather/forecast

Detailed multi-day (or hourly) NWS forecast for a coordinate.

ParameterInDescription
latqueryrequired Latitude.
lonqueryrequired Longitude.
hourlyqueryoptional 'true' for the hourly forecast.
curl https://api.aquabotany.com/v1/weather/forecast?lat=44.98&lon=-93.26 \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/weather/forecast?lat=44.98&lon=-93.26",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/weather/forecast?lat=44.98&lon=-93.26", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/weather/forecast?lat=44.98&lon=-93.26", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "location": {
    "lat": 44.98,
    "lon": -93.26,
    "city": "Minneapolis",
    "state": "MN"
  },
  "office": "MPX",
  "type": "daily",
  "periods": [
    {
      "name": "Today",
      "start": "2026-07-08T06:00:00-05:00",
      "end": "2026-07-08T18:00:00-05:00",
      "is_daytime": true,
      "temperature": 91,
      "temperature_unit": "F",
      "wind_speed": "10 to 15 mph",
      "wind_direction": "SW",
      "short_forecast": "Mostly Sunny",
      "detailed_forecast": "Mostly sunny, with a high near 91. Heat index values as high as 100. Southwest wind 10 to 15 mph, with gusts as high as 25 mph.",
      "precip_probability": null
    },
    {
      "name": "Tonight",
      "start": "2026-07-08T18:00:00-05:00",
      "end": "2026-07-09T06:00:00-05:00",
      "is_daytime": false,
      "temperature": 72,
      "temperature_unit": "F",
      "wind_speed": "5 to 10 mph",
      "wind_direction": "S",
      "short_forecast": "Chance Showers And Thunderstorms",
      "detailed_forecast": "A chance of showers and thunderstorms after 1am. Mostly cloudy, with a low around 72. Chance of precipitation is 30%.",
      "precip_probability": 30
    }
  ]
}
GETapi.aquabotany.com/weather/history

Daily historical weather over a date range (ERA5 reanalysis).

ParameterInDescription
latqueryrequired Latitude.
lonqueryrequired Longitude.
start_datequeryrequired YYYY-MM-DD.
end_datequeryrequired YYYY-MM-DD (span <= 366 days).
curl https://api.aquabotany.com/v1/weather/history?lat=44.98&lon=-93.26&start_date=2026-06-01&end_date=2026-06-07 \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/weather/history?lat=44.98&lon=-93.26&start_date=2026-06-01&end_date=2026-06-07",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/weather/history?lat=44.98&lon=-93.26&start_date=2026-06-01&end_date=2026-06-07", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/weather/history?lat=44.98&lon=-93.26&start_date=2026-06-01&end_date=2026-06-07", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "location": {
    "lat": 44.98,
    "lon": -93.26
  },
  "start_date": "2026-06-01",
  "end_date": "2026-06-07",
  "units": {
    "time": "iso8601",
    "temperature_2m_max": "°C",
    "temperature_2m_min": "°C",
    "temperature_2m_mean": "°C",
    "precipitation_sum": "mm",
    "rain_sum": "mm",
    "windspeed_10m_max": "km/h"
  },
  "days": [
    {
      "date": "2026-06-01",
      "temperature_2m_max": 26.4,
      "temperature_2m_min": 14.2,
      "temperature_2m_mean": 20.1,
      "precipitation_sum": 0.0,
      "rain_sum": 0.0,
      "windspeed_10m_max": 18.7
    },
    {
      "date": "2026-06-02",
      "temperature_2m_max": 28.1,
      "temperature_2m_min": 15.8,
      "temperature_2m_mean": 21.9,
      "precipitation_sum": 3.2,
      "rain_sum": 3.2,
      "windspeed_10m_max": 22.3
    }
  ]
}
GETapi.aquabotany.com/weather/product

The latest NWS text product (forecast discussion, roundup, etc.).

ParameterInDescription
typequeryoptional Product code (default AFD). e.g. RWR, HWO.
locationqueryoptional Office or state code (default MPX). e.g. MN.
curl https://api.aquabotany.com/v1/weather/product \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/weather/product",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/weather/product", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/weather/product", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "type": "AFD",
  "location": "MPX",
  "name": "Area Forecast Discussion",
  "issuing_office": "KMPX",
  "issued_at": "2026-07-08T09:12:00+00:00",
  "text": "000\nFXUS63 KMPX 080912\nAFDMPX\n\nArea Forecast Discussion\nNational Weather Service Twin Cities/Chanhassen MN\n412 AM CDT Wed Jul 8 2026\n\n.SYNOPSIS...\nHot and increasingly humid conditions build through midweek. Heat\nindex values approach 100 degrees Wednesday afternoon, with a\nchance of showers and thunderstorms Wednesday night as a weak\ncold front approaches from the northwest.\n\n$\n"
}
GETapi.aquabotany.com/weather/seasonal-advice

Seasonal gardening advice for the location.

curl https://api.aquabotany.com/v1/weather/seasonal-advice?lat=44.98 \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/weather/seasonal-advice?lat=44.98",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/weather/seasonal-advice?lat=44.98", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/weather/seasonal-advice?lat=44.98", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "season": "Summer",
  "gardening_tips": [
    "Water plants regularly, especially during dry spells.",
    "Harvest fruits and vegetables as they ripen.",
    "Mulch garden beds to retain moisture and suppress weeds."
  ]
}
GETapi.aquabotany.com/weather/zone

Look up the hardiness zone by lat/lon query params.

ParameterInDescription
latqueryoptional GET: latitude (with lon).
lonqueryoptional GET: longitude (with lat).
curl https://api.aquabotany.com/v1/weather/zone?lat=44.98&lon=-93.26 \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/weather/zone?lat=44.98&lon=-93.26",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/weather/zone?lat=44.98&lon=-93.26", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/weather/zone?lat=44.98&lon=-93.26", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "zone": "4",
  "latitude": 44.98,
  "longitude": -93.26
}
POSTapi.aquabotany.com/weather/zone

Look up the hardiness zone by JSON body (zip or lat/lon).

ParameterInDescription
latqueryoptional GET: latitude (with lon).
lonqueryoptional GET: longitude (with lat).
curl https://api.aquabotany.com/v1/weather/zone \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"zip": "55401"}'
import requests

resp = requests.post(
    "https://api.aquabotany.com/v1/weather/zone",
    headers={"x-api-key": API_KEY},
    json={'zip': '55401'},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/weather/zone", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"zip": "55401"})
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/weather/zone", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"zip": "55401"})
});
const data = await resp.json();
200 Response
{
  "zone": "4",
  "latitude": 44.979265,
  "longitude": -93.273024
}

Environment

Base URL https://api.aquabotany.com/v1

GETapi.aquabotany.com/environment/air

Current air quality for a coordinate (US coverage).

ParameterInDescription
latqueryrequired Latitude.
lonqueryrequired Longitude.
distancequerySearch radius in miles, as a whole number. Defaults to 50.
curl https://api.aquabotany.com/v1/environment/air?lat=44.98&lon=-93.26 \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/environment/air?lat=44.98&lon=-93.26",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/environment/air?lat=44.98&lon=-93.26", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/environment/air?lat=44.98&lon=-93.26", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "location": {
    "lat": 44.98,
    "lon": -93.26
  },
  "timestamp": "2026-07-08 14:32:10",
  "pollutants": [
    {
      "pollutant": "O3",
      "aqi": 41,
      "category": "Good",
      "health_effects": "Air quality is considered satisfactory, and air pollution poses little or no risk."
    },
    {
      "pollutant": "PM2.5",
      "aqi": 54,
      "category": "Moderate",
      "health_effects": "Air quality is acceptable; however, some pollutants may pose a moderate health concern for sensitive groups."
    }
  ],
  "overall_aqi": 54,
  "overall_category": "Moderate"
}
GETapi.aquabotany.com/environment/air/forecast

Daily air-quality outlook, global: mean pollutant levels and peak AQI per day.

ParameterInDescription
latqueryrequired Latitude.
lonqueryrequired Longitude.
daysqueryoptional Days ahead (1-7, default 3).
curl https://api.aquabotany.com/v1/environment/air/forecast?lat=44.98&lon=-93.26 \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/environment/air/forecast?lat=44.98&lon=-93.26",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/environment/air/forecast?lat=44.98&lon=-93.26", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/environment/air/forecast?lat=44.98&lon=-93.26", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "location": {
    "lat": 44.98,
    "lon": -93.26
  },
  "mode": "forecast",
  "days_requested": 3,
  "days": [
    {
      "date": "2026-07-08",
      "pm2_5": 7.9,
      "pm10": 14.2,
      "ozone": 68.3,
      "nitrogen_dioxide": 9.6,
      "us_aqi": 52,
      "european_aqi": 38
    },
    {
      "date": "2026-07-09",
      "pm2_5": 9.4,
      "pm10": 16.8,
      "ozone": 74.1,
      "nitrogen_dioxide": 11.2,
      "us_aqi": 57,
      "european_aqi": 41
    },
    {
      "date": "2026-07-10",
      "pm2_5": 6.1,
      "pm10": 12.5,
      "ozone": 61.9,
      "nitrogen_dioxide": 8.3,
      "us_aqi": 46,
      "european_aqi": 34
    }
  ],
  "units": {
    "pm2_5": "μg/m³",
    "pm10": "μg/m³",
    "ozone": "μg/m³",
    "nitrogen_dioxide": "μg/m³",
    "us_aqi": "USAQI",
    "european_aqi": "EAQI"
  }
}
GETapi.aquabotany.com/environment/air/history

Daily air-quality history, global (up to 92 days back).

ParameterInDescription
latqueryrequired Latitude.
lonqueryrequired Longitude.
daysqueryoptional Days back (1-92, default 7).
curl https://api.aquabotany.com/v1/environment/air/history?lat=44.98&lon=-93.26&days=2 \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/environment/air/history?lat=44.98&lon=-93.26&days=2",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/environment/air/history?lat=44.98&lon=-93.26&days=2", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/environment/air/history?lat=44.98&lon=-93.26&days=2", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "location": {
    "lat": 44.98,
    "lon": -93.26
  },
  "mode": "history",
  "days_requested": 2,
  "days": [
    {
      "date": "2026-07-06",
      "pm2_5": 11.3,
      "pm10": 19.7,
      "ozone": 71.5,
      "nitrogen_dioxide": 12.8,
      "us_aqi": 61,
      "european_aqi": 44
    },
    {
      "date": "2026-07-07",
      "pm2_5": 8.6,
      "pm10": 15.1,
      "ozone": 65.0,
      "nitrogen_dioxide": 10.4,
      "us_aqi": 54,
      "european_aqi": 39
    }
  ],
  "units": {
    "pm2_5": "μg/m³",
    "pm10": "μg/m³",
    "ozone": "μg/m³",
    "nitrogen_dioxide": "μg/m³",
    "us_aqi": "USAQI",
    "european_aqi": "EAQI"
  }
}
GETapi.aquabotany.com/environment/earthquakes

Recent earthquakes, optionally near a coordinate.

ParameterInDescription
latqueryoptional Latitude (filter near a point).
lonqueryoptional Longitude.
radius_kmqueryoptional Search radius (default 500).
min_magnitudequeryoptional Minimum magnitude.
windowqueryoptional 'day' or 'week' (default week).
curl https://api.aquabotany.com/v1/environment/earthquakes \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/environment/earthquakes",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/environment/earthquakes", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/environment/earthquakes", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "window": "week",
  "near": null,
  "min_magnitude": null,
  "count": 2,
  "earthquakes": [
    {
      "id": "us7000q3xy",
      "magnitude": 5.8,
      "place": "42 km SSW of Adak, Alaska",
      "time": 1783497600000,
      "lat": 51.51,
      "lon": -176.91,
      "depth_km": 35.2,
      "url": "https://earthquake.usgs.gov/earthquakes/eventpage/us7000q3xy",
      "tsunami": false
    },
    {
      "id": "us7000q3wa",
      "magnitude": 4.6,
      "place": "18 km ENE of Kainantu, Papua New Guinea",
      "time": 1783463650000,
      "lat": -6.24,
      "lon": 146.12,
      "depth_km": 102.4,
      "url": "https://earthquake.usgs.gov/earthquakes/eventpage/us7000q3wa",
      "tsunami": false
    }
  ]
}
GETapi.aquabotany.com/environment/elevation

Terrain elevation at a coordinate.

ParameterInDescription
latqueryrequired Latitude.
lonqueryrequired Longitude.
curl https://api.aquabotany.com/v1/environment/elevation?lat=44.98&lon=-93.26 \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/environment/elevation?lat=44.98&lon=-93.26",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/environment/elevation?lat=44.98&lon=-93.26", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/environment/elevation?lat=44.98&lon=-93.26", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "location": {
    "lat": 44.98,
    "lon": -93.26
  },
  "elevation_m": 254.0
}
GETapi.aquabotany.com/environment/elevation-by-place

Terrain elevation for a named place.

ParameterInDescription
namequeryrequired Place name.
curl https://api.aquabotany.com/v1/environment/elevation-by-place?name=Kyoto \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/environment/elevation-by-place?name=Kyoto",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/environment/elevation-by-place?name=Kyoto", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/environment/elevation-by-place?name=Kyoto", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "query": "Kyoto",
  "resolved": {
    "name": "Kyoto",
    "lat": 35.02107,
    "lon": 135.75385,
    "country": "Japan",
    "admin1": "Kyōto"
  },
  "elevation_m": 45.0
}
GETapi.aquabotany.com/environment/events

Active natural-hazard events across all EONET natural-event categories: wildfires, severe storms, volcanoes, floods, drought, dust and haze, earthquakes, landslides, sea and lake ice, snow, temperature extremes, water color, and man-made events.

ParameterInDescription
categoryqueryoptional EONET category id, one of drought, dustHaze, earthquakes, floods, landslides, manmade, seaLakeIce, severeStorms, snow, tempExtremes, volcanoes, waterColor, wildfires; omit for all categories.
latqueryoptional Latitude (filter near a point).
lonqueryoptional Longitude.
radius_kmqueryoptional Search radius (default 200).
limitqueryoptional Max events (default 20).
curl https://api.aquabotany.com/v1/environment/events \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/environment/events",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/environment/events", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/environment/events", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "count": 2,
  "near": null,
  "events": [
    {
      "id": "EONET_15482",
      "title": "Tropical Storm Gilma",
      "lat": 14.8,
      "lon": -108.2,
      "date": "2026-07-08T06:00:00Z",
      "link": "https://eonet.gsfc.nasa.gov/api/v3/events/EONET_15482",
      "categories": [
        "severeStorms"
      ]
    },
    {
      "id": "EONET_15471",
      "title": "Boise Creek Fire, Idaho",
      "lat": 44.31,
      "lon": -115.62,
      "date": "2026-07-07T18:00:00Z",
      "link": "https://eonet.gsfc.nasa.gov/api/v3/events/EONET_15471",
      "categories": [
        "wildfires"
      ]
    }
  ],
  "category": null
}
GETapi.aquabotany.com/environment/flora-and-fauna

Nearby species observations.

ParameterInDescription
latqueryrequired Latitude.
lonqueryrequired Longitude.
radiusquerySearch radius in kilometres, as a whole number. Defaults to 10. Echoed back as radius_km.
curl https://api.aquabotany.com/v1/environment/flora-and-fauna?lat=44.98&lon=-93.26 \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/environment/flora-and-fauna?lat=44.98&lon=-93.26",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/environment/flora-and-fauna?lat=44.98&lon=-93.26", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/environment/flora-and-fauna?lat=44.98&lon=-93.26", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "location": {
    "latitude": 44.98,
    "longitude": -93.26
  },
  "radius_km": 10,
  "observations": [
    {
      "name": "Sciurus carolinensis",
      "common_name": "Eastern Gray Squirrel",
      "rank": "species",
      "observed_on": "2026-07-07",
      "location": "44.9741,-93.2277"
    },
    {
      "name": "Asclepias syriaca",
      "common_name": "Common Milkweed",
      "rank": "species",
      "observed_on": "2026-07-06",
      "location": "44.9902,-93.2895"
    }
  ]
}
GETapi.aquabotany.com/environment/moon

Lunar phase for a date.

ParameterInDescription
datequeryoptional YYYY-MM-DD; defaults to today.
curl https://api.aquabotany.com/v1/environment/moon \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/environment/moon",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/environment/moon", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/environment/moon", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "date": "2026-07-08",
  "phase": "Last Quarter",
  "age_days": 23.32,
  "illumination": 0.3764,
  "illumination_percent": 37.6,
  "is_waxing": false,
  "previous_new_moon": "2026-06-15",
  "next_new_moon": "2026-07-14",
  "next_full_moon": "2026-07-29"
}
GETapi.aquabotany.com/environment/pollen

Current airborne pollen levels by type (Europe coverage).

ParameterInDescription
latqueryrequired Latitude.
lonqueryrequired Longitude.
curl https://api.aquabotany.com/v1/environment/pollen?lat=52.52&lon=13.41 \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/environment/pollen?lat=52.52&lon=13.41",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/environment/pollen?lat=52.52&lon=13.41", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/environment/pollen?lat=52.52&lon=13.41", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "location": {
    "lat": 52.52,
    "lon": 13.41
  },
  "observed_at": "2026-07-08T16:15",
  "unit": "grains/m³",
  "pollen": {
    "alder": {
      "value": 0.0,
      "severity": "none"
    },
    "birch": {
      "value": 0.0,
      "severity": "none"
    },
    "grass": {
      "value": 34.6,
      "severity": "moderate"
    },
    "mugwort": {
      "value": 2.1,
      "severity": "low"
    },
    "olive": {
      "value": 0.0,
      "severity": "none"
    },
    "ragweed": {
      "value": 1.8,
      "severity": "low"
    }
  },
  "coverage": "Europe (CAMS); null outside the model domain"
}
GETapi.aquabotany.com/environment/pollen/forecast

Daily pollen outlook: each type's peak level with a severity band (Europe coverage).

ParameterInDescription
latqueryrequired Latitude.
lonqueryrequired Longitude.
daysqueryoptional Days ahead (1-7, default 3).
curl https://api.aquabotany.com/v1/environment/pollen/forecast?lat=52.52&lon=13.41 \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/environment/pollen/forecast?lat=52.52&lon=13.41",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/environment/pollen/forecast?lat=52.52&lon=13.41", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/environment/pollen/forecast?lat=52.52&lon=13.41", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "location": {
    "lat": 52.52,
    "lon": 13.41
  },
  "mode": "forecast",
  "days_requested": 3,
  "days": [
    {
      "date": "2026-07-08",
      "pollen": {
        "alder": {
          "value": 0.0,
          "severity": "none"
        },
        "birch": {
          "value": 0.0,
          "severity": "none"
        },
        "grass": {
          "value": 58.2,
          "severity": "high"
        },
        "mugwort": {
          "value": 3.4,
          "severity": "low"
        },
        "olive": {
          "value": 0.0,
          "severity": "none"
        },
        "ragweed": {
          "value": 2.9,
          "severity": "low"
        }
      }
    },
    {
      "date": "2026-07-09",
      "pollen": {
        "alder": {
          "value": 0.0,
          "severity": "none"
        },
        "birch": {
          "value": 0.0,
          "severity": "none"
        },
        "grass": {
          "value": 41.7,
          "severity": "moderate"
        },
        "mugwort": {
          "value": 4.8,
          "severity": "low"
        },
        "olive": {
          "value": 0.0,
          "severity": "none"
        },
        "ragweed": {
          "value": 3.6,
          "severity": "low"
        }
      }
    },
    {
      "date": "2026-07-10",
      "pollen": {
        "alder": {
          "value": 0.0,
          "severity": "none"
        },
        "birch": {
          "value": 0.0,
          "severity": "none"
        },
        "grass": {
          "value": 24.3,
          "severity": "moderate"
        },
        "mugwort": {
          "value": 2.2,
          "severity": "low"
        },
        "olive": {
          "value": 0.0,
          "severity": "none"
        },
        "ragweed": {
          "value": 1.5,
          "severity": "low"
        }
      }
    }
  ],
  "unit": "grains/m³",
  "coverage": "Europe (CAMS); null outside the model domain"
}
GETapi.aquabotany.com/environment/pollen/history

Daily pollen history, up to 92 days back (Europe coverage).

ParameterInDescription
latqueryrequired Latitude.
lonqueryrequired Longitude.
daysqueryoptional Days back (1-92, default 7).
curl https://api.aquabotany.com/v1/environment/pollen/history?lat=52.52&lon=13.41&days=2 \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/environment/pollen/history?lat=52.52&lon=13.41&days=2",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/environment/pollen/history?lat=52.52&lon=13.41&days=2", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/environment/pollen/history?lat=52.52&lon=13.41&days=2", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "location": {
    "lat": 52.52,
    "lon": 13.41
  },
  "mode": "history",
  "days_requested": 2,
  "days": [
    {
      "date": "2026-07-06",
      "pollen": {
        "alder": {
          "value": 0.0,
          "severity": "none"
        },
        "birch": {
          "value": 0.0,
          "severity": "none"
        },
        "grass": {
          "value": 47.9,
          "severity": "moderate"
        },
        "mugwort": {
          "value": 2.7,
          "severity": "low"
        },
        "olive": {
          "value": 0.0,
          "severity": "none"
        },
        "ragweed": {
          "value": 2.0,
          "severity": "low"
        }
      }
    },
    {
      "date": "2026-07-07",
      "pollen": {
        "alder": {
          "value": 0.0,
          "severity": "none"
        },
        "birch": {
          "value": 0.0,
          "severity": "none"
        },
        "grass": {
          "value": 52.4,
          "severity": "high"
        },
        "mugwort": {
          "value": 3.1,
          "severity": "low"
        },
        "olive": {
          "value": 0.0,
          "severity": "none"
        },
        "ragweed": {
          "value": 2.4,
          "severity": "low"
        }
      }
    }
  ],
  "unit": "grains/m³",
  "coverage": "Europe (CAMS); null outside the model domain"
}
GETapi.aquabotany.com/environment/soil/composition

Soil classification (WRB) for a coordinate.

ParameterInDescription
latqueryrequired Latitude.
lonqueryrequired Longitude.
curl https://api.aquabotany.com/v1/environment/soil/composition?lat=44.98&lon=-93.26 \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/environment/soil/composition?lat=44.98&lon=-93.26",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/environment/soil/composition?lat=44.98&lon=-93.26", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/environment/soil/composition?lat=44.98&lon=-93.26", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "location": {
    "lat": 44.98,
    "lon": -93.26
  },
  "soil_class": "Luvisols",
  "top_classes": [
    {
      "class": "Luvisols",
      "probability_percent": 31
    },
    {
      "class": "Phaeozems",
      "probability_percent": 23
    },
    {
      "class": "Cambisols",
      "probability_percent": 14
    },
    {
      "class": "Retisols",
      "probability_percent": 9
    },
    {
      "class": "Chernozems",
      "probability_percent": 7
    }
  ],
  "reference_system": "WRB (World Reference Base)"
}
GETapi.aquabotany.com/environment/soil/moisture

Live soil temperature and moisture at several depths.

ParameterInDescription
latqueryrequired Latitude.
lonqueryrequired Longitude.
curl https://api.aquabotany.com/v1/environment/soil/moisture?lat=44.98&lon=-93.26 \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/environment/soil/moisture?lat=44.98&lon=-93.26",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/environment/soil/moisture?lat=44.98&lon=-93.26", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/environment/soil/moisture?lat=44.98&lon=-93.26", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "location": {
    "lat": 44.98,
    "lon": -93.26
  },
  "observed_at": "2026-07-08T09:15",
  "temperature": {
    "unit": "°C",
    "by_depth": {
      "0cm": 24.3,
      "6cm": 22.1,
      "18cm": 20.4,
      "54cm": 16.8
    }
  },
  "moisture": {
    "unit": "m³/m³",
    "by_depth": {
      "0_to_1cm": 0.28,
      "1_to_3cm": 0.291,
      "3_to_9cm": 0.305,
      "9_to_27cm": 0.312,
      "27_to_81cm": 0.334
    }
  }
}
GETapi.aquabotany.com/environment/soil/properties

Numeric soil properties for a point. soil_properties passes through every numeric layer ISRIC SoilGrids publishes for the location, keyed by SoilGrids layer name (the example shows clay, sand, phh2o, and soc); each property carries its unit and mean values per depth band, already scaled to the stated unit.

ParameterInDescription
latqueryrequired Latitude.
lonqueryrequired Longitude.
curl https://api.aquabotany.com/v1/environment/soil/properties?lat=44.98&lon=-93.26 \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/environment/soil/properties?lat=44.98&lon=-93.26",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/environment/soil/properties?lat=44.98&lon=-93.26", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/environment/soil/properties?lat=44.98&lon=-93.26", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "location": {
    "lat": 44.98,
    "lon": -93.26
  },
  "soil_properties": {
    "clay": {
      "unit": "g/kg",
      "depths": {
        "0-5cm": 25.4,
        "5-15cm": 26.1,
        "15-30cm": 27.8
      }
    },
    "sand": {
      "unit": "g/kg",
      "depths": {
        "0-5cm": 38.2,
        "5-15cm": 37.5,
        "15-30cm": 36.9
      }
    },
    "phh2o": {
      "unit": "pH",
      "depths": {
        "0-5cm": 6.4,
        "5-15cm": 6.5,
        "15-30cm": 6.7
      }
    },
    "soc": {
      "unit": "g/kg",
      "depths": {
        "0-5cm": 41.7,
        "5-15cm": 28.3,
        "15-30cm": 17.2
      }
    }
  }
}
GETapi.aquabotany.com/environment/wildfire

Active wildfire events, optionally near a coordinate.

ParameterInDescription
latqueryoptional Latitude (filter near a point).
lonqueryoptional Longitude.
radius_kmqueryoptional Search radius (default 200).
limitqueryoptional Max events (default 20).
curl https://api.aquabotany.com/v1/environment/wildfire \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/environment/wildfire",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/environment/wildfire", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/environment/wildfire", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "count": 2,
  "near": null,
  "events": [
    {
      "id": "EONET_15471",
      "title": "Boise Creek Fire, Idaho",
      "lat": 44.31,
      "lon": -115.62,
      "date": "2026-07-07T18:00:00Z",
      "link": "https://eonet.gsfc.nasa.gov/api/v3/events/EONET_15471"
    },
    {
      "id": "EONET_15465",
      "title": "Rock Springs Fire, Nevada",
      "lat": 40.87,
      "lon": -117.45,
      "date": "2026-07-06T12:00:00Z",
      "link": "https://eonet.gsfc.nasa.gov/api/v3/events/EONET_15465"
    }
  ]
}
GETapi.aquabotany.com/sustainability/recycling-locations

Nearby recycling drop-off locations.

curl https://api.aquabotany.com/v1/sustainability/recycling-locations \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/sustainability/recycling-locations",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/sustainability/recycling-locations", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/sustainability/recycling-locations", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "name": "Minneapolis Recycling Drop-Off Facility",
  "address": "1400 W 96th Street, Bloomington, MN 55431",
  "website": "https://www.hennepin.us/dropoff",
  "hours": "Tuesday-Saturday, 9 AM - 5 PM",
  "materials_accepted": [
    "Electronics",
    "Batteries",
    "Cardboard",
    "Household hazardous waste"
  ],
  "notes": "Free drop-off for Minneapolis residents."
}
GETapi.aquabotany.com/sustainability/tips

Sustainability and eco tips.

curl https://api.aquabotany.com/v1/sustainability/tips?material=plastic \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/sustainability/tips?material=plastic",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/sustainability/tips?material=plastic", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/sustainability/tips?material=plastic", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "description": "Plastics can usually be recycled if they are clean and have a recycling code (1-7).",
  "instructions": [
    "Check for the recycling triangle with a number (1-7).",
    "Clean and dry plastics before recycling.",
    "Avoid recycling plastic bags; take them to a designated drop-off."
  ],
  "examples": [
    "Bottles, containers, food packaging",
    "HDPE pipes",
    "Polystyrene trays"
  ],
  "notes": "Some plastics like styrofoam, PVC, and polystyrene are not recyclable curbside."
}

Location & places

Base URL https://api.aquabotany.com/v1

GETapi.aquabotany.com/location/geocode

Resolve a place name to coordinates.

ParameterInDescription
namequeryrequired Town, city, or landmark.
countqueryoptional Max results (1-10, default 5).
curl https://api.aquabotany.com/v1/location/geocode?name=Kyoto \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/location/geocode?name=Kyoto",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/location/geocode?name=Kyoto", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/location/geocode?name=Kyoto", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "query": "Kyoto",
  "count": 1,
  "results": [
    {
      "name": "Kyoto",
      "lat": 35.02107,
      "lon": 135.75385,
      "country": "Japan",
      "country_code": "JP",
      "admin1": "Kyōto",
      "admin2": "Kyōto",
      "timezone": "Asia/Tokyo",
      "population": 1463723,
      "elevation_m": 50.0
    }
  ]
}
GETapi.aquabotany.com/location/reverse-geocode

Resolve a coordinate to the nearest address.

ParameterInDescription
latqueryrequired Latitude.
lonqueryrequired Longitude.
curl https://api.aquabotany.com/v1/location/reverse-geocode?lat=44.98&lon=-93.26 \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/location/reverse-geocode?lat=44.98&lon=-93.26",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/location/reverse-geocode?lat=44.98&lon=-93.26", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/location/reverse-geocode?lat=44.98&lon=-93.26", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "location": {
    "lat": 44.98,
    "lon": -93.26
  },
  "display_name": "Downtown West, Minneapolis, Hennepin County, Minnesota, 55403, United States",
  "address": {
    "city": "Minneapolis",
    "county": "Hennepin County",
    "state": "Minnesota",
    "postcode": "55403",
    "country": "United States",
    "country_code": "us"
  }
}
GETapi.aquabotany.com/places/gas-stations

Nearest gas stations (the Kwik Trip / Kwik Star network) to a coordinate. Locations only: id, name, distance, coordinates, address, and phone. No fuel price fields. Also reachable at its original path, /places/kwik-trip, which remains a live alias.

Stations come from a packaged snapshot of the network, so the response is a station locator, not a live price feed: source is kwiktrip-snapshot and data_as_of is the snapshot date. The former path /places/kwik-trip remains a working alias for backward compatibility and returns the same response.

ParameterInDescription
latqueryrequired Latitude.
lonqueryrequired Longitude.
radius_kmqueryoptional Only stores within this radius.
limitqueryoptional Max stores (default 10, max 50).
curl https://api.aquabotany.com/v1/places/gas-stations?lat=44.98&lon=-93.26&limit=2 \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/places/gas-stations?lat=44.98&lon=-93.26&limit=2",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/places/gas-stations?lat=44.98&lon=-93.26&limit=2", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/places/gas-stations?lat=44.98&lon=-93.26&limit=2", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "near": {
    "lat": 44.98,
    "lon": -93.26,
    "radius_km": null
  },
  "count": 2,
  "stores": [
    {
      "id": 411,
      "name": "KWIK TRIP #411",
      "distance_km": 15.4,
      "lat": 44.99722,
      "lon": -93.45473,
      "address": {
        "street": "1605 ANNAPOLIS LN N",
        "city": "PLYMOUTH",
        "state": "MN",
        "zip": 55441
      },
      "phone": "(763) 577-1983"
    },
    {
      "id": 896,
      "name": "KWIK TRIP #896",
      "distance_km": 16.3,
      "lat": 45.12528,
      "lon": -93.23671,
      "address": {
        "street": "1355 85TH AVE NE",
        "city": "BLAINE",
        "state": "MN",
        "zip": 55434
      },
      "phone": "(763) 717-2807"
    }
  ],
  "data_as_of": "2026-07-06"
}

Plants

Base URL https://api.aquabotany.com/v1

GETapi.aquabotany.com/my-plants

List the caller's plants.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/my-plants \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be "*" (read across every platform)
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/my-plants?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/my-plants",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/my-plants",
    headers={"x-api-key": API_KEY},
    # platform can also be "*" (read across every platform)
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/my-plants", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/my-plants?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/my-plants", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/my-plants?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "plants": [
    {
      "id": "3f9c2a71-8e4d-4c2b-9f6a-2d7e0b5a1c34",
      "name": "Basil",
      "environmentId": "7b1e6c2d-05a9-4f3e-8d21-c4a9e7f2b810",
      "quantity": 1.0,
      "createdAt": "2026-07-08T14:21:37.412903+00:00",
      "updatedAt": "2026-07-08T14:21:37.412903+00:00",
      "details": {
        "species": "Ocimum basilicum"
      }
    },
    {
      "id": "9d4b8e02-6f1a-47c5-b3e8-51a0c2d97f46",
      "name": "Monstera",
      "environmentId": "7b1e6c2d-05a9-4f3e-8d21-c4a9e7f2b810",
      "quantity": 2.0,
      "createdAt": "2026-06-30T09:05:12.038471+00:00",
      "updatedAt": "2026-07-05T18:47:03.559214+00:00",
      "details": {}
    }
  ],
  "environments": [
    {
      "id": "7b1e6c2d-05a9-4f3e-8d21-c4a9e7f2b810",
      "name": "Kitchen Window"
    }
  ]
}
POSTapi.aquabotany.com/my-plants

Add a plant to the collection.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/my-plants \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Basil", "species": "Ocimum basilicum", "environment_id": "7b1e6c2d-05a9-4f3e-8d21-c4a9e7f2b810"}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/my-plants \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Basil", "species": "Ocimum basilicum", "environment_id": "7b1e6c2d-05a9-4f3e-8d21-c4a9e7f2b810", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.post(
    "https://api.aquabotany.com/v1/my-plants",
    headers={"x-api-key": API_KEY},
    json={'name': 'Basil', 'species': 'Ocimum basilicum', 'environment_id': '7b1e6c2d-05a9-4f3e-8d21-c4a9e7f2b810'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.post(
    "https://api.aquabotany.com/v1/my-plants",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'name': 'Basil', 'species': 'Ocimum basilicum', 'environment_id': '7b1e6c2d-05a9-4f3e-8d21-c4a9e7f2b810', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/my-plants", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"name": "Basil", "species": "Ocimum basilicum", "environment_id": "7b1e6c2d-05a9-4f3e-8d21-c4a9e7f2b810"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/my-plants", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"name": "Basil", "species": "Ocimum basilicum", "environment_id": "7b1e6c2d-05a9-4f3e-8d21-c4a9e7f2b810", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/my-plants", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"name": "Basil", "species": "Ocimum basilicum", "environment_id": "7b1e6c2d-05a9-4f3e-8d21-c4a9e7f2b810"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/my-plants", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"name": "Basil", "species": "Ocimum basilicum", "environment_id": "7b1e6c2d-05a9-4f3e-8d21-c4a9e7f2b810", "platform": "my_garden"})
});
201 Response
{
  "message": "Plant added successfully",
  "plant_id": "3f9c2a71-8e4d-4c2b-9f6a-2d7e0b5a1c34"
}
PUTapi.aquabotany.com/my-plants/{plantId}

Update or remove a plant in the collection.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/my-plants/3f9c2a71-8e4d-4c2b-9f6a-2d7e0b5a1c34 \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Basil", "quantity": 2, "location": "kitchen windowsill"}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/my-plants/3f9c2a71-8e4d-4c2b-9f6a-2d7e0b5a1c34 \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Basil", "quantity": 2, "location": "kitchen windowsill", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.put(
    "https://api.aquabotany.com/v1/my-plants/3f9c2a71-8e4d-4c2b-9f6a-2d7e0b5a1c34",
    headers={"x-api-key": API_KEY},
    json={'name': 'Basil', 'quantity': 2, 'location': 'kitchen windowsill'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.put(
    "https://api.aquabotany.com/v1/my-plants/3f9c2a71-8e4d-4c2b-9f6a-2d7e0b5a1c34",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'name': 'Basil', 'quantity': 2, 'location': 'kitchen windowsill', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/my-plants/3f9c2a71-8e4d-4c2b-9f6a-2d7e0b5a1c34", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"name": "Basil", "quantity": 2, "location": "kitchen windowsill"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/my-plants/3f9c2a71-8e4d-4c2b-9f6a-2d7e0b5a1c34", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"name": "Basil", "quantity": 2, "location": "kitchen windowsill", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/my-plants/3f9c2a71-8e4d-4c2b-9f6a-2d7e0b5a1c34", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"name": "Basil", "quantity": 2, "location": "kitchen windowsill"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/my-plants/3f9c2a71-8e4d-4c2b-9f6a-2d7e0b5a1c34", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"name": "Basil", "quantity": 2, "location": "kitchen windowsill", "platform": "my_garden"})
});
200 Response
{
  "message": "Plant updated successfully",
  "updated_attributes": {
    "Name": "Basil",
    "Quantity": 2.0,
    "Details": {
      "species": "Ocimum basilicum",
      "location": "kitchen windowsill"
    },
    "UpdatedAt": "2026-07-08T15:02:44.918274+00:00"
  }
}
DELETEapi.aquabotany.com/my-plants/{plantId}

Update or remove a plant in the collection.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/my-plants/9d4b8e02-6f1a-47c5-b3e8-51a0c2d97f46 \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl "https://api.aquabotany.com/v1/my-plants/9d4b8e02-6f1a-47c5-b3e8-51a0c2d97f46?platform=my_garden" \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.delete(
    "https://api.aquabotany.com/v1/my-plants/9d4b8e02-6f1a-47c5-b3e8-51a0c2d97f46",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.delete(
    "https://api.aquabotany.com/v1/my-plants/9d4b8e02-6f1a-47c5-b3e8-51a0c2d97f46",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/my-plants/9d4b8e02-6f1a-47c5-b3e8-51a0c2d97f46", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/my-plants/9d4b8e02-6f1a-47c5-b3e8-51a0c2d97f46?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/my-plants/9d4b8e02-6f1a-47c5-b3e8-51a0c2d97f46", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/my-plants/9d4b8e02-6f1a-47c5-b3e8-51a0c2d97f46?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "message": "Plant deleted successfully"
}
GETapi.aquabotany.com/plants/plantfinder

Find plants matching criteria.

curl https://api.aquabotany.com/v1/plants/plantfinder?query_type=zone&filter_value=4 \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/plants/plantfinder?query_type=zone&filter_value=4",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/plants/plantfinder?query_type=zone&filter_value=4", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/plants/plantfinder?query_type=zone&filter_value=4", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
[
  {
    "CommonName": "Bee Balm",
    "Zone": "4"
  },
  {
    "CommonName": "Purple Coneflower",
    "Zone": "4"
  }
]
GETapi.aquabotany.com/plants/tips

Plant-care tips.

ParameterInDescription
plantqueryrequired Plant type: succulent, fern, cactus, herbs, orchid, peace lily, monstera, spider plant, or bonsai.
curl https://api.aquabotany.com/v1/plants/tips?plant=monstera \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/plants/tips?plant=monstera",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/plants/tips?plant=monstera", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/plants/tips?plant=monstera", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "watering": "Water every 1-2 weeks; keep soil lightly moist.",
  "light": "Bright, indirect light.",
  "humidity": "Moderate to high humidity.",
  "notes": "Wipe leaves occasionally to remove dust and promote growth."
}

Smart garden

Base URL https://api.aquabotany.com/v1

GETapi.aquabotany.com/automation-devices

Registered garden automation devices.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/automation-devices \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be "*" (read across every platform)
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/automation-devices?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/automation-devices",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/automation-devices",
    headers={"x-api-key": API_KEY},
    # platform can also be "*" (read across every platform)
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/automation-devices", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/automation-devices?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/automation-devices", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/automation-devices?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "devices": [
    {
      "id": "b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70",
      "name": "Drip valve",
      "assignedTo": "e4b1c8a6-2d93-4f70-b1e5-6c8a2d4f9b13",
      "environment": "c2a8f1d3-7b64-4e09-a5d2-8f3c1b6e9a47",
      "serial": "DV-00142",
      "type": "Device",
      "config": {}
    }
  ],
  "deviceTypes": [
    "Type 1",
    "Type 2"
  ],
  "selectedDeviceType": "Type 1",
  "plants": [
    {
      "id": "e4b1c8a6-2d93-4f70-b1e5-6c8a2d4f9b13",
      "name": "Basil",
      "environment": "c2a8f1d3-7b64-4e09-a5d2-8f3c1b6e9a47"
    }
  ],
  "environments": [
    {
      "id": "c2a8f1d3-7b64-4e09-a5d2-8f3c1b6e9a47",
      "name": "Backyard bed"
    }
  ]
}
POSTapi.aquabotany.com/automation-devices

Registered garden automation devices.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/automation-devices \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Drip valve", "environment_id": "c2a8f1d3-7b64-4e09-a5d2-8f3c1b6e9a47", "plant_id": "e4b1c8a6-2d93-4f70-b1e5-6c8a2d4f9b13", "serial": "DV-00142", "config": {}}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/automation-devices \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Drip valve", "environment_id": "c2a8f1d3-7b64-4e09-a5d2-8f3c1b6e9a47", "plant_id": "e4b1c8a6-2d93-4f70-b1e5-6c8a2d4f9b13", "serial": "DV-00142", "config": {}, "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.post(
    "https://api.aquabotany.com/v1/automation-devices",
    headers={"x-api-key": API_KEY},
    json={'name': 'Drip valve', 'environment_id': 'c2a8f1d3-7b64-4e09-a5d2-8f3c1b6e9a47', 'plant_id': 'e4b1c8a6-2d93-4f70-b1e5-6c8a2d4f9b13', 'serial': 'DV-00142', 'config': {}},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.post(
    "https://api.aquabotany.com/v1/automation-devices",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'name': 'Drip valve', 'environment_id': 'c2a8f1d3-7b64-4e09-a5d2-8f3c1b6e9a47', 'plant_id': 'e4b1c8a6-2d93-4f70-b1e5-6c8a2d4f9b13', 'serial': 'DV-00142', 'config': {}, 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/automation-devices", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"name": "Drip valve", "environment_id": "c2a8f1d3-7b64-4e09-a5d2-8f3c1b6e9a47", "plant_id": "e4b1c8a6-2d93-4f70-b1e5-6c8a2d4f9b13", "serial": "DV-00142", "config": {}})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/automation-devices", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"name": "Drip valve", "environment_id": "c2a8f1d3-7b64-4e09-a5d2-8f3c1b6e9a47", "plant_id": "e4b1c8a6-2d93-4f70-b1e5-6c8a2d4f9b13", "serial": "DV-00142", "config": {}, "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/automation-devices", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"name": "Drip valve", "environment_id": "c2a8f1d3-7b64-4e09-a5d2-8f3c1b6e9a47", "plant_id": "e4b1c8a6-2d93-4f70-b1e5-6c8a2d4f9b13", "serial": "DV-00142", "config": {}})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/automation-devices", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"name": "Drip valve", "environment_id": "c2a8f1d3-7b64-4e09-a5d2-8f3c1b6e9a47", "plant_id": "e4b1c8a6-2d93-4f70-b1e5-6c8a2d4f9b13", "serial": "DV-00142", "config": {}, "platform": "my_garden"})
});
201 Response
{
  "message": "Device added successfully",
  "device_id": "b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70",
  "environment_id": "c2a8f1d3-7b64-4e09-a5d2-8f3c1b6e9a47",
  "plant_id": "e4b1c8a6-2d93-4f70-b1e5-6c8a2d4f9b13",
  "platform": "my_garden"
}
PUTapi.aquabotany.com/automation-devices/{deviceId}

Update or remove a device.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/automation-devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70 \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Drip valve", "serial": "DV-00142", "config": {}}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/automation-devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70 \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Drip valve", "serial": "DV-00142", "config": {}, "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.put(
    "https://api.aquabotany.com/v1/automation-devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70",
    headers={"x-api-key": API_KEY},
    json={'name': 'Drip valve', 'serial': 'DV-00142', 'config': {}},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.put(
    "https://api.aquabotany.com/v1/automation-devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'name': 'Drip valve', 'serial': 'DV-00142', 'config': {}, 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/automation-devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"name": "Drip valve", "serial": "DV-00142", "config": {}})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/automation-devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"name": "Drip valve", "serial": "DV-00142", "config": {}, "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/automation-devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"name": "Drip valve", "serial": "DV-00142", "config": {}})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/automation-devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"name": "Drip valve", "serial": "DV-00142", "config": {}, "platform": "my_garden"})
});
200 Response
{
  "message": "Device updated successfully"
}
DELETEapi.aquabotany.com/automation-devices/{deviceId}

Update or remove a device.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/automation-devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70 \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl "https://api.aquabotany.com/v1/automation-devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70?platform=my_garden" \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.delete(
    "https://api.aquabotany.com/v1/automation-devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.delete(
    "https://api.aquabotany.com/v1/automation-devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/automation-devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/automation-devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/automation-devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/automation-devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "message": "Device deleted successfully"
}
GETapi.aquabotany.com/devices/{deviceId}/commands

Queue or list commands for a device.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70/commands \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be "*" (read across every platform)
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70/commands?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70/commands",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70/commands",
    headers={"x-api-key": API_KEY},
    # platform can also be "*" (read across every platform)
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70/commands", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70/commands?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70/commands", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70/commands?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
[
  {
    "deviceId": "b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70",
    "commandId": "7f3a9c21-64bd-4e5a-8f1c-0d2b6e9a3c58",
    "command": "open_valve",
    "timestamp": "2026-07-08T09:15:32.418206"
  },
  {
    "deviceId": "b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70",
    "commandId": "2c5d8f4b-1a97-4e63-b0d8-7f3e6a1c9b25",
    "command": "close_valve",
    "timestamp": "2026-07-08T10:02:11.907414"
  }
]
POSTapi.aquabotany.com/devices/{deviceId}/commands

Queue or list commands for a device.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70/commands \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"command": "open_valve"}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70/commands \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"command": "open_valve", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.post(
    "https://api.aquabotany.com/v1/devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70/commands",
    headers={"x-api-key": API_KEY},
    json={'command': 'open_valve'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.post(
    "https://api.aquabotany.com/v1/devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70/commands",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'command': 'open_valve', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70/commands", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"command": "open_valve"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70/commands", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"command": "open_valve", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70/commands", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"command": "open_valve"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70/commands", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"command": "open_valve", "platform": "my_garden"})
});
200 Response
{
  "message": "Command created",
  "commandId": "7f3a9c21-64bd-4e5a-8f1c-0d2b6e9a3c58"
}
DELETEapi.aquabotany.com/devices/{deviceId}/commands/{commandId}

Delete a queued device command.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70/commands/7f3a9c21-64bd-4e5a-8f1c-0d2b6e9a3c58 \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl "https://api.aquabotany.com/v1/devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70/commands/7f3a9c21-64bd-4e5a-8f1c-0d2b6e9a3c58?platform=my_garden" \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.delete(
    "https://api.aquabotany.com/v1/devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70/commands/7f3a9c21-64bd-4e5a-8f1c-0d2b6e9a3c58",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.delete(
    "https://api.aquabotany.com/v1/devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70/commands/7f3a9c21-64bd-4e5a-8f1c-0d2b6e9a3c58",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70/commands/7f3a9c21-64bd-4e5a-8f1c-0d2b6e9a3c58", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70/commands/7f3a9c21-64bd-4e5a-8f1c-0d2b6e9a3c58?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70/commands/7f3a9c21-64bd-4e5a-8f1c-0d2b6e9a3c58", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/devices/b7e9d2c4-5a1f-4e8b-9c3d-2f6a8e1b4d70/commands/7f3a9c21-64bd-4e5a-8f1c-0d2b6e9a3c58?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "message": "Command deleted"
}
GETapi.aquabotany.com/garden-environment

Garden environment readings / zones.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/garden-environment \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be "*" (read across every platform)
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/garden-environment?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/garden-environment",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/garden-environment",
    headers={"x-api-key": API_KEY},
    # platform can also be "*" (read across every platform)
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/garden-environment", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/garden-environment?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/garden-environment", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/garden-environment?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
[
  {
    "EnvironmentID": "c2a8f1d3-7b64-4e09-a5d2-8f3c1b6e9a47",
    "Name": "Backyard bed",
    "CreatedAt": "2026-07-08T09:15:32.418206+00:00",
    "UpdatedAt": "2026-07-08T09:15:32.418206+00:00",
    "Details": {
      "zone": "4b"
    },
    "id": "c2a8f1d3-7b64-4e09-a5d2-8f3c1b6e9a47"
  },
  {
    "EnvironmentID": "f6d2b9e4-8a15-4c73-9e0b-3d7f5a2c8e61",
    "Name": "Greenhouse",
    "CreatedAt": "2026-07-01T14:22:05.113842+00:00",
    "UpdatedAt": "2026-07-05T08:47:19.664501+00:00",
    "Details": {
      "zone": "4b",
      "humidity_target": 65
    },
    "id": "f6d2b9e4-8a15-4c73-9e0b-3d7f5a2c8e61"
  }
]
POSTapi.aquabotany.com/garden-environment

Garden environment readings / zones.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/garden-environment \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Backyard bed", "zone": "4b"}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/garden-environment \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Backyard bed", "zone": "4b", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.post(
    "https://api.aquabotany.com/v1/garden-environment",
    headers={"x-api-key": API_KEY},
    json={'name': 'Backyard bed', 'zone': '4b'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.post(
    "https://api.aquabotany.com/v1/garden-environment",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'name': 'Backyard bed', 'zone': '4b', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/garden-environment", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"name": "Backyard bed", "zone": "4b"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/garden-environment", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"name": "Backyard bed", "zone": "4b", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/garden-environment", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"name": "Backyard bed", "zone": "4b"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/garden-environment", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"name": "Backyard bed", "zone": "4b", "platform": "my_garden"})
});
200 Response
{
  "message": "Environment added successfully",
  "environment_id": "c2a8f1d3-7b64-4e09-a5d2-8f3c1b6e9a47"
}
PUTapi.aquabotany.com/garden-environment/{environmentId}

Update or delete a garden environment.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/garden-environment/c2a8f1d3-7b64-4e09-a5d2-8f3c1b6e9a47 \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Backyard bed", "zone": "5a"}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/garden-environment/c2a8f1d3-7b64-4e09-a5d2-8f3c1b6e9a47 \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Backyard bed", "zone": "5a", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.put(
    "https://api.aquabotany.com/v1/garden-environment/c2a8f1d3-7b64-4e09-a5d2-8f3c1b6e9a47",
    headers={"x-api-key": API_KEY},
    json={'name': 'Backyard bed', 'zone': '5a'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.put(
    "https://api.aquabotany.com/v1/garden-environment/c2a8f1d3-7b64-4e09-a5d2-8f3c1b6e9a47",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'name': 'Backyard bed', 'zone': '5a', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/garden-environment/c2a8f1d3-7b64-4e09-a5d2-8f3c1b6e9a47", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"name": "Backyard bed", "zone": "5a"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/garden-environment/c2a8f1d3-7b64-4e09-a5d2-8f3c1b6e9a47", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"name": "Backyard bed", "zone": "5a", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/garden-environment/c2a8f1d3-7b64-4e09-a5d2-8f3c1b6e9a47", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"name": "Backyard bed", "zone": "5a"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/garden-environment/c2a8f1d3-7b64-4e09-a5d2-8f3c1b6e9a47", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"name": "Backyard bed", "zone": "5a", "platform": "my_garden"})
});
200 Response
{
  "message": "Environment updated successfully",
  "updated_attributes": {
    "Name": "Backyard bed",
    "Details": {
      "zone": "5a"
    },
    "UpdatedAt": "2026-07-08T10:02:11.907414+00:00"
  }
}
DELETEapi.aquabotany.com/garden-environment/{environmentId}

Update or delete a garden environment.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/garden-environment/c2a8f1d3-7b64-4e09-a5d2-8f3c1b6e9a47 \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl "https://api.aquabotany.com/v1/garden-environment/c2a8f1d3-7b64-4e09-a5d2-8f3c1b6e9a47?platform=my_garden" \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.delete(
    "https://api.aquabotany.com/v1/garden-environment/c2a8f1d3-7b64-4e09-a5d2-8f3c1b6e9a47",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.delete(
    "https://api.aquabotany.com/v1/garden-environment/c2a8f1d3-7b64-4e09-a5d2-8f3c1b6e9a47",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/garden-environment/c2a8f1d3-7b64-4e09-a5d2-8f3c1b6e9a47", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/garden-environment/c2a8f1d3-7b64-4e09-a5d2-8f3c1b6e9a47?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/garden-environment/c2a8f1d3-7b64-4e09-a5d2-8f3c1b6e9a47", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/garden-environment/c2a8f1d3-7b64-4e09-a5d2-8f3c1b6e9a47?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "message": "Environment with ID c2a8f1d3-7b64-4e09-a5d2-8f3c1b6e9a47 deleted successfully"
}
GETapi.aquabotany.com/sensor-data

Submit or read garden sensor readings.

ParameterInDescription
deviceIdqueryoptional Restrict to one device serial.
startKeyqueryoptional Opaque pagination token from the previous page's nextKey. Pass it back verbatim; a nextKey of null means the last page.
curl https://api.aquabotany.com/v1/sensor-data?deviceId=SN-00142 \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/sensor-data?deviceId=SN-00142",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/sensor-data?deviceId=SN-00142", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/sensor-data?deviceId=SN-00142", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "Items": [
    {
      "deviceId": "SN-00142",
      "ts": 1783503000,
      "sensorType": "moisture",
      "value": 42.0
    },
    {
      "deviceId": "SN-00142",
      "ts": 1783499400,
      "sensorType": "temp_c",
      "value": 21.5
    }
  ],
  "nextKey": null
}
POSTapi.aquabotany.com/sensor-data

Submit or read garden sensor readings.

curl https://api.aquabotany.com/v1/sensor-data \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"serial": "SN-00142", "sensordata": [{"timestamp": 1783674000, "type": "soilmoisture", "value": 41.5}, {"timestamp": 1783674600, "type": "temp", "value": 22.4}]}'
import requests

resp = requests.post(
    "https://api.aquabotany.com/v1/sensor-data",
    headers={"x-api-key": API_KEY},
    json={'serial': 'SN-00142', 'sensordata': [{'timestamp': 1783674000, 'type': 'soilmoisture', 'value': 41.5}, {'timestamp': 1783674600, 'type': 'temp', 'value': 22.4}]},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/sensor-data", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"serial": "SN-00142", "sensordata": [{"timestamp": 1783674000, "type": "soilmoisture", "value": 41.5}, {"timestamp": 1783674600, "type": "temp", "value": 22.4}]})
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/sensor-data", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"serial": "SN-00142", "sensordata": [{"timestamp": 1783674000, "type": "soilmoisture", "value": 41.5}, {"timestamp": 1783674600, "type": "temp", "value": 22.4}]})
});
const data = await resp.json();
202 Response
{
  "msg": "Accepted 2 readings 🌱"
}

Tasks & reminders

Base URL https://api.aquabotany.com/v1

GETapi.aquabotany.com/reminders

Reminders (legacy collection).

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/reminders \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be "*" (read across every platform)
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/reminders?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/reminders",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/reminders",
    headers={"x-api-key": API_KEY},
    # platform can also be "*" (read across every platform)
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/reminders", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/reminders?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/reminders", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/reminders?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
[
  {
    "reminderId": "9f2c4b7d8a1e4f60b3c5d7e9a2f14c86",
    "reminderText": "Water the ferns",
    "dueEpoch": 1783674000,
    "status": "PENDING",
    "created": "2026-07-08T09:15:32+00:00",
    "lastModified": "2026-07-08T09:15:32+00:00"
  },
  {
    "reminderId": "4a8e1c6f2b9d4073a5e8c1f6d2b94037",
    "reminderText": "Check drip lines",
    "dueEpoch": 1783760400,
    "status": "PENDING",
    "created": "2026-07-08T09:20:04+00:00",
    "lastModified": "2026-07-08T09:20:04+00:00"
  }
]
POSTapi.aquabotany.com/reminders

Reminders (legacy collection).

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/reminders \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"reminderText": "Water the ferns", "dueDateTime": "2026-07-10T09:00:00Z"}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/reminders \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"reminderText": "Water the ferns", "dueDateTime": "2026-07-10T09:00:00Z", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.post(
    "https://api.aquabotany.com/v1/reminders",
    headers={"x-api-key": API_KEY},
    json={'reminderText': 'Water the ferns', 'dueDateTime': '2026-07-10T09:00:00Z'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.post(
    "https://api.aquabotany.com/v1/reminders",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'reminderText': 'Water the ferns', 'dueDateTime': '2026-07-10T09:00:00Z', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/reminders", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"reminderText": "Water the ferns", "dueDateTime": "2026-07-10T09:00:00Z"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/reminders", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"reminderText": "Water the ferns", "dueDateTime": "2026-07-10T09:00:00Z", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/reminders", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"reminderText": "Water the ferns", "dueDateTime": "2026-07-10T09:00:00Z"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/reminders", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"reminderText": "Water the ferns", "dueDateTime": "2026-07-10T09:00:00Z", "platform": "my_garden"})
});
201 Response
{
  "reminderId": "9f2c4b7d8a1e4f60b3c5d7e9a2f14c86"
}
GETapi.aquabotany.com/reminders/{id}

Read, update, or delete a reminder.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/reminders/9f2c4b7d8a1e4f60b3c5d7e9a2f14c86 \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform must match where the item lives: a project name
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/reminders/9f2c4b7d8a1e4f60b3c5d7e9a2f14c86?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/reminders/9f2c4b7d8a1e4f60b3c5d7e9a2f14c86",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/reminders/9f2c4b7d8a1e4f60b3c5d7e9a2f14c86",
    headers={"x-api-key": API_KEY},
    # platform must match where the item lives: a project name
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/reminders/9f2c4b7d8a1e4f60b3c5d7e9a2f14c86", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform must match where the item lives: a project name
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/reminders/9f2c4b7d8a1e4f60b3c5d7e9a2f14c86?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/reminders/9f2c4b7d8a1e4f60b3c5d7e9a2f14c86", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform must match where the item lives: a project name
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/reminders/9f2c4b7d8a1e4f60b3c5d7e9a2f14c86?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "reminderId": "9f2c4b7d8a1e4f60b3c5d7e9a2f14c86",
  "reminderText": "Water the ferns",
  "dueEpoch": 1783674000,
  "status": "PENDING",
  "created": "2026-07-08T09:15:32+00:00",
  "lastModified": "2026-07-08T09:15:32+00:00"
}
PUTapi.aquabotany.com/reminders/{id}

Read, update, or delete a reminder.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/reminders/9f2c4b7d8a1e4f60b3c5d7e9a2f14c86 \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"reminderText": "Water the ferns and orchids", "status": "COMPLETED"}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/reminders/9f2c4b7d8a1e4f60b3c5d7e9a2f14c86 \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"reminderText": "Water the ferns and orchids", "status": "COMPLETED", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.put(
    "https://api.aquabotany.com/v1/reminders/9f2c4b7d8a1e4f60b3c5d7e9a2f14c86",
    headers={"x-api-key": API_KEY},
    json={'reminderText': 'Water the ferns and orchids', 'status': 'COMPLETED'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.put(
    "https://api.aquabotany.com/v1/reminders/9f2c4b7d8a1e4f60b3c5d7e9a2f14c86",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'reminderText': 'Water the ferns and orchids', 'status': 'COMPLETED', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/reminders/9f2c4b7d8a1e4f60b3c5d7e9a2f14c86", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"reminderText": "Water the ferns and orchids", "status": "COMPLETED"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/reminders/9f2c4b7d8a1e4f60b3c5d7e9a2f14c86", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"reminderText": "Water the ferns and orchids", "status": "COMPLETED", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/reminders/9f2c4b7d8a1e4f60b3c5d7e9a2f14c86", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"reminderText": "Water the ferns and orchids", "status": "COMPLETED"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/reminders/9f2c4b7d8a1e4f60b3c5d7e9a2f14c86", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"reminderText": "Water the ferns and orchids", "status": "COMPLETED", "platform": "my_garden"})
});
200 Response
{
  "reminderId": "9f2c4b7d8a1e4f60b3c5d7e9a2f14c86",
  "reminderText": "Water the ferns and orchids",
  "dueEpoch": 1783674000,
  "status": "COMPLETED",
  "created": "2026-07-08T09:15:32+00:00",
  "lastModified": "2026-07-08T11:42:05+00:00"
}
DELETEapi.aquabotany.com/reminders/{id}

Read, update, or delete a reminder.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/reminders/9f2c4b7d8a1e4f60b3c5d7e9a2f14c86 \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl "https://api.aquabotany.com/v1/reminders/9f2c4b7d8a1e4f60b3c5d7e9a2f14c86?platform=my_garden" \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.delete(
    "https://api.aquabotany.com/v1/reminders/9f2c4b7d8a1e4f60b3c5d7e9a2f14c86",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.delete(
    "https://api.aquabotany.com/v1/reminders/9f2c4b7d8a1e4f60b3c5d7e9a2f14c86",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/reminders/9f2c4b7d8a1e4f60b3c5d7e9a2f14c86", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/reminders/9f2c4b7d8a1e4f60b3c5d7e9a2f14c86?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/reminders/9f2c4b7d8a1e4f60b3c5d7e9a2f14c86", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/reminders/9f2c4b7d8a1e4f60b3c5d7e9a2f14c86?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
204 Response
(no response body)
GETapi.aquabotany.com/tasks

List gardening tasks.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/tasks \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be "*" (read across every platform)
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/tasks?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/tasks",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/tasks",
    headers={"x-api-key": API_KEY},
    # platform can also be "*" (read across every platform)
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/tasks", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/tasks?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/tasks", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/tasks?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
[
  {
    "task_id": "d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39",
    "action_type": "water",
    "source": "manual",
    "details": {
      "plant": "Tomatoes",
      "amount_ml": 500
    },
    "schedule_time": "2026-07-10T09:00:00Z",
    "created_at": "2026-07-08T09:15:32.418206",
    "completed": false
  },
  {
    "task_id": "a8c3e6f1-9d24-4b7e-8f05-3a6c9e2d5b71",
    "action_type": "fertilize",
    "source": "assistant",
    "details": {},
    "schedule_time": null,
    "created_at": "2026-07-07T18:40:11.203984",
    "completed": true
  }
]
POSTapi.aquabotany.com/tasks

Create a gardening task.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/tasks \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"action_type": "water", "source": "manual", "schedule_time": "2026-07-10T09:00:00Z", "details": {"plant": "Tomatoes", "amount_ml": 500}}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/tasks \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"action_type": "water", "source": "manual", "schedule_time": "2026-07-10T09:00:00Z", "details": {"plant": "Tomatoes", "amount_ml": 500}, "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.post(
    "https://api.aquabotany.com/v1/tasks",
    headers={"x-api-key": API_KEY},
    json={'action_type': 'water', 'source': 'manual', 'schedule_time': '2026-07-10T09:00:00Z', 'details': {'plant': 'Tomatoes', 'amount_ml': 500}},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.post(
    "https://api.aquabotany.com/v1/tasks",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'action_type': 'water', 'source': 'manual', 'schedule_time': '2026-07-10T09:00:00Z', 'details': {'plant': 'Tomatoes', 'amount_ml': 500}, 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/tasks", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"action_type": "water", "source": "manual", "schedule_time": "2026-07-10T09:00:00Z", "details": {"plant": "Tomatoes", "amount_ml": 500}})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/tasks", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"action_type": "water", "source": "manual", "schedule_time": "2026-07-10T09:00:00Z", "details": {"plant": "Tomatoes", "amount_ml": 500}, "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/tasks", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"action_type": "water", "source": "manual", "schedule_time": "2026-07-10T09:00:00Z", "details": {"plant": "Tomatoes", "amount_ml": 500}})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/tasks", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"action_type": "water", "source": "manual", "schedule_time": "2026-07-10T09:00:00Z", "details": {"plant": "Tomatoes", "amount_ml": 500}, "platform": "my_garden"})
});
200 Response
{
  "message": "Task created successfully",
  "task_id": "d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39"
}
POSTapi.aquabotany.com/tasks/completed

Mark a gardening task completed.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/tasks/completed \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"task_id": "d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39", "source": "manual"}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/tasks/completed \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"task_id": "d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39", "source": "manual", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.post(
    "https://api.aquabotany.com/v1/tasks/completed",
    headers={"x-api-key": API_KEY},
    json={'task_id': 'd1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39', 'source': 'manual'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.post(
    "https://api.aquabotany.com/v1/tasks/completed",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'task_id': 'd1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39', 'source': 'manual', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/tasks/completed", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"task_id": "d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39", "source": "manual"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/tasks/completed", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"task_id": "d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39", "source": "manual", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/tasks/completed", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"task_id": "d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39", "source": "manual"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/tasks/completed", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"task_id": "d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39", "source": "manual", "platform": "my_garden"})
});
200 Response
{
  "status": "completed"
}
GETapi.aquabotany.com/tasks/reminders

List the caller's reminders, soonest-due first.

ParameterInDescription
statusqueryoptional GET filter: scheduled|done|cancelled.
limitqueryoptional GET: max results (default 50, max 200).
idqueryoptional DELETE: id of the reminder to delete.
# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/tasks/reminders \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be "*" (read across every platform)
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/tasks/reminders?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/tasks/reminders",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/tasks/reminders",
    headers={"x-api-key": API_KEY},
    # platform can also be "*" (read across every platform)
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/tasks/reminders", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/tasks/reminders?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/tasks/reminders", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/tasks/reminders?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "count": 2,
  "reminders": [
    {
      "id": "5b8e2d4a-7c31-49f6-a2d8-0e6b3f9c1a57",
      "title": "Prune roses",
      "notes": "morning",
      "due_at": "2026-07-10T09:00:00+00:00",
      "created_at": "2026-07-08T09:15:32.418206+00:00",
      "updated_at": "2026-07-08T09:15:32.418206+00:00",
      "status": "scheduled"
    },
    {
      "id": "1d7f3a92-6e48-4c05-9b2a-e5c803d61f74",
      "title": "Fertilize tomatoes",
      "notes": "",
      "due_at": "2026-07-12T15:30:00+00:00",
      "created_at": "2026-07-08T09:20:04.771203+00:00",
      "updated_at": "2026-07-08T09:20:04.771203+00:00",
      "status": "scheduled"
    }
  ]
}
POSTapi.aquabotany.com/tasks/reminders

Create a reminder.

ParameterInDescription
statusqueryoptional GET filter: scheduled|done|cancelled.
limitqueryoptional GET: max results (default 50, max 200).
idqueryoptional DELETE: id of the reminder to delete.
# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/tasks/reminders \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Prune roses", "due_at": "2026-07-10T09:00:00Z", "notes": "morning"}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/tasks/reminders \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Prune roses", "due_at": "2026-07-10T09:00:00Z", "notes": "morning", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.post(
    "https://api.aquabotany.com/v1/tasks/reminders",
    headers={"x-api-key": API_KEY},
    json={'title': 'Prune roses', 'due_at': '2026-07-10T09:00:00Z', 'notes': 'morning'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.post(
    "https://api.aquabotany.com/v1/tasks/reminders",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'title': 'Prune roses', 'due_at': '2026-07-10T09:00:00Z', 'notes': 'morning', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/tasks/reminders", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"title": "Prune roses", "due_at": "2026-07-10T09:00:00Z", "notes": "morning"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/tasks/reminders", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"title": "Prune roses", "due_at": "2026-07-10T09:00:00Z", "notes": "morning", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/tasks/reminders", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"title": "Prune roses", "due_at": "2026-07-10T09:00:00Z", "notes": "morning"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/tasks/reminders", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"title": "Prune roses", "due_at": "2026-07-10T09:00:00Z", "notes": "morning", "platform": "my_garden"})
});
201 Response
{
  "reminder": {
    "id": "5b8e2d4a-7c31-49f6-a2d8-0e6b3f9c1a57",
    "title": "Prune roses",
    "notes": "morning",
    "due_at": "2026-07-10T09:00:00+00:00",
    "created_at": "2026-07-08T09:15:32.418206+00:00",
    "updated_at": "2026-07-08T09:15:32.418206+00:00",
    "status": "scheduled"
  }
}
PATCHapi.aquabotany.com/tasks/reminders

Update a reminder (including marking it done).

ParameterInDescription
statusqueryoptional GET filter: scheduled|done|cancelled.
limitqueryoptional GET: max results (default 50, max 200).
idqueryoptional DELETE: id of the reminder to delete.
# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/tasks/reminders \
  -X PATCH \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"id": "5b8e2d4a-7c31-49f6-a2d8-0e6b3f9c1a57", "status": "done"}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/tasks/reminders \
  -X PATCH \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"id": "5b8e2d4a-7c31-49f6-a2d8-0e6b3f9c1a57", "status": "done", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.patch(
    "https://api.aquabotany.com/v1/tasks/reminders",
    headers={"x-api-key": API_KEY},
    json={'id': '5b8e2d4a-7c31-49f6-a2d8-0e6b3f9c1a57', 'status': 'done'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.patch(
    "https://api.aquabotany.com/v1/tasks/reminders",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'id': '5b8e2d4a-7c31-49f6-a2d8-0e6b3f9c1a57', 'status': 'done', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/tasks/reminders", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"id": "5b8e2d4a-7c31-49f6-a2d8-0e6b3f9c1a57", "status": "done"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/tasks/reminders", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"id": "5b8e2d4a-7c31-49f6-a2d8-0e6b3f9c1a57", "status": "done", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/tasks/reminders", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"id": "5b8e2d4a-7c31-49f6-a2d8-0e6b3f9c1a57", "status": "done"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/tasks/reminders", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"id": "5b8e2d4a-7c31-49f6-a2d8-0e6b3f9c1a57", "status": "done", "platform": "my_garden"})
});
200 Response
{
  "reminder": {
    "id": "5b8e2d4a-7c31-49f6-a2d8-0e6b3f9c1a57",
    "title": "Prune roses",
    "notes": "morning",
    "due_at": "2026-07-10T09:00:00+00:00",
    "created_at": "2026-07-08T09:15:32.418206+00:00",
    "updated_at": "2026-07-08T11:42:05.771203+00:00",
    "status": "done"
  }
}
DELETEapi.aquabotany.com/tasks/reminders

Delete a reminder (by ?id=).

ParameterInDescription
statusqueryoptional GET filter: scheduled|done|cancelled.
limitqueryoptional GET: max results (default 50, max 200).
idqueryoptional DELETE: id of the reminder to delete.
# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/tasks/reminders?id=5b8e2d4a-7c31-49f6-a2d8-0e6b3f9c1a57 \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl "https://api.aquabotany.com/v1/tasks/reminders?id=5b8e2d4a-7c31-49f6-a2d8-0e6b3f9c1a57&platform=my_garden" \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.delete(
    "https://api.aquabotany.com/v1/tasks/reminders?id=5b8e2d4a-7c31-49f6-a2d8-0e6b3f9c1a57",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.delete(
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    "https://api.aquabotany.com/v1/tasks/reminders?id=5b8e2d4a-7c31-49f6-a2d8-0e6b3f9c1a57&platform=my_garden",
    headers={"x-api-key": API_KEY},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/tasks/reminders?id=5b8e2d4a-7c31-49f6-a2d8-0e6b3f9c1a57", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/tasks/reminders?id=5b8e2d4a-7c31-49f6-a2d8-0e6b3f9c1a57&platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/tasks/reminders?id=5b8e2d4a-7c31-49f6-a2d8-0e6b3f9c1a57", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/tasks/reminders?id=5b8e2d4a-7c31-49f6-a2d8-0e6b3f9c1a57&platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "deleted": "5b8e2d4a-7c31-49f6-a2d8-0e6b3f9c1a57"
}
GETapi.aquabotany.com/tasks/scheduled

Scheduled (upcoming) tasks.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/tasks/scheduled?start_date=2026-07-08&end_date=2026-07-15 \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be "*" (read across every platform)
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/tasks/scheduled?start_date=2026-07-08&end_date=2026-07-15&platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/tasks/scheduled?start_date=2026-07-08&end_date=2026-07-15",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    # platform can also be "*" (read across every platform)
    # or the Aqua Botany product slug "bb_aquabotany"
    "https://api.aquabotany.com/v1/tasks/scheduled?start_date=2026-07-08&end_date=2026-07-15&platform=my_garden",
    headers={"x-api-key": API_KEY},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/tasks/scheduled?start_date=2026-07-08&end_date=2026-07-15", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/tasks/scheduled?start_date=2026-07-08&end_date=2026-07-15&platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/tasks/scheduled?start_date=2026-07-08&end_date=2026-07-15", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/tasks/scheduled?start_date=2026-07-08&end_date=2026-07-15&platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
[
  {
    "task_id": "d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39",
    "action_type": "water",
    "source": "manual",
    "details": {
      "plant": "Tomatoes",
      "amount_ml": 500
    },
    "schedule_time": "2026-07-10T09:00:00Z",
    "created_at": "2026-07-08T09:15:32.418206",
    "completed": false
  }
]
GETapi.aquabotany.com/tasks/{task_id}

Get a single task. Supports ?expand=links to inline this record's links in the same call - see Link System.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/tasks/d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39 \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform must match where the item lives: a project name
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/tasks/d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/tasks/d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/tasks/d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39",
    headers={"x-api-key": API_KEY},
    # platform must match where the item lives: a project name
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/tasks/d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform must match where the item lives: a project name
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/tasks/d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/tasks/d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform must match where the item lives: a project name
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/tasks/d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "task_id": "d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39",
  "action_type": "water",
  "source": "manual",
  "details": {
    "plant": "Tomatoes",
    "amount_ml": 500
  },
  "schedule_time": "2026-07-10T09:00:00Z",
  "created_at": "2026-07-08T09:15:32.418206",
  "completed": false
}
PUTapi.aquabotany.com/tasks/{task_id}

Update a task's fields.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/tasks/d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39 \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"action_type": "water", "schedule_time": "2026-07-10T18:00:00Z"}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/tasks/d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39 \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"action_type": "water", "schedule_time": "2026-07-10T18:00:00Z", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.put(
    "https://api.aquabotany.com/v1/tasks/d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39",
    headers={"x-api-key": API_KEY},
    json={'action_type': 'water', 'schedule_time': '2026-07-10T18:00:00Z'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.put(
    "https://api.aquabotany.com/v1/tasks/d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'action_type': 'water', 'schedule_time': '2026-07-10T18:00:00Z', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/tasks/d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"action_type": "water", "schedule_time": "2026-07-10T18:00:00Z"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/tasks/d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"action_type": "water", "schedule_time": "2026-07-10T18:00:00Z", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/tasks/d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"action_type": "water", "schedule_time": "2026-07-10T18:00:00Z"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/tasks/d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"action_type": "water", "schedule_time": "2026-07-10T18:00:00Z", "platform": "my_garden"})
});
200 Response
{
  "status": "updated"
}
PATCHapi.aquabotany.com/tasks/{task_id}

Update a task's fields (alias of PUT).

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/tasks/d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39 \
  -X PATCH \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"schedule_time": "2026-07-10T18:00:00Z"}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/tasks/d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39 \
  -X PATCH \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"schedule_time": "2026-07-10T18:00:00Z", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.patch(
    "https://api.aquabotany.com/v1/tasks/d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39",
    headers={"x-api-key": API_KEY},
    json={'schedule_time': '2026-07-10T18:00:00Z'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.patch(
    "https://api.aquabotany.com/v1/tasks/d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'schedule_time': '2026-07-10T18:00:00Z', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/tasks/d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"schedule_time": "2026-07-10T18:00:00Z"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/tasks/d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"schedule_time": "2026-07-10T18:00:00Z", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/tasks/d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"schedule_time": "2026-07-10T18:00:00Z"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/tasks/d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"schedule_time": "2026-07-10T18:00:00Z", "platform": "my_garden"})
});
200 Response
{
  "status": "updated"
}
DELETEapi.aquabotany.com/tasks/{task_id}

Delete a task.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/tasks/d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39 \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl "https://api.aquabotany.com/v1/tasks/d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39?platform=my_garden" \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.delete(
    "https://api.aquabotany.com/v1/tasks/d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.delete(
    "https://api.aquabotany.com/v1/tasks/d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/tasks/d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/tasks/d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/tasks/d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/tasks/d1a5f8e2-3c7b-4a90-b6d4-8e2f5c1a7b39?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "status": "deleted"
}

Community

Base URL https://api.aquabotany.com/v1

GETapi.aquabotany.com/articles

List community articles.

Rows omit content, so a browse stays cheap; read one article by id for its body.

ParameterInDescription
platformqueryOptional. Namespace to read. Omitted means your default namespace; * reads across every platform you have.
categoryqueryOptional. Exact match on the article's category. Category is required at create time, so every article has one.
sincequeryOptional. ISO timestamp. Returns articles created at or after it.
authorIdqueryOptional. One author. Accepts either the opaque authorId returned in responses or the author's login email.
limitqueryOptional. Rows per page, 1 to 1000. Defaults to 20.
startKeyqueryOptional. The nextKey from a previous response, to fetch the next page.

Filters are applied before the page is cut, so a page holds limit matching rows and nextKey refers to the filtered sequence. Before 2026-09-06 the order was reversed: asking for 20 could return 3, and a caller walking pages could not tell a thin page from the end of the data. Clients written against the old behavior keep working, they simply stop seeing short pages.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/articles \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be "*" (read across every platform)
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/articles?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"

# One category, filtered server side
curl "https://api.aquabotany.com/v1/articles?platform=my_garden&category=herbs&limit=50" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/articles",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/articles",
    headers={"x-api-key": API_KEY},
    # platform can also be "*" (read across every platform)
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)

# One category, filtered server side
resp = requests.get(
    "https://api.aquabotany.com/v1/articles",
    headers={"x-api-key": API_KEY},
    params={"platform": "my_garden", "category": "herbs", "limit": 50},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/articles", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/articles?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});

// One category, filtered server side
const herbs = await fetch(
  "https://api.aquabotany.com/v1/articles?platform=my_garden&category=herbs&limit=50",
  { method: "GET", headers: { "x-api-key": API_KEY } }
);
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/articles", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/articles?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});

// One category, filtered server side
const herbs = await fetch(
  "https://api.aquabotany.com/v1/articles?platform=my_garden&category=herbs&limit=50",
  { method: "GET", headers: { "x-api-key": API_KEY } }
);
200 Response
{
  "items": [
    {
      "articleId": "e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83",
      "title": "My tomato season",
      "category": "vegetables",
      "thumbnail": "https://cdn.aquabotany.com/thumbs/tomato-season.jpg",
      "authorId": "u_9f3a1c7b8e2d0a4f", "isYou": true,
      "created": "2026-07-08T13:05:41Z",
      "likeCount": 3,
      "shareCount": 1,
      "commentCount": 2
    },
    {
      "articleId": "b7d3f0c2-4a1e-4f6b-9c8d-2e5a7f0b3c61",
      "title": "Overwintering rosemary indoors",
      "category": "herbs",
      "thumbnail": null,
      "authorId": "u_1a2b3c4d5e6f7a8b", "isYou": false,
      "created": "2026-07-06T09:41:33Z",
      "likeCount": 5,
      "shareCount": 2,
      "commentCount": 4
    }
  ],
  "nextKey": null
}
POSTapi.aquabotany.com/articles

Publish an article.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/articles \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "My tomato season", "content": "How I staked, pruned, and watered my way to a July harvest.", "category": "vegetables", "thumbnail": "https://cdn.aquabotany.com/thumbs/tomato-season.jpg"}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/articles \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "My tomato season", "content": "How I staked, pruned, and watered my way to a July harvest.", "category": "vegetables", "thumbnail": "https://cdn.aquabotany.com/thumbs/tomato-season.jpg", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.post(
    "https://api.aquabotany.com/v1/articles",
    headers={"x-api-key": API_KEY},
    json={'title': 'My tomato season', 'content': 'How I staked, pruned, and watered my way to a July harvest.', 'category': 'vegetables', 'thumbnail': 'https://cdn.aquabotany.com/thumbs/tomato-season.jpg'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.post(
    "https://api.aquabotany.com/v1/articles",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'title': 'My tomato season', 'content': 'How I staked, pruned, and watered my way to a July harvest.', 'category': 'vegetables', 'thumbnail': 'https://cdn.aquabotany.com/thumbs/tomato-season.jpg', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/articles", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"title": "My tomato season", "content": "How I staked, pruned, and watered my way to a July harvest.", "category": "vegetables", "thumbnail": "https://cdn.aquabotany.com/thumbs/tomato-season.jpg"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/articles", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"title": "My tomato season", "content": "How I staked, pruned, and watered my way to a July harvest.", "category": "vegetables", "thumbnail": "https://cdn.aquabotany.com/thumbs/tomato-season.jpg", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/articles", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"title": "My tomato season", "content": "How I staked, pruned, and watered my way to a July harvest.", "category": "vegetables", "thumbnail": "https://cdn.aquabotany.com/thumbs/tomato-season.jpg"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/articles", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"title": "My tomato season", "content": "How I staked, pruned, and watered my way to a July harvest.", "category": "vegetables", "thumbnail": "https://cdn.aquabotany.com/thumbs/tomato-season.jpg", "platform": "my_garden"})
});
201 Response
{
  "articleId": "e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83"
}
GETapi.aquabotany.com/articles/{articleId}

Read, update, or delete an article. Supports ?expand=links to inline this record's links in the same call - see Link System.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83 \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform must match where the item lives: a project name
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83",
    headers={"x-api-key": API_KEY},
    # platform must match where the item lives: a project name
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform must match where the item lives: a project name
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform must match where the item lives: a project name
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "articleId": "e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83",
  "title": "My tomato season",
  "content": "How I staked, pruned, and watered my way to a July harvest.",
  "category": "vegetables",
  "thumbnail": "https://cdn.aquabotany.com/thumbs/tomato-season.jpg",
  "authorId": "u_9f3a1c7b8e2d0a4f", "isYou": true,
  "created": "2026-07-08T13:05:41Z",
  "likeCount": 3,
  "shareCount": 1,
  "commentCount": 2
}
PUTapi.aquabotany.com/articles/{articleId}

Read, update, or delete an article.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83 \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "My tomato season, revised"}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83 \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "My tomato season, revised", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.put(
    "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83",
    headers={"x-api-key": API_KEY},
    json={'title': 'My tomato season, revised'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.put(
    "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'title': 'My tomato season, revised', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"title": "My tomato season, revised"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"title": "My tomato season, revised", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"title": "My tomato season, revised"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"title": "My tomato season, revised", "platform": "my_garden"})
});
200 Response
{
  "message": "updated"
}
PATCHapi.aquabotany.com/articles/{articleId}

Read, update, or delete an article.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83 \
  -X PATCH \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"content": "Updated text"}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83 \
  -X PATCH \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"content": "Updated text", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.patch(
    "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83",
    headers={"x-api-key": API_KEY},
    json={'content': 'Updated text'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.patch(
    "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'content': 'Updated text', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"content": "Updated text"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"content": "Updated text", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"content": "Updated text"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"content": "Updated text", "platform": "my_garden"})
});
200 Response
{
  "message": "updated"
}
DELETEapi.aquabotany.com/articles/{articleId}

Read, update, or delete an article.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83 \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83?platform=my_garden" \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.delete(
    "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.delete(
    "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
204 Response
{}
GETapi.aquabotany.com/articles/{articleId}/comments

Comments on an article.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/comments \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be "*" (read across every platform)
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/comments?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/comments",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/comments",
    headers={"x-api-key": API_KEY},
    # platform can also be "*" (read across every platform)
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/comments", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/comments?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/comments", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/comments?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "items": [
    {
      "commentId": "5f8e2b1a-7c4d-4b9e-8a3f-6d1c0e9b5a27",
      "articleId": "e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83",
      "userId": "u_1a2b3c4d5e6f7a8b", "isYou": false,
      "body": "Great tips!",
      "created": "2026-07-08T14:12:09Z",
      "edited": false
    },
    {
      "commentId": "0c3d7a9e-2f5b-4d8c-b1e6-4a7f9c2d5e80",
      "articleId": "e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83",
      "userId": "u_9f3a1c7b8e2d0a4f", "isYou": true,
      "body": "Which variety did you grow?",
      "created": "2026-07-08T15:03:44Z",
      "edited": false
    }
  ],
  "nextKey": null
}
POSTapi.aquabotany.com/articles/{articleId}/comments

Comments on an article.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/comments \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"body": "Great tips!"}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/comments \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"body": "Great tips!", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.post(
    "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/comments",
    headers={"x-api-key": API_KEY},
    json={'body': 'Great tips!'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.post(
    "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/comments",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'body': 'Great tips!', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/comments", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"body": "Great tips!"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/comments", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"body": "Great tips!", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/comments", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"body": "Great tips!"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/comments", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"body": "Great tips!", "platform": "my_garden"})
});
201 Response
{
  "commentId": "5f8e2b1a-7c4d-4b9e-8a3f-6d1c0e9b5a27"
}
PATCHapi.aquabotany.com/articles/{articleId}/comments/{commentId}

Update or delete a comment.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/comments/5f8e2b1a-7c4d-4b9e-8a3f-6d1c0e9b5a27 \
  -X PATCH \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"body": "Great tips! Edited."}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/comments/5f8e2b1a-7c4d-4b9e-8a3f-6d1c0e9b5a27 \
  -X PATCH \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"body": "Great tips! Edited.", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.patch(
    "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/comments/5f8e2b1a-7c4d-4b9e-8a3f-6d1c0e9b5a27",
    headers={"x-api-key": API_KEY},
    json={'body': 'Great tips! Edited.'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.patch(
    "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/comments/5f8e2b1a-7c4d-4b9e-8a3f-6d1c0e9b5a27",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'body': 'Great tips! Edited.', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/comments/5f8e2b1a-7c4d-4b9e-8a3f-6d1c0e9b5a27", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"body": "Great tips! Edited."})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/comments/5f8e2b1a-7c4d-4b9e-8a3f-6d1c0e9b5a27", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"body": "Great tips! Edited.", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/comments/5f8e2b1a-7c4d-4b9e-8a3f-6d1c0e9b5a27", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"body": "Great tips! Edited."})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/comments/5f8e2b1a-7c4d-4b9e-8a3f-6d1c0e9b5a27", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"body": "Great tips! Edited.", "platform": "my_garden"})
});
200 Response
{
  "message": "updated"
}
DELETEapi.aquabotany.com/articles/{articleId}/comments/{commentId}

Update or delete a comment.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/comments/5f8e2b1a-7c4d-4b9e-8a3f-6d1c0e9b5a27 \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/comments/5f8e2b1a-7c4d-4b9e-8a3f-6d1c0e9b5a27?platform=my_garden" \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.delete(
    "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/comments/5f8e2b1a-7c4d-4b9e-8a3f-6d1c0e9b5a27",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.delete(
    "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/comments/5f8e2b1a-7c4d-4b9e-8a3f-6d1c0e9b5a27",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/comments/5f8e2b1a-7c4d-4b9e-8a3f-6d1c0e9b5a27", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/comments/5f8e2b1a-7c4d-4b9e-8a3f-6d1c0e9b5a27?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/comments/5f8e2b1a-7c4d-4b9e-8a3f-6d1c0e9b5a27", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/comments/5f8e2b1a-7c4d-4b9e-8a3f-6d1c0e9b5a27?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
204 Response
{}
GETapi.aquabotany.com/articles/{articleId}/likes

Likes on an article.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/likes \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be "*" (read across every platform)
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/likes?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/likes",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/likes",
    headers={"x-api-key": API_KEY},
    # platform can also be "*" (read across every platform)
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/likes", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/likes?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/likes", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/likes?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "items": [
    {
      "userId": "u_1a2b3c4d5e6f7a8b", "isYou": false,
      "articleId": "e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83",
      "likedAt": "2026-07-08T13:47:20Z"
    },
    {
      "userId": "u_9f3a1c7b8e2d0a4f", "isYou": true,
      "articleId": "e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83",
      "likedAt": "2026-07-08T14:30:52Z"
    }
  ],
  "nextKey": null
}
POSTapi.aquabotany.com/articles/{articleId}/likes

Like an article.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/likes \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"platform": "my_garden"}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/likes \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"platform": "my_garden", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.post(
    "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/likes",
    headers={"x-api-key": API_KEY},
    json={'platform': 'my_garden'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.post(
    "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/likes",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'platform': 'my_garden', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/likes", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"platform": "my_garden"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/likes", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"platform": "my_garden", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/likes", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"platform": "my_garden"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/likes", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"platform": "my_garden", "platform": "my_garden"})
});
201 Response
{}
DELETEapi.aquabotany.com/articles/{articleId}/likes

Remove a like.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/likes \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/likes?platform=my_garden" \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.delete(
    "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/likes",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.delete(
    "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/likes",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/likes", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/likes?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/likes", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/likes?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
204 Response
{}
GETapi.aquabotany.com/articles/{articleId}/shares

Shares of an article.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/shares \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be "*" (read across every platform)
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/shares?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/shares",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/shares",
    headers={"x-api-key": API_KEY},
    # platform can also be "*" (read across every platform)
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/shares", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/shares?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/shares", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/shares?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "items": [
    {
      "userId": "u_9f3a1c7b8e2d0a4f", "isYou": true,
      "articleId": "e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83",
      "sharedAt": "2026-07-08T14:35:10Z"
    }
  ]
}
POSTapi.aquabotany.com/articles/{articleId}/shares

Shares of an article.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/shares \
  -X POST \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/shares?platform=my_garden" \
  -X POST \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.post(
    "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/shares",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.post(
    "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/shares",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/shares", {
  method: "POST",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/shares?platform=my_garden", {
  method: "POST",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/shares", {
  method: "POST",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/shares?platform=my_garden", {
  method: "POST",
  headers: { "x-api-key": API_KEY }
});
201 Response
{}
GETapi.aquabotany.com/articles/{articleId}/stats

Engagement stats for an article.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/stats \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform must match where the item lives: a project name
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/stats?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/stats",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/stats",
    headers={"x-api-key": API_KEY},
    # platform must match where the item lives: a project name
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/stats", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform must match where the item lives: a project name
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/stats?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/stats", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform must match where the item lives: a project name
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/articles/e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83/stats?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "likeCount": 3,
  "shareCount": 1,
  "commentCount": 2
}
GETapi.aquabotany.com/comments

Recent comments across articles.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/comments \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be "*" (read across every platform)
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/comments?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/comments",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/comments",
    headers={"x-api-key": API_KEY},
    # platform can also be "*" (read across every platform)
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/comments", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/comments?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/comments", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/comments?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "items": [
    {
      "commentId": "0c3d7a9e-2f5b-4d8c-b1e6-4a7f9c2d5e80",
      "articleId": "e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83",
      "userId": "u_9f3a1c7b8e2d0a4f", "isYou": true,
      "body": "Which variety did you grow?",
      "created": "2026-07-08T15:03:44Z",
      "edited": false
    }
  ],
  "nextKey": null
}
GETapi.aquabotany.com/feed

The community activity feed.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/feed \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be "*" (read across every platform)
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/feed?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/feed",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/feed",
    headers={"x-api-key": API_KEY},
    # platform can also be "*" (read across every platform)
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/feed", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/feed?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/feed", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/feed?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "items": [
    {
      "articleId": "e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83",
      "title": "My tomato season",
      "content": "How I staked, pruned, and watered my way to a July harvest.",
      "category": "vegetables",
      "thumbnail": "https://cdn.aquabotany.com/thumbs/tomato-season.jpg",
      "authorId": "u_9f3a1c7b8e2d0a4f", "isYou": true,
      "created": "2026-07-08T13:05:41Z",
      "likeCount": 3,
      "shareCount": 1,
      "commentCount": 2
    },
    {
      "articleId": "b7d3f0c2-4a1e-4f6b-9c8d-2e5a7f0b3c61",
      "title": "Overwintering rosemary indoors",
      "content": "Light, airflow, and restrained watering keep rosemary alive until spring.",
      "category": "herbs",
      "thumbnail": null,
      "authorId": "u_1a2b3c4d5e6f7a8b", "isYou": false,
      "created": "2026-07-06T09:41:33Z",
      "likeCount": 5,
      "shareCount": 2,
      "commentCount": 4
    }
  ],
  "nextKey": null
}
GETapi.aquabotany.com/likes

The caller's likes.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/likes \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be "*" (read across every platform)
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/likes?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/likes",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/likes",
    headers={"x-api-key": API_KEY},
    # platform can also be "*" (read across every platform)
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/likes", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/likes?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/likes", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/likes?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "items": [
    {
      "userId": "u_9f3a1c7b8e2d0a4f", "isYou": true,
      "articleId": "e2a9c4d7-8b3f-4e1a-a6c5-9d0f2b7e4a83",
      "likedAt": "2026-07-08T14:30:52Z"
    }
  ],
  "nextKey": null
}

Gamification

Base URL https://api.aquabotany.com/v1

Catalog visibility and write permissions (events, challenges, rewards). On a Bottle Blue product platform (the first-party app slugs, e.g. bb_aquabotany) these three catalogs are a shared community feed: a read returns every contributor's rows, and creating, updating, or deleting a row requires the admin key (x-admin-key) or it returns 403. Completing a challenge is not a catalog write and needs no admin key: POST and DELETE on /challenges/{challengeId}/complete record your own progress, so every user of the product can take part in a shared challenge independently. On your own project platform (or the default platform) the catalog is private to your account: a list or by-id read returns only rows you created (another account reusing the same platform name sees nothing of yours, and an unknown or foreign id returns 404), and you may create freely but can only edit, complete, or delete a row you created. Another account's row is not addressable from yours at all, so acting on one returns 404, exactly as an unknown id does. An id you supply yourself is scoped to your account on a private platform, so two accounts can each use the same challengeId or rewardId without colliding.
GETapi.aquabotany.com/actions

List your logged actions, newest first.

An action's timestamp is part of its sort key, so since and until are a real keyed range: only the matching rows are read. Use them for a date-bounded history, such as a progress chart or an export of last quarter, rather than paging backwards until you walk past the date you wanted.

ParameterInDescription
platformqueryOptional. Namespace to read. Omitted means your default namespace; * reads across every platform you have.
sincequeryOptional. Inclusive lower bound: YYYY-MM-DD, or a timestamp such as 2026-03-14T09:30:00. A Z or +HH:MM suffix is accepted and converted to UTC, so new Date().toISOString() works as-is. Requires a single platform.
untilqueryOptional. Inclusive upper bound, same format. A bare date covers the whole of that day, not just midnight. Requires a single platform.
limitqueryOptional. Rows per page, 1 to 1000. Defaults to 20.
startKeyqueryOptional. The nextKey from a previous response, to fetch the next page.

The range cannot be combined with platform=*. In the sort key the platform segment sits between the entity and the timestamp, so a range spanning every platform would bound platform names rather than dates. Rather than return a confidently wrong answer the endpoint refuses with 400 RANGE_NEEDS_PLATFORM.

A bound is also refused when it is not a usable date, with a code naming the side that failed: 400 BAD_SINCE or 400 BAD_UNTIL for a value containing # (it would forge sort-key structure), for a malformed value, or for a date that does not exist such as 2026-02-30. 400 BAD_RANGE means since is after until. A non-integer limit is 400 BAD_LIMIT, and a startKey that is not a cursor this endpoint minted is 400 BAD_START_KEY.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/actions \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be "*" (read across every platform)
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/actions?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"

# A date range. Needs a single platform, not "*".
# until is inclusive: this covers all of March 31.
curl "https://api.aquabotany.com/v1/actions?platform=my_garden&since=2026-01-01&until=2026-03-31&limit=200" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/actions",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/actions",
    headers={"x-api-key": API_KEY},
    # platform can also be "*" (read across every platform)
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)

# A date range. Needs a single platform, not "*".
# until is inclusive: this covers all of March 31.
resp = requests.get(
    "https://api.aquabotany.com/v1/actions",
    headers={"x-api-key": API_KEY},
    params={
        "platform": "my_garden",
        "since": "2026-01-01",
        "until": "2026-03-31",
        "limit": 200,
    },
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/actions", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/actions?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});

// A date range. Needs a single platform, not "*".
// until is inclusive: this covers all of March 31.
const range = await fetch(
  "https://api.aquabotany.com/v1/actions?platform=my_garden&since=2026-01-01&until=2026-03-31&limit=200",
  { method: "GET", headers: { "x-api-key": API_KEY } }
);
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/actions", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/actions?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});

// A date range. Needs a single platform, not "*".
// until is inclusive: this covers all of March 31.
const range = await fetch(
  "https://api.aquabotany.com/v1/actions?platform=my_garden&since=2026-01-01&until=2026-03-31&limit=200",
  { method: "GET", headers: { "x-api-key": API_KEY } }
);
200 Response
{
  "actions": [
    {
      "userId": "coughlin058@gmail.com",
      "actionId": "8f3d2c1a-7b4e-4e2a-9c6d-1a5b3e7f9d02",
      "timestamp": "2026-07-08T09:14:23.481932",
      "recordedAt": "2026-07-08T09:14:23.481932",
      "actionType": "watering",
      "description": "Watered garden",
      "metadata": {}
    },
    {
      "userId": "coughlin058@gmail.com",
      "actionId": "4c9b1e6d-2f8a-47c3-b0e5-6d7a2c4f8e11",
      "timestamp": "2026-07-07T18:02:11.905417",
      "recordedAt": "2026-07-07T18:02:11.905417",
      "actionType": "composting",
      "description": "Added kitchen scraps to compost bin",
      "metadata": {}
    }
  ],
  "nextKey": null
}
400 Response
{
  "error": "since/until require a single platform, not '*'",
  "code": "RANGE_NEEDS_PLATFORM"
}
POSTapi.aquabotany.com/actions

Log an action.

By default the server stamps the current time. Pass occurredAt to file the action under when it actually HAPPENED, which is what you want for anything the user is recording rather than doing: a receipt from Saturday belongs on Saturday. The timestamp is part of the sort key, so this is also what keeps a date range exact rather than approximate.

ParameterInDescription
actionTypebodyOptional string. Defaults to unknown.
descriptionbodyOptional string.
metadatabodyOptional object. Free-form; stored and returned as given.
occurredAtbodyOptional. YYYY-MM-DD, or a timestamp such as 2026-03-14T09:30:00. A Z or +HH:MM suffix is accepted and converted to UTC, so new Date().toISOString() works as-is. Absent means now.
platformbodyOptional. Namespace to write to. * is not allowed on writes.

Responses carry both timestamp, when the action happened, and recordedAt, when the row was written. recordedAt is written at create time and was added on 2026-09-06 with no backfill, so rows created before that date do not have it: null-check rather than assuming. They are equal unless the action was backdated, and recordedAt is what orders two actions filed against the same day.

occurredAt is key material, so it is validated strictly and returns 400 BAD_OCCURRED_AT for a value containing #, a date that does not exist such as 2026-02-30, anything before 2000-01-01, or a date more than a day in the future. A non-string actionType or description, or a non-object metadata, is 400 BAD_TYPE; passing the per-user row cap is 409 LIMIT_REACHED.

That last one allows for client clock skew while refusing a row that would sort ahead of every real action forever.

A date is write-once: PATCH does not move an action in time, so correcting one means deleting and recreating it.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/actions \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"actionType": "watering", "description": "Watered garden"}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/actions \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"actionType": "watering", "description": "Watered garden", "platform": "my_garden"}'

# Backdated: file this under the day it happened, not today
curl https://api.aquabotany.com/v1/actions \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"actionType": "expense", "description": "Groceries", "occurredAt": "2026-03-14", "platform": "my_budget"}'
import requests

# Default namespace (platform omitted)
resp = requests.post(
    "https://api.aquabotany.com/v1/actions",
    headers={"x-api-key": API_KEY},
    json={'actionType': 'watering', 'description': 'Watered garden'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.post(
    "https://api.aquabotany.com/v1/actions",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'actionType': 'watering', 'description': 'Watered garden', 'platform': 'my_garden'},
)

# Backdated: file this under the day it happened, not today
resp = requests.post(
    "https://api.aquabotany.com/v1/actions",
    headers={"x-api-key": API_KEY},
    json={
        "actionType": "expense",
        "description": "Groceries",
        "occurredAt": "2026-03-14",
        "platform": "my_budget",
    },
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/actions", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"actionType": "watering", "description": "Watered garden"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/actions", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"actionType": "watering", "description": "Watered garden", "platform": "my_garden"})
});

// Backdated: file this under the day it happened, not today
const backdated = await fetch("https://api.aquabotany.com/v1/actions", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({
    actionType: "expense",
    description: "Groceries",
    occurredAt: "2026-03-14",
    platform: "my_budget"
  })
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/actions", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"actionType": "watering", "description": "Watered garden"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/actions", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"actionType": "watering", "description": "Watered garden", "platform": "my_garden"})
});

// Backdated: file this under the day it happened, not today
const backdated = await fetch("https://api.aquabotany.com/v1/actions", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({
    actionType: "expense",
    description: "Groceries",
    occurredAt: "2026-03-14",
    platform: "my_budget"
  })
});
201 Response
{
  "message": "Action logged",
  "actionId": "8f3d2c1a-7b4e-4e2a-9c6d-1a5b3e7f9d02"
}
400 Response
{
  "error": "occurredAt must not be in the future",
  "code": "BAD_OCCURRED_AT"
}
GETapi.aquabotany.com/actions/user-progress

The caller's action progress.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/actions/user-progress \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be "*" (read across every platform)
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/actions/user-progress?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/actions/user-progress",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/actions/user-progress",
    headers={"x-api-key": API_KEY},
    # platform can also be "*" (read across every platform)
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/actions/user-progress", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/actions/user-progress?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/actions/user-progress", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/actions/user-progress?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "totalActions": 2,
  "action_counts": {
    "watering": 1,
    "composting": 1
  }
}
GETapi.aquabotany.com/actions/{actionId}

Read, update, or delete an action. Supports ?expand=links to inline this record's links in the same call - see Link System.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/actions/8f3d2c1a-7b4e-4e2a-9c6d-1a5b3e7f9d02 \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform must match where the item lives: a project name
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/actions/8f3d2c1a-7b4e-4e2a-9c6d-1a5b3e7f9d02?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/actions/8f3d2c1a-7b4e-4e2a-9c6d-1a5b3e7f9d02",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/actions/8f3d2c1a-7b4e-4e2a-9c6d-1a5b3e7f9d02",
    headers={"x-api-key": API_KEY},
    # platform must match where the item lives: a project name
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/actions/8f3d2c1a-7b4e-4e2a-9c6d-1a5b3e7f9d02", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform must match where the item lives: a project name
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/actions/8f3d2c1a-7b4e-4e2a-9c6d-1a5b3e7f9d02?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/actions/8f3d2c1a-7b4e-4e2a-9c6d-1a5b3e7f9d02", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform must match where the item lives: a project name
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/actions/8f3d2c1a-7b4e-4e2a-9c6d-1a5b3e7f9d02?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "userId": "coughlin058@gmail.com",
  "actionId": "8f3d2c1a-7b4e-4e2a-9c6d-1a5b3e7f9d02",
  "timestamp": "2026-07-08T09:14:23.481932",
  "recordedAt": "2026-07-08T09:14:23.481932",
  "actionType": "watering",
  "description": "Watered garden",
  "metadata": {}
}
PUTapi.aquabotany.com/actions/{actionId}

Read, update, or delete an action.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/actions/8f3d2c1a-7b4e-4e2a-9c6d-1a5b3e7f9d02 \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"actionType": "watering", "description": "Watered the herb garden beds", "metadata": {}}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/actions/8f3d2c1a-7b4e-4e2a-9c6d-1a5b3e7f9d02 \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"actionType": "watering", "description": "Watered the herb garden beds", "metadata": {}, "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.put(
    "https://api.aquabotany.com/v1/actions/8f3d2c1a-7b4e-4e2a-9c6d-1a5b3e7f9d02",
    headers={"x-api-key": API_KEY},
    json={'actionType': 'watering', 'description': 'Watered the herb garden beds', 'metadata': {}},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.put(
    "https://api.aquabotany.com/v1/actions/8f3d2c1a-7b4e-4e2a-9c6d-1a5b3e7f9d02",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'actionType': 'watering', 'description': 'Watered the herb garden beds', 'metadata': {}, 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/actions/8f3d2c1a-7b4e-4e2a-9c6d-1a5b3e7f9d02", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"actionType": "watering", "description": "Watered the herb garden beds", "metadata": {}})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/actions/8f3d2c1a-7b4e-4e2a-9c6d-1a5b3e7f9d02", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"actionType": "watering", "description": "Watered the herb garden beds", "metadata": {}, "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/actions/8f3d2c1a-7b4e-4e2a-9c6d-1a5b3e7f9d02", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"actionType": "watering", "description": "Watered the herb garden beds", "metadata": {}})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/actions/8f3d2c1a-7b4e-4e2a-9c6d-1a5b3e7f9d02", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"actionType": "watering", "description": "Watered the herb garden beds", "metadata": {}, "platform": "my_garden"})
});
200 Response
{
  "userId": "coughlin058@gmail.com",
  "actionId": "8f3d2c1a-7b4e-4e2a-9c6d-1a5b3e7f9d02",
  "timestamp": "2026-07-08T09:14:23.481932",
  "recordedAt": "2026-07-08T09:14:23.481932",
  "actionType": "watering",
  "description": "Watered the herb garden beds",
  "metadata": {}
}
PATCHapi.aquabotany.com/actions/{actionId}

Read, update, or delete an action.

ParameterInDescription
actionTypebodyOptional string.
descriptionbodyOptional string.
metadatabodyOptional object. Replaced wholesale, not merged.

Those three fields are the only updatable ones. An action's DATE is write-once: timestamp and occurredAt are ignored here, because the timestamp is part of the sort key and this route does not move a row. Correcting a date means deleting the action and creating it again. A body with none of the three updatable fields returns 400.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/actions/8f3d2c1a-7b4e-4e2a-9c6d-1a5b3e7f9d02 \
  -X PATCH \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"actionType": "pruning"}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/actions/8f3d2c1a-7b4e-4e2a-9c6d-1a5b3e7f9d02 \
  -X PATCH \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"actionType": "pruning", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.patch(
    "https://api.aquabotany.com/v1/actions/8f3d2c1a-7b4e-4e2a-9c6d-1a5b3e7f9d02",
    headers={"x-api-key": API_KEY},
    json={'actionType': 'pruning'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.patch(
    "https://api.aquabotany.com/v1/actions/8f3d2c1a-7b4e-4e2a-9c6d-1a5b3e7f9d02",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'actionType': 'pruning', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/actions/8f3d2c1a-7b4e-4e2a-9c6d-1a5b3e7f9d02", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"actionType": "pruning"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/actions/8f3d2c1a-7b4e-4e2a-9c6d-1a5b3e7f9d02", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"actionType": "pruning", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/actions/8f3d2c1a-7b4e-4e2a-9c6d-1a5b3e7f9d02", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"actionType": "pruning"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/actions/8f3d2c1a-7b4e-4e2a-9c6d-1a5b3e7f9d02", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"actionType": "pruning", "platform": "my_garden"})
});
200 Response
{
  "userId": "coughlin058@gmail.com",
  "actionId": "8f3d2c1a-7b4e-4e2a-9c6d-1a5b3e7f9d02",
  "timestamp": "2026-07-08T09:14:23.481932",
  "recordedAt": "2026-07-08T09:14:23.481932",
  "actionType": "pruning",
  "description": "Watered garden",
  "metadata": {}
}
DELETEapi.aquabotany.com/actions/{actionId}

Read, update, or delete an action.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/actions/4c9b1e6d-2f8a-47c3-b0e5-6d7a2c4f8e11 \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl "https://api.aquabotany.com/v1/actions/4c9b1e6d-2f8a-47c3-b0e5-6d7a2c4f8e11?platform=my_garden" \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.delete(
    "https://api.aquabotany.com/v1/actions/4c9b1e6d-2f8a-47c3-b0e5-6d7a2c4f8e11",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.delete(
    "https://api.aquabotany.com/v1/actions/4c9b1e6d-2f8a-47c3-b0e5-6d7a2c4f8e11",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/actions/4c9b1e6d-2f8a-47c3-b0e5-6d7a2c4f8e11", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/actions/4c9b1e6d-2f8a-47c3-b0e5-6d7a2c4f8e11?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/actions/4c9b1e6d-2f8a-47c3-b0e5-6d7a2c4f8e11", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/actions/4c9b1e6d-2f8a-47c3-b0e5-6d7a2c4f8e11?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "status": "deleted"
}
GETapi.aquabotany.com/challenges

Community challenges.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/challenges \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be "*" (read across every platform)
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/challenges?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/challenges",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/challenges",
    headers={"x-api-key": API_KEY},
    # platform can also be "*" (read across every platform)
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/challenges", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/challenges?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/challenges", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/challenges?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
[
  {
    "challengeId": "2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15",
    "title": "Compost for a week",
    "description": "Add to your compost bin every day for 7 days",
    "status": "active",
    "created": "2026-07-01T08:30:12.114256",
    "lastModified": "2026-07-01T08:30:12.114256"
  },
  {
    "challengeId": "7b3f1d8e-9a2c-4c6b-8e0d-5f4a1c9b7e22",
    "title": "No-plastic watering month",
    "description": "Water only with collected rainwater for 30 days",
    "status": "completed",
    "created": "2026-06-01T10:05:44.902317",
    "lastModified": "2026-07-02T16:41:09.337815"
  }
]
POSTapi.aquabotany.com/challenges

Community challenges.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/challenges \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Compost for a week", "description": "Add to your compost bin every day for 7 days", "status": "active"}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/challenges \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Compost for a week", "description": "Add to your compost bin every day for 7 days", "status": "active", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.post(
    "https://api.aquabotany.com/v1/challenges",
    headers={"x-api-key": API_KEY},
    json={'title': 'Compost for a week', 'description': 'Add to your compost bin every day for 7 days', 'status': 'active'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.post(
    "https://api.aquabotany.com/v1/challenges",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'title': 'Compost for a week', 'description': 'Add to your compost bin every day for 7 days', 'status': 'active', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/challenges", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"title": "Compost for a week", "description": "Add to your compost bin every day for 7 days", "status": "active"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/challenges", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"title": "Compost for a week", "description": "Add to your compost bin every day for 7 days", "status": "active", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/challenges", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"title": "Compost for a week", "description": "Add to your compost bin every day for 7 days", "status": "active"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/challenges", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"title": "Compost for a week", "description": "Add to your compost bin every day for 7 days", "status": "active", "platform": "my_garden"})
});
201 Response
{
  "message": "Challenge created",
  "challengeId": "2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15"
}
GETapi.aquabotany.com/challenges/active

Challenges that are published and that you have not completed yet. Completing one removes it from your list only.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/challenges/active \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be "*" (read across every platform)
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/challenges/active?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/challenges/active",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/challenges/active",
    headers={"x-api-key": API_KEY},
    # platform can also be "*" (read across every platform)
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/challenges/active", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/challenges/active?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/challenges/active", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/challenges/active?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
[
  {
    "challengeId": "2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15",
    "title": "Compost for a week",
    "description": "Add to your compost bin every day for 7 days",
    "status": "active",
    "created": "2026-07-01T08:30:12.114256",
    "lastModified": "2026-07-01T08:30:12.114256"
  }
]
GETapi.aquabotany.com/challenges/history

Challenges you have completed, newest data first. This is your own history, not a list of challenges flagged finished.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/challenges/history \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be "*" (read across every platform)
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/challenges/history?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/challenges/history",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/challenges/history",
    headers={"x-api-key": API_KEY},
    # platform can also be "*" (read across every platform)
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/challenges/history", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/challenges/history?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/challenges/history", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/challenges/history?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
[
  {
    "challengeId": "7b3f1d8e-9a2c-4c6b-8e0d-5f4a1c9b7e22",
    "title": "No-plastic watering month",
    "description": "Water only with collected rainwater for 30 days",
    "status": "completed",
    "created": "2026-06-01T10:05:44.902317",
    "lastModified": "2026-07-02T16:41:09.337815"
  }
]
GETapi.aquabotany.com/challenges/{challengeId}

Read, update, or delete a challenge. Supports ?expand=links to inline this record's links in the same call - see Link System.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/challenges/2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15 \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform must match where the item lives: a project name
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/challenges/2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/challenges/2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/challenges/2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15",
    headers={"x-api-key": API_KEY},
    # platform must match where the item lives: a project name
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/challenges/2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform must match where the item lives: a project name
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/challenges/2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/challenges/2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform must match where the item lives: a project name
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/challenges/2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "challengeId": "2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15",
  "title": "Compost for a week",
  "description": "Add to your compost bin every day for 7 days",
  "status": "active",
  "created": "2026-07-01T08:30:12.114256",
  "lastModified": "2026-07-01T08:30:12.114256"
}
PUTapi.aquabotany.com/challenges/{challengeId}

Read, update, or delete a challenge.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/challenges/2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15 \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Compost for a week", "description": "Add kitchen scraps to your compost bin every day for 7 days", "status": "active"}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/challenges/2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15 \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Compost for a week", "description": "Add kitchen scraps to your compost bin every day for 7 days", "status": "active", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.put(
    "https://api.aquabotany.com/v1/challenges/2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15",
    headers={"x-api-key": API_KEY},
    json={'title': 'Compost for a week', 'description': 'Add kitchen scraps to your compost bin every day for 7 days', 'status': 'active'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.put(
    "https://api.aquabotany.com/v1/challenges/2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'title': 'Compost for a week', 'description': 'Add kitchen scraps to your compost bin every day for 7 days', 'status': 'active', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/challenges/2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"title": "Compost for a week", "description": "Add kitchen scraps to your compost bin every day for 7 days", "status": "active"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/challenges/2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"title": "Compost for a week", "description": "Add kitchen scraps to your compost bin every day for 7 days", "status": "active", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/challenges/2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"title": "Compost for a week", "description": "Add kitchen scraps to your compost bin every day for 7 days", "status": "active"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/challenges/2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"title": "Compost for a week", "description": "Add kitchen scraps to your compost bin every day for 7 days", "status": "active", "platform": "my_garden"})
});
200 Response
{
  "message": "Challenge updated"
}
DELETEapi.aquabotany.com/challenges/{challengeId}

Read, update, or delete a challenge.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/challenges/7b3f1d8e-9a2c-4c6b-8e0d-5f4a1c9b7e22 \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl "https://api.aquabotany.com/v1/challenges/7b3f1d8e-9a2c-4c6b-8e0d-5f4a1c9b7e22?platform=my_garden" \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.delete(
    "https://api.aquabotany.com/v1/challenges/7b3f1d8e-9a2c-4c6b-8e0d-5f4a1c9b7e22",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.delete(
    "https://api.aquabotany.com/v1/challenges/7b3f1d8e-9a2c-4c6b-8e0d-5f4a1c9b7e22",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/challenges/7b3f1d8e-9a2c-4c6b-8e0d-5f4a1c9b7e22", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/challenges/7b3f1d8e-9a2c-4c6b-8e0d-5f4a1c9b7e22?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/challenges/7b3f1d8e-9a2c-4c6b-8e0d-5f4a1c9b7e22", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/challenges/7b3f1d8e-9a2c-4c6b-8e0d-5f4a1c9b7e22?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
204 Response
(no response body)
POSTapi.aquabotany.com/challenges/{challengeId}/complete

Mark a challenge complete for you. Completion is per account: finishing a shared challenge does not finish it for anyone else, and on a Bottle Blue product platform any signed-in user may complete one without the admin key. Completing twice returns 409.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/challenges/2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15/complete \
  -X POST \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl "https://api.aquabotany.com/v1/challenges/2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15/complete?platform=my_garden" \
  -X POST \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.post(
    "https://api.aquabotany.com/v1/challenges/2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15/complete",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.post(
    "https://api.aquabotany.com/v1/challenges/2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15/complete",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/challenges/2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15/complete", {
  method: "POST",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/challenges/2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15/complete?platform=my_garden", {
  method: "POST",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/challenges/2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15/complete", {
  method: "POST",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/challenges/2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15/complete?platform=my_garden", {
  method: "POST",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "message": "Challenge marked as completed",
  "challengeId": "2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15",
  "completedAt": "2026-08-01T22:41:07.184213"
}
DELETEapi.aquabotany.com/challenges/{challengeId}/complete

Un-complete a challenge you previously finished. Removes only your completion, never anyone else's, and puts the challenge back in your /challenges/active list. Returns 204 on success, or 404 if you had not completed it.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/challenges/2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15/complete   -X DELETE   -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl "https://api.aquabotany.com/v1/challenges/2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15/complete?platform=my_garden"   -X DELETE   -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.delete(
    "https://api.aquabotany.com/v1/challenges/2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15/complete",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code)  # 204 undone, 404 if it was not completed
const resp = await fetch("https://api.aquabotany.com/v1/challenges/2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15/complete", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
// 204 undone, 404 if it was not completed
const resp = await fetch("https://api.aquabotany.com/v1/challenges/2e7a9c4d-5b1f-4a8e-bd63-0c9e2f7a4b15/complete", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
// 204 undone, 404 if it was not completed
204 Response
(no body)
GETapi.aquabotany.com/events

Community events.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/events \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be "*" (read across every platform)
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/events?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/events",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/events",
    headers={"x-api-key": API_KEY},
    # platform can also be "*" (read across every platform)
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/events", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/events?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/events", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/events?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "events": [
    {
      "eventId": "a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73",
      "title": "Seed swap",
      "type": "community",
      "description": "Bring your extra seeds and trade with fellow gardeners.",
      "datetime": "2026-08-01T10:00:00",
      "details": "Held at the Loring Park pavilion. Rain or shine.",
      "lastModified": "2026-07-01T09:15:42.318204"
    },
    {
      "eventId": "5b1e9f60-2c3d-4a87-b6e4-9f0d2c7a4e18",
      "title": "Rain barrel workshop",
      "type": "workshop",
      "description": "Learn to build and install a rain barrel for garden irrigation.",
      "datetime": "2026-07-18T13:30:00",
      "details": "",
      "lastModified": "2026-06-25T16:40:07.902113"
    }
  ]
}
POSTapi.aquabotany.com/events

Community events.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/events \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Seed swap", "type": "community", "description": "Bring your extra seeds and trade with fellow gardeners.", "datetime": "2026-08-01T10:00:00", "details": "Held at the Loring Park pavilion. Rain or shine."}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/events \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Seed swap", "type": "community", "description": "Bring your extra seeds and trade with fellow gardeners.", "datetime": "2026-08-01T10:00:00", "details": "Held at the Loring Park pavilion. Rain or shine.", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.post(
    "https://api.aquabotany.com/v1/events",
    headers={"x-api-key": API_KEY},
    json={'title': 'Seed swap', 'type': 'community', 'description': 'Bring your extra seeds and trade with fellow gardeners.', 'datetime': '2026-08-01T10:00:00', 'details': 'Held at the Loring Park pavilion. Rain or shine.'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.post(
    "https://api.aquabotany.com/v1/events",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'title': 'Seed swap', 'type': 'community', 'description': 'Bring your extra seeds and trade with fellow gardeners.', 'datetime': '2026-08-01T10:00:00', 'details': 'Held at the Loring Park pavilion. Rain or shine.', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/events", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"title": "Seed swap", "type": "community", "description": "Bring your extra seeds and trade with fellow gardeners.", "datetime": "2026-08-01T10:00:00", "details": "Held at the Loring Park pavilion. Rain or shine."})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/events", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"title": "Seed swap", "type": "community", "description": "Bring your extra seeds and trade with fellow gardeners.", "datetime": "2026-08-01T10:00:00", "details": "Held at the Loring Park pavilion. Rain or shine.", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/events", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"title": "Seed swap", "type": "community", "description": "Bring your extra seeds and trade with fellow gardeners.", "datetime": "2026-08-01T10:00:00", "details": "Held at the Loring Park pavilion. Rain or shine."})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/events", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"title": "Seed swap", "type": "community", "description": "Bring your extra seeds and trade with fellow gardeners.", "datetime": "2026-08-01T10:00:00", "details": "Held at the Loring Park pavilion. Rain or shine.", "platform": "my_garden"})
});
201 Response
{
  "eventId": "a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73"
}
GETapi.aquabotany.com/events/upcoming

Upcoming events.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/events/upcoming \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be "*" (read across every platform)
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/events/upcoming?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/events/upcoming",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/events/upcoming",
    headers={"x-api-key": API_KEY},
    # platform can also be "*" (read across every platform)
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/events/upcoming", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/events/upcoming?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/events/upcoming", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/events/upcoming?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "events": [
    {
      "eventId": "5b1e9f60-2c3d-4a87-b6e4-9f0d2c7a4e18",
      "title": "Rain barrel workshop",
      "type": "workshop",
      "description": "Learn to build and install a rain barrel for garden irrigation.",
      "datetime": "2026-07-18T13:30:00",
      "details": "",
      "lastModified": "2026-06-25T16:40:07.902113"
    },
    {
      "eventId": "a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73",
      "title": "Seed swap",
      "type": "community",
      "description": "Bring your extra seeds and trade with fellow gardeners.",
      "datetime": "2026-08-01T10:00:00",
      "details": "Held at the Loring Park pavilion. Rain or shine.",
      "lastModified": "2026-07-01T09:15:42.318204"
    }
  ]
}
GETapi.aquabotany.com/events/{eventId}

Read, update, or delete an event. Supports ?expand=links to inline this record's links in the same call - see Link System.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/events/a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73 \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform must match where the item lives: a project name
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/events/a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/events/a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/events/a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73",
    headers={"x-api-key": API_KEY},
    # platform must match where the item lives: a project name
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/events/a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform must match where the item lives: a project name
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/events/a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/events/a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform must match where the item lives: a project name
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/events/a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "eventId": "a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73",
  "title": "Seed swap",
  "type": "community",
  "description": "Bring your extra seeds and trade with fellow gardeners.",
  "datetime": "2026-08-01T10:00:00",
  "details": "Held at the Loring Park pavilion. Rain or shine.",
  "lastModified": "2026-07-01T09:15:42.318204"
}
PUTapi.aquabotany.com/events/{eventId}

Read, update, or delete an event.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/events/a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73 \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Seed swap (rescheduled)", "type": "community", "description": "Bring your extra seeds and trade with fellow gardeners.", "datetime": "2026-08-08T10:00:00", "details": "Held at the Loring Park pavilion. Rain or shine."}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/events/a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73 \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Seed swap (rescheduled)", "type": "community", "description": "Bring your extra seeds and trade with fellow gardeners.", "datetime": "2026-08-08T10:00:00", "details": "Held at the Loring Park pavilion. Rain or shine.", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.put(
    "https://api.aquabotany.com/v1/events/a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73",
    headers={"x-api-key": API_KEY},
    json={'title': 'Seed swap (rescheduled)', 'type': 'community', 'description': 'Bring your extra seeds and trade with fellow gardeners.', 'datetime': '2026-08-08T10:00:00', 'details': 'Held at the Loring Park pavilion. Rain or shine.'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.put(
    "https://api.aquabotany.com/v1/events/a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'title': 'Seed swap (rescheduled)', 'type': 'community', 'description': 'Bring your extra seeds and trade with fellow gardeners.', 'datetime': '2026-08-08T10:00:00', 'details': 'Held at the Loring Park pavilion. Rain or shine.', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/events/a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"title": "Seed swap (rescheduled)", "type": "community", "description": "Bring your extra seeds and trade with fellow gardeners.", "datetime": "2026-08-08T10:00:00", "details": "Held at the Loring Park pavilion. Rain or shine."})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/events/a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"title": "Seed swap (rescheduled)", "type": "community", "description": "Bring your extra seeds and trade with fellow gardeners.", "datetime": "2026-08-08T10:00:00", "details": "Held at the Loring Park pavilion. Rain or shine.", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/events/a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"title": "Seed swap (rescheduled)", "type": "community", "description": "Bring your extra seeds and trade with fellow gardeners.", "datetime": "2026-08-08T10:00:00", "details": "Held at the Loring Park pavilion. Rain or shine."})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/events/a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"title": "Seed swap (rescheduled)", "type": "community", "description": "Bring your extra seeds and trade with fellow gardeners.", "datetime": "2026-08-08T10:00:00", "details": "Held at the Loring Park pavilion. Rain or shine.", "platform": "my_garden"})
});
200 Response
{
  "eventId": "a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73",
  "title": "Seed swap (rescheduled)",
  "type": "community",
  "description": "Bring your extra seeds and trade with fellow gardeners.",
  "datetime": "2026-08-08T10:00:00",
  "details": "Held at the Loring Park pavilion. Rain or shine.",
  "lastModified": "2026-07-08T14:22:31.594820"
}
PATCHapi.aquabotany.com/events/{eventId}

Read, update, or delete an event.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/events/a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73 \
  -X PATCH \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Seed swap (rescheduled)"}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/events/a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73 \
  -X PATCH \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Seed swap (rescheduled)", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.patch(
    "https://api.aquabotany.com/v1/events/a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73",
    headers={"x-api-key": API_KEY},
    json={'title': 'Seed swap (rescheduled)'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.patch(
    "https://api.aquabotany.com/v1/events/a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'title': 'Seed swap (rescheduled)', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/events/a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"title": "Seed swap (rescheduled)"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/events/a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"title": "Seed swap (rescheduled)", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/events/a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"title": "Seed swap (rescheduled)"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/events/a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"title": "Seed swap (rescheduled)", "platform": "my_garden"})
});
200 Response
{
  "eventId": "a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73",
  "title": "Seed swap (rescheduled)",
  "type": "community",
  "description": "Bring your extra seeds and trade with fellow gardeners.",
  "datetime": "2026-08-01T10:00:00",
  "details": "Held at the Loring Park pavilion. Rain or shine.",
  "lastModified": "2026-07-08T14:25:03.117456"
}
DELETEapi.aquabotany.com/events/{eventId}

Read, update, or delete an event.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/events/a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73 \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl "https://api.aquabotany.com/v1/events/a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73?platform=my_garden" \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.delete(
    "https://api.aquabotany.com/v1/events/a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.delete(
    "https://api.aquabotany.com/v1/events/a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/events/a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/events/a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/events/a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/events/a3f8c2d1-7b4e-4c9a-9e2f-5d8b6a1c0e73?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "status": "deleted"
}
GETapi.aquabotany.com/rewards

Available rewards.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/rewards \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be "*" (read across every platform)
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/rewards?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/rewards",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/rewards",
    headers={"x-api-key": API_KEY},
    # platform can also be "*" (read across every platform)
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/rewards", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/rewards?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/rewards", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/rewards?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
[
  {
    "rewardId": "c7d2a940-8f1b-4e6c-a3d5-2b9e0f7c4a61",
    "title": "Free seed packet",
    "pointsRequired": 100,
    "description": "Redeem for a heirloom tomato seed packet."
  },
  {
    "rewardId": "e91f4b28-6a0d-47c3-b8e2-1c5d9a3f7b04",
    "title": "10% off soil amendments",
    "pointsRequired": 250,
    "description": "One-time 10% discount on any soil amendment order."
  }
]
POSTapi.aquabotany.com/rewards

Available rewards.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/rewards \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Free seed packet", "pointsRequired": 100, "description": "Redeem for a heirloom tomato seed packet."}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/rewards \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Free seed packet", "pointsRequired": 100, "description": "Redeem for a heirloom tomato seed packet.", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.post(
    "https://api.aquabotany.com/v1/rewards",
    headers={"x-api-key": API_KEY},
    json={'title': 'Free seed packet', 'pointsRequired': 100, 'description': 'Redeem for a heirloom tomato seed packet.'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.post(
    "https://api.aquabotany.com/v1/rewards",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'title': 'Free seed packet', 'pointsRequired': 100, 'description': 'Redeem for a heirloom tomato seed packet.', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/rewards", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"title": "Free seed packet", "pointsRequired": 100, "description": "Redeem for a heirloom tomato seed packet."})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/rewards", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"title": "Free seed packet", "pointsRequired": 100, "description": "Redeem for a heirloom tomato seed packet.", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/rewards", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"title": "Free seed packet", "pointsRequired": 100, "description": "Redeem for a heirloom tomato seed packet."})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/rewards", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"title": "Free seed packet", "pointsRequired": 100, "description": "Redeem for a heirloom tomato seed packet.", "platform": "my_garden"})
});
201 Response
{
  "rewardId": "c7d2a940-8f1b-4e6c-a3d5-2b9e0f7c4a61"
}
GETapi.aquabotany.com/rewards/history

The caller's reward-redemption history.

curl https://api.aquabotany.com/v1/rewards/history \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/rewards/history",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/rewards/history", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/rewards/history", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
[
  {
    "rewardId": "c7d2a940-8f1b-4e6c-a3d5-2b9e0f7c4a61",
    "timestamp": "2026-07-08T14:31:09.284511Z"
  },
  {
    "rewardId": "e91f4b28-6a0d-47c3-b8e2-1c5d9a3f7b04",
    "timestamp": "2026-06-30T11:02:47.910335Z"
  }
]
GETapi.aquabotany.com/rewards/points

The caller's current points balance.

curl https://api.aquabotany.com/v1/rewards/points \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/rewards/points",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/rewards/points", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/rewards/points", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "points": 175
}
POSTapi.aquabotany.com/rewards/redeem

Redeem points for a reward.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/rewards/redeem \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"rewardId": "c7d2a940-8f1b-4e6c-a3d5-2b9e0f7c4a61"}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/rewards/redeem \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"rewardId": "c7d2a940-8f1b-4e6c-a3d5-2b9e0f7c4a61", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.post(
    "https://api.aquabotany.com/v1/rewards/redeem",
    headers={"x-api-key": API_KEY},
    json={'rewardId': 'c7d2a940-8f1b-4e6c-a3d5-2b9e0f7c4a61'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.post(
    "https://api.aquabotany.com/v1/rewards/redeem",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'rewardId': 'c7d2a940-8f1b-4e6c-a3d5-2b9e0f7c4a61', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/rewards/redeem", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"rewardId": "c7d2a940-8f1b-4e6c-a3d5-2b9e0f7c4a61"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/rewards/redeem", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"rewardId": "c7d2a940-8f1b-4e6c-a3d5-2b9e0f7c4a61", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/rewards/redeem", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"rewardId": "c7d2a940-8f1b-4e6c-a3d5-2b9e0f7c4a61"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/rewards/redeem", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"rewardId": "c7d2a940-8f1b-4e6c-a3d5-2b9e0f7c4a61", "platform": "my_garden"})
});
200 Response
{
  "message": "Reward redeemed successfully",
  "remainingPoints": 125
}
GETapi.aquabotany.com/rewards/{rewardId}

Read, update, or delete a reward. Supports ?expand=links to inline this record's links in the same call - see Link System.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/rewards/c7d2a940-8f1b-4e6c-a3d5-2b9e0f7c4a61 \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform must match where the item lives: a project name
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/rewards/c7d2a940-8f1b-4e6c-a3d5-2b9e0f7c4a61?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/rewards/c7d2a940-8f1b-4e6c-a3d5-2b9e0f7c4a61",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/rewards/c7d2a940-8f1b-4e6c-a3d5-2b9e0f7c4a61",
    headers={"x-api-key": API_KEY},
    # platform must match where the item lives: a project name
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/rewards/c7d2a940-8f1b-4e6c-a3d5-2b9e0f7c4a61", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform must match where the item lives: a project name
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/rewards/c7d2a940-8f1b-4e6c-a3d5-2b9e0f7c4a61?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/rewards/c7d2a940-8f1b-4e6c-a3d5-2b9e0f7c4a61", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform must match where the item lives: a project name
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/rewards/c7d2a940-8f1b-4e6c-a3d5-2b9e0f7c4a61?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "rewardId": "c7d2a940-8f1b-4e6c-a3d5-2b9e0f7c4a61",
  "title": "Free seed packet",
  "pointsRequired": 100,
  "description": "Redeem for a heirloom tomato seed packet.",
  "created": "2026-06-15T08:20:33.104928Z",
  "lastModified": "2026-06-15T08:20:33.104928Z"
}
PUTapi.aquabotany.com/rewards/{rewardId}

Read, update, or delete a reward.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/rewards/c7d2a940-8f1b-4e6c-a3d5-2b9e0f7c4a61 \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"pointsRequired": 120}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/rewards/c7d2a940-8f1b-4e6c-a3d5-2b9e0f7c4a61 \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"pointsRequired": 120, "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.put(
    "https://api.aquabotany.com/v1/rewards/c7d2a940-8f1b-4e6c-a3d5-2b9e0f7c4a61",
    headers={"x-api-key": API_KEY},
    json={'pointsRequired': 120},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.put(
    "https://api.aquabotany.com/v1/rewards/c7d2a940-8f1b-4e6c-a3d5-2b9e0f7c4a61",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'pointsRequired': 120, 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/rewards/c7d2a940-8f1b-4e6c-a3d5-2b9e0f7c4a61", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"pointsRequired": 120})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/rewards/c7d2a940-8f1b-4e6c-a3d5-2b9e0f7c4a61", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"pointsRequired": 120, "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/rewards/c7d2a940-8f1b-4e6c-a3d5-2b9e0f7c4a61", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"pointsRequired": 120})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/rewards/c7d2a940-8f1b-4e6c-a3d5-2b9e0f7c4a61", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"pointsRequired": 120, "platform": "my_garden"})
});
200 Response
{
  "rewardId": "c7d2a940-8f1b-4e6c-a3d5-2b9e0f7c4a61",
  "title": "Free seed packet",
  "pointsRequired": 120,
  "description": "Redeem for a heirloom tomato seed packet.",
  "created": "2026-06-15T08:20:33.104928Z",
  "lastModified": "2026-07-08T14:36:52.771204Z"
}
DELETEapi.aquabotany.com/rewards/{rewardId}

Read, update, or delete a reward.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/rewards/e91f4b28-6a0d-47c3-b8e2-1c5d9a3f7b04 \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl "https://api.aquabotany.com/v1/rewards/e91f4b28-6a0d-47c3-b8e2-1c5d9a3f7b04?platform=my_garden" \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.delete(
    "https://api.aquabotany.com/v1/rewards/e91f4b28-6a0d-47c3-b8e2-1c5d9a3f7b04",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.delete(
    "https://api.aquabotany.com/v1/rewards/e91f4b28-6a0d-47c3-b8e2-1c5d9a3f7b04",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/rewards/e91f4b28-6a0d-47c3-b8e2-1c5d9a3f7b04", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/rewards/e91f4b28-6a0d-47c3-b8e2-1c5d9a3f7b04?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/rewards/e91f4b28-6a0d-47c3-b8e2-1c5d9a3f7b04", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/rewards/e91f4b28-6a0d-47c3-b8e2-1c5d9a3f7b04?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
204 Response
(no response body)

Notifications

Base URL https://api.aquabotany.com/v1

POSTapi.aquabotany.com/notifications

Create a notification for the authenticated account.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/notifications \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Time to water", "body": "Your basil needs water"}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/notifications \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Time to water", "body": "Your basil needs water", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.post(
    "https://api.aquabotany.com/v1/notifications",
    headers={"x-api-key": API_KEY},
    json={'title': 'Time to water', 'body': 'Your basil needs water'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.post(
    "https://api.aquabotany.com/v1/notifications",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'title': 'Time to water', 'body': 'Your basil needs water', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/notifications", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"title": "Time to water", "body": "Your basil needs water"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/notifications", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"title": "Time to water", "body": "Your basil needs water", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/notifications", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"title": "Time to water", "body": "Your basil needs water"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/notifications", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"title": "Time to water", "body": "Your basil needs water", "platform": "my_garden"})
});
201 Response
{
  "message": "Notification created",
  "notifId": "9f3c1a2b4d5e6f708192a3b4c5d6e7f8"
}
GETapi.aquabotany.com/notifications/{userId}

Read or update your notifications (scoped to the authenticated caller).

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/notifications/you@example.com \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be "*" (read across every platform)
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/notifications/you@example.com?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/notifications/you@example.com",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/notifications/you@example.com",
    headers={"x-api-key": API_KEY},
    # platform can also be "*" (read across every platform)
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/notifications/you@example.com", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/notifications/you@example.com?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/notifications/you@example.com", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/notifications/you@example.com?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
[
  {
    "notifId": "9f3c1a2b4d5e6f708192a3b4c5d6e7f8",
    "title": "Time to water",
    "body": "Your basil needs water",
    "status": "unread",
    "createdAt": "2026-07-08T14:12:31.482913"
  },
  {
    "notifId": "3d0a7b8c9e1f2a3b4c5d6e7f80912a3b",
    "title": "Fertilize reminder",
    "body": "Feed the tomatoes this week",
    "status": "read",
    "createdAt": "2026-07-07T09:03:55.120044"
  }
]
PUTapi.aquabotany.com/notifications/{userId}

Read or update your notifications (scoped to the authenticated caller).

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/notifications/you@example.com \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "read"}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/notifications/you@example.com \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "read", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.put(
    "https://api.aquabotany.com/v1/notifications/you@example.com",
    headers={"x-api-key": API_KEY},
    json={'status': 'read'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.put(
    "https://api.aquabotany.com/v1/notifications/you@example.com",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'status': 'read', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/notifications/you@example.com", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"status": "read"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/notifications/you@example.com", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"status": "read", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/notifications/you@example.com", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"status": "read"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/notifications/you@example.com", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"status": "read", "platform": "my_garden"})
});
400 Response
{
  "message": "Unsupported route"
}
POSTapi.aquabotany.com/notifications/{userId}/read-all

Mark all of your notifications read.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/notifications/you@example.com/read-all \
  -X POST \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl "https://api.aquabotany.com/v1/notifications/you@example.com/read-all?platform=my_garden" \
  -X POST \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.post(
    "https://api.aquabotany.com/v1/notifications/you@example.com/read-all",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.post(
    "https://api.aquabotany.com/v1/notifications/you@example.com/read-all",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/notifications/you@example.com/read-all", {
  method: "POST",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/notifications/you@example.com/read-all?platform=my_garden", {
  method: "POST",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/notifications/you@example.com/read-all", {
  method: "POST",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/notifications/you@example.com/read-all?platform=my_garden", {
  method: "POST",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "message": "Marked 2 notifications as read."
}
PUTapi.aquabotany.com/notifications/{userId}/{notifId}

Update or delete one of your notifications.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/notifications/you@example.com/9f3c1a2b4d5e6f708192a3b4c5d6e7f8 \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "read"}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/notifications/you@example.com/9f3c1a2b4d5e6f708192a3b4c5d6e7f8 \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "read", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.put(
    "https://api.aquabotany.com/v1/notifications/you@example.com/9f3c1a2b4d5e6f708192a3b4c5d6e7f8",
    headers={"x-api-key": API_KEY},
    json={'status': 'read'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.put(
    "https://api.aquabotany.com/v1/notifications/you@example.com/9f3c1a2b4d5e6f708192a3b4c5d6e7f8",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'status': 'read', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/notifications/you@example.com/9f3c1a2b4d5e6f708192a3b4c5d6e7f8", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"status": "read"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/notifications/you@example.com/9f3c1a2b4d5e6f708192a3b4c5d6e7f8", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"status": "read", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/notifications/you@example.com/9f3c1a2b4d5e6f708192a3b4c5d6e7f8", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"status": "read"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/notifications/you@example.com/9f3c1a2b4d5e6f708192a3b4c5d6e7f8", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"status": "read", "platform": "my_garden"})
});
200 Response
{
  "notifId": "9f3c1a2b4d5e6f708192a3b4c5d6e7f8",
  "title": "Time to water",
  "body": "Your basil needs water",
  "status": "read",
  "createdAt": "2026-07-08T14:12:31.482913"
}
DELETEapi.aquabotany.com/notifications/{userId}/{notifId}

Update or delete one of your notifications.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/notifications/you@example.com/9f3c1a2b4d5e6f708192a3b4c5d6e7f8 \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl "https://api.aquabotany.com/v1/notifications/you@example.com/9f3c1a2b4d5e6f708192a3b4c5d6e7f8?platform=my_garden" \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.delete(
    "https://api.aquabotany.com/v1/notifications/you@example.com/9f3c1a2b4d5e6f708192a3b4c5d6e7f8",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.delete(
    "https://api.aquabotany.com/v1/notifications/you@example.com/9f3c1a2b4d5e6f708192a3b4c5d6e7f8",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/notifications/you@example.com/9f3c1a2b4d5e6f708192a3b4c5d6e7f8", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/notifications/you@example.com/9f3c1a2b4d5e6f708192a3b4c5d6e7f8?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/notifications/you@example.com/9f3c1a2b4d5e6f708192a3b4c5d6e7f8", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/notifications/you@example.com/9f3c1a2b4d5e6f708192a3b4c5d6e7f8?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
204 Response
(no response body)

Content & settings

Base URL https://api.aquabotany.com/v1

GETapi.aquabotany.com/learning-resources

Educational gardening resources.

Results are scoped to the platform you ask for, and only that platform's library is returned. A platform whose library is itself part of what that product sells is not served here and returns 403 PAID_CATALOG; the wildcard * is not accepted and returns 400 WILDCARD_NOT_ALLOWED. Omitting platform reads the default namespace, which holds no shared library.

curl https://api.aquabotany.com/v1/learning-resources \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/learning-resources",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/learning-resources", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/learning-resources", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
[
  {
    "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
    "type": "internal",
    "title": "Getting Started with Hydroponics",
    "description": "A beginner's guide to setting up your first hydroponic system.",
    "author": "Aqua Botany Team",
    "date": "2026-06-15T10:30:00",
    "thumbnail": "https://cdn.aquabotany.com/articles/hydroponics-101.jpg",
    "content": "Hydroponics is the practice of growing plants without soil...",
    "link": null
  },
  {
    "id": "f6e5d4c3-b2a1-4d9c-8b7a-6f5e4d3c2b1a",
    "type": "external",
    "title": "USDA Plant Hardiness Zone Map",
    "description": "Find your growing zone before planning your garden.",
    "author": "USDA",
    "date": "2026-05-02T08:00:00",
    "thumbnail": null,
    "content": null,
    "link": "https://planthardiness.ars.usda.gov/"
  }
]

// 403 - this platform's library is part of what the product sells
{
  "error": "this platform's catalog is a paid product and is not served here",
  "code": "PAID_CATALOG"
}

// 400 - one platform at a time
{
  "error": "this endpoint reads one platform at a time; '*' is not accepted",
  "code": "WILDCARD_NOT_ALLOWED"
}
GETapi.aquabotany.com/settings

Read all account settings.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/settings \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be "*" (read across every platform)
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/settings?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/settings",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/settings",
    headers={"x-api-key": API_KEY},
    # platform can also be "*" (read across every platform)
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/settings", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/settings?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/settings", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/settings?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
[
  {
    "value": {
      "system": "metric"
    },
    "lastModified": "2026-07-08T14:15:09.271836",
    "settingName": "units"
  },
  {
    "value": {
      "email": true,
      "push": false
    },
    "lastModified": "2026-07-06T18:40:12.503311",
    "settingName": "notifications"
  }
]
POSTapi.aquabotany.com/settings

Create or update a setting.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/settings \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"settingName": "units", "value": {"system": "metric"}}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/settings \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"settingName": "units", "value": {"system": "metric"}, "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.post(
    "https://api.aquabotany.com/v1/settings",
    headers={"x-api-key": API_KEY},
    json={'settingName': 'units', 'value': {'system': 'metric'}},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.post(
    "https://api.aquabotany.com/v1/settings",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'settingName': 'units', 'value': {'system': 'metric'}, 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/settings", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"settingName": "units", "value": {"system": "metric"}})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/settings", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"settingName": "units", "value": {"system": "metric"}, "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/settings", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"settingName": "units", "value": {"system": "metric"}})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/settings", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"settingName": "units", "value": {"system": "metric"}, "platform": "my_garden"})
});
201 Response
{
  "message": "Setting saved"
}
GETapi.aquabotany.com/settings/{settingName}

Read or delete one setting. Supports ?expand=links to inline this record's links in the same call - see Link System.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/settings/units \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform must match where the item lives: a project name
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/settings/units?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/settings/units",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/settings/units",
    headers={"x-api-key": API_KEY},
    # platform must match where the item lives: a project name
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/settings/units", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform must match where the item lives: a project name
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/settings/units?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/settings/units", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform must match where the item lives: a project name
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/settings/units?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "value": {
    "system": "metric"
  },
  "lastModified": "2026-07-08T14:15:09.271836",
  "settingName": "units"
}
DELETEapi.aquabotany.com/settings/{settingName}

Read or delete one setting.

# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/settings/units \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl "https://api.aquabotany.com/v1/settings/units?platform=my_garden" \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.delete(
    "https://api.aquabotany.com/v1/settings/units",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.delete(
    "https://api.aquabotany.com/v1/settings/units",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/settings/units", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/settings/units?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/settings/units", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/settings/units?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
204 Response
(no response body)

Aqua Botany

Base URL https://api.aquabotany.com/v1

GETapi.aquabotany.com/dashboard

The caller's dashboard summary.

curl https://api.aquabotany.com/v1/dashboard \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.aquabotany.com/v1/dashboard",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.aquabotany.com/v1/dashboard", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.aquabotany.com/v1/dashboard", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
[
  {
    "serial": "12345",
    "sensordata": {
      "temp": {
        "readings": [
          {
            "timestamp": "2026-07-08T14:00:00Z",
            "value": 23.1
          },
          {
            "timestamp": "2026-07-08T13:00:00Z",
            "value": 22.8
          },
          {
            "timestamp": "2026-07-08T12:00:00Z",
            "value": 22.4
          }
        ]
      },
      "soilmoisture": {
        "readings": [
          {
            "timestamp": "2026-07-08T14:00:00Z",
            "value": 46.0
          },
          {
            "timestamp": "2026-07-08T13:00:00Z",
            "value": 47.0
          },
          {
            "timestamp": "2026-07-08T12:00:00Z",
            "value": 48.0
          }
        ]
      },
      "sunlight": {
        "readings": [
          {
            "timestamp": "2026-07-08T14:00:00Z",
            "value": 315.0
          },
          {
            "timestamp": "2026-07-08T13:00:00Z",
            "value": 340.0
          },
          {
            "timestamp": "2026-07-08T12:00:00Z",
            "value": 298.0
          }
        ]
      }
    }
  },
  {
    "serial": "54321",
    "sensordata": {
      "temp": {
        "readings": [
          {
            "timestamp": "2026-07-08T14:00:00Z",
            "value": 21.7
          },
          {
            "timestamp": "2026-07-08T13:00:00Z",
            "value": 21.5
          },
          {
            "timestamp": "2026-07-08T12:00:00Z",
            "value": 21.2
          }
        ]
      },
      "soilmoisture": {
        "readings": [
          {
            "timestamp": "2026-07-08T14:00:00Z",
            "value": 51.0
          },
          {
            "timestamp": "2026-07-08T13:00:00Z",
            "value": 52.0
          },
          {
            "timestamp": "2026-07-08T12:00:00Z",
            "value": 52.0
          }
        ]
      },
      "sunlight": {
        "readings": [
          {
            "timestamp": "2026-07-08T14:00:00Z",
            "value": 402.0
          },
          {
            "timestamp": "2026-07-08T13:00:00Z",
            "value": 415.0
          },
          {
            "timestamp": "2026-07-08T12:00:00Z",
            "value": 388.0
          }
        ]
      }
    }
  },
  {
    "serial": "00000",
    "sensordata": {
      "temp": {
        "readings": [
          {
            "timestamp": "2026-07-08T14:00:00Z",
            "value": 24.3
          },
          {
            "timestamp": "2026-07-08T13:00:00Z",
            "value": 24.0
          },
          {
            "timestamp": "2026-07-08T12:00:00Z",
            "value": 23.9
          }
        ]
      },
      "soilmoisture": {
        "readings": [
          {
            "timestamp": "2026-07-08T14:00:00Z",
            "value": 39.0
          },
          {
            "timestamp": "2026-07-08T13:00:00Z",
            "value": 41.0
          },
          {
            "timestamp": "2026-07-08T12:00:00Z",
            "value": 42.0
          }
        ]
      },
      "sunlight": {
        "readings": [
          {
            "timestamp": "2026-07-08T14:00:00Z",
            "value": 512.0
          },
          {
            "timestamp": "2026-07-08T13:00:00Z",
            "value": 530.0
          },
          {
            "timestamp": "2026-07-08T12:00:00Z",
            "value": 505.0
          }
        ]
      }
    }
  }
]

Work orders

Base URL https://api.aquabotany.com/v1

Organization scoping. Every route in this group accepts an optional orgId that reads or stores the work order in a shared organization's partition instead of your own (see Organizations). Omitting orgId behaves exactly as it did before this was added, so existing integrations need no change. Send it in the query string on the GET routes and in the JSON body on POST, PUT and PATCH; DELETE accepts either. It is a string of at most 128 characters made up of letters, digits and _.:- only, and anything else returns 400 INVALID_ORG_ID. Membership is checked fail closed: if it cannot be verified the call returns 403 NOT_ORG_MEMBER rather than quietly falling back to your own work orders. The member role covers list, read, create and update, while DELETE also requires admin, since removing shared inventory is more destructive than editing it. Work orders created under an organization count against that organization's shared cap of 2000 rows, which is tracked separately from the 500 row per account cap.
GETapi.aquabotany.com/workorder

List the caller's work orders, or an organization's shared work orders when orgId is given.

Rows come back with normalized lowercase keys: id, platform, title, department, status, priority, assigned_to, notes, created_at, updated_at and created_by. GET /workorder/{workorderId} returns the same shape, so a client needs only one mapper.

ParameterInDescription
orgIdqueryOptional. List an organization's shared work orders instead of your own (see Organizations). Any member role can list.
platformqueryOptional. Namespace to read. Omitted means your default namespace; * reads across every platform you have.
# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/workorder \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be "*" (read across every platform)
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/workorder?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to an organization (shared work orders)
curl "https://api.aquabotany.com/v1/workorder?orgId=3f1c9e2a7d614b839e5a3f8b1c6d2e07" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/workorder",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/workorder",
    headers={"x-api-key": API_KEY},
    # platform can also be "*" (read across every platform)
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)

# Scoped to an organization (shared work orders)
resp = requests.get(
    "https://api.aquabotany.com/v1/workorder",
    headers={"x-api-key": API_KEY},
    params={"orgId": "3f1c9e2a7d614b839e5a3f8b1c6d2e07"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/workorder", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/workorder?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});

// Scoped to an organization (shared work orders)
const shared = await fetch("https://api.aquabotany.com/v1/workorder?orgId=3f1c9e2a7d614b839e5a3f8b1c6d2e07", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/workorder", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be "*" (read across every platform)
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/workorder?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});

// Scoped to an organization (shared work orders)
const shared = await fetch("https://api.aquabotany.com/v1/workorder?orgId=3f1c9e2a7d614b839e5a3f8b1c6d2e07", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
[
  {
    "id": "5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f",
    "platform": "default",
    "title": "Replace irrigation pump",
    "department": "Maintenance",
    "status": "New",
    "priority": "High",
    "assigned_to": null,
    "created_at": "2026-07-08T14:18:02.331674+00:00",
    "updated_at": "2026-07-08T14:18:02.331674+00:00"
  },
  {
    "id": "0a9b8c7d-6e5f-4d3c-2b1a-9f8e7d6c5b4a",
    "platform": "default",
    "title": "Calibrate pH sensors",
    "department": "Operations",
    "status": "In Progress",
    "priority": "Normal",
    "assigned_to": "tech@example.com",
    "created_at": "2026-07-05T09:22:47.905112+00:00",
    "updated_at": "2026-07-07T16:03:11.284559+00:00"
  }
]
400 Response
{
  "error": "invalid orgId (letters, digits, and _.:- only)",
  "code": "INVALID_ORG_ID"
}
403 Response
{
  "error": "You are not a member of this organization",
  "code": "NOT_ORG_MEMBER"
}
POSTapi.aquabotany.com/workorder

Create a work order. New orders start with status New.

Returns {"status": "created", "workorder_id": "<uuid>"}. Missing title or department returns 400.

ParameterInDescription
titlebodyrequired Short description of the work.
departmentbodyrequired Owning department, for example Maintenance.
assigned_tobodyOptional. Email address of the person responsible. Defaults to unassigned.
prioritybodyOptional. Defaults to Normal.
notesbodyOptional free text.
orgIdbodyOptional. Create the work order in an organization's shared partition instead of your own (see Organizations). Any member role can create.
platformbodyOptional. Namespace to write to. Defaults to default; * is not allowed on writes.
# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/workorder \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Replace irrigation pump", "department": "Maintenance", "priority": "High", "notes": "Pump failed on section 3."}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/workorder \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Replace irrigation pump", "department": "Maintenance", "priority": "High", "notes": "Pump failed on section 3.", "platform": "my_garden"}'

# Stored in an organization's shared partition (orgId goes in the body on writes)
curl https://api.aquabotany.com/v1/workorder \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Replace irrigation pump", "department": "Maintenance", "orgId": "3f1c9e2a7d614b839e5a3f8b1c6d2e07"}'
import requests

# Default namespace (platform omitted)
resp = requests.post(
    "https://api.aquabotany.com/v1/workorder",
    headers={"x-api-key": API_KEY},
    json={"title": "Replace irrigation pump", "department": "Maintenance",
          "priority": "High", "notes": "Pump failed on section 3."},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.post(
    "https://api.aquabotany.com/v1/workorder",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={"title": "Replace irrigation pump", "department": "Maintenance",
          "priority": "High", "notes": "Pump failed on section 3.", "platform": "my_garden"},
)

# Stored in an organization's shared partition (orgId goes in the body on writes)
resp = requests.post(
    "https://api.aquabotany.com/v1/workorder",
    headers={"x-api-key": API_KEY},
    json={"title": "Replace irrigation pump", "department": "Maintenance",
          "orgId": "3f1c9e2a7d614b839e5a3f8b1c6d2e07"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/workorder", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"title": "Replace irrigation pump", "department": "Maintenance", "priority": "High", "notes": "Pump failed on section 3."})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/workorder", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"title": "Replace irrigation pump", "department": "Maintenance", "priority": "High", "notes": "Pump failed on section 3.", "platform": "my_garden"})
});

// Stored in an organization's shared partition (orgId goes in the body on writes)
const shared = await fetch("https://api.aquabotany.com/v1/workorder", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"title": "Replace irrigation pump", "department": "Maintenance", "orgId": "3f1c9e2a7d614b839e5a3f8b1c6d2e07"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/workorder", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"title": "Replace irrigation pump", "department": "Maintenance", "priority": "High", "notes": "Pump failed on section 3."})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/workorder", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"title": "Replace irrigation pump", "department": "Maintenance", "priority": "High", "notes": "Pump failed on section 3.", "platform": "my_garden"})
});

// Stored in an organization's shared partition (orgId goes in the body on writes)
const shared = await fetch("https://api.aquabotany.com/v1/workorder", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"title": "Replace irrigation pump", "department": "Maintenance", "orgId": "3f1c9e2a7d614b839e5a3f8b1c6d2e07"})
});
201 Response
{
  "status": "created",
  "workorder_id": "5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f"
}
400 Response
{
  "error": "invalid orgId (letters, digits, and _.:- only)",
  "code": "INVALID_ORG_ID"
}
403 Response
{
  "error": "You are not a member of this organization",
  "code": "NOT_ORG_MEMBER"
}
409 Response
{
  "error": "Item limit reached (2000); delete some before adding more",
  "code": "LIMIT_REACHED"
}
GETapi.aquabotany.com/workorder/{workorderId}

Get a single work order. Supports ?expand=links to inline this record's links in the same call - see Link System. Returns 404 if there is no such work order, or if it belongs to another account and no orgId was given.

This route returns the same normalized shape as GET /workorder: id, platform, title, department, status, priority, assigned_to, notes, created_at, updated_at and created_by. Until 2026-09-05 it returned the raw stored attribute names instead (WorkOrderId, Title and so on), so one work order had two spellings depending on which route you called. Both reads now go through a single projection and cannot drift apart again.

ParameterInDescription
workorderIdpathrequired Work order UUID.
orgIdqueryOptional. Read an organization's shared work order instead of your own (see Organizations). Any member role can read.
expandqueryOptional. Pass expand=links to inline this work order's relationships from Links under a links key, both directions, instead of making a second call. Capped at 50 rows per direction; a truncated flag says to page through /links instead.
platformqueryOptional. Namespace the work order lives in. Omitted means your default namespace.
# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/workorder/5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform must match where the item lives: a project name
# or the Aqua Botany product slug "bb_aquabotany"
curl "https://api.aquabotany.com/v1/workorder/5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f?platform=my_garden" \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.get(
    "https://api.aquabotany.com/v1/workorder/5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.get(
    "https://api.aquabotany.com/v1/workorder/5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f",
    headers={"x-api-key": API_KEY},
    # platform must match where the item lives: a project name
    # or the Aqua Botany product slug "bb_aquabotany"
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/workorder/5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform must match where the item lives: a project name
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/workorder/5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/workorder/5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform must match where the item lives: a project name
// or the Aqua Botany product slug "bb_aquabotany"
const scoped = await fetch("https://api.aquabotany.com/v1/workorder/5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f?platform=my_garden", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "id": "8c71b74a-89bc-4a74-bdfe-8ba772fab124",
  "platform": "default",
  "title": "Replace Pump",
  "department": "Maintenance",
  "status": "In Progress",
  "priority": "High",
  "assigned_to": "alex@acme.com",
  "notes": "Waiting for part",
  "created_at": "2026-09-05T12:00:00Z",
  "updated_at": "2026-09-05T14:30:00Z",
  "created_by": "owner@acme.com"
}
400 Response
{
  "error": "invalid orgId (letters, digits, and _.:- only)",
  "code": "INVALID_ORG_ID"
}
403 Response
{
  "error": "You are not a member of this organization",
  "code": "NOT_ORG_MEMBER"
}
PUTapi.aquabotany.com/workorder/{workorderId}

Update a work order.

Supply at least one of title, department, assigned_to, priority, status or notes; a request with no fields returns 400. Returns {"status": "updated", "workorder_id": "<uuid>"}.

ParameterInDescription
workorderIdpathrequired Work order UUID.
titlebodyOptional. Short description of the work.
departmentbodyOptional. Owning department.
assigned_tobodyOptional. Email address of the person responsible.
prioritybodyOptional.
statusbodyOptional, for example In Progress.
notesbodyOptional free text.
orgIdbodyOptional. Update an organization's shared work order instead of your own (see Organizations). Any member role can update.
platformbodyOptional. Namespace to write to. Defaults to default; * is not allowed on writes.
# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/workorder/5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "In Progress", "assigned_to": "tech@example.com"}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/workorder/5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f \
  -X PUT \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "In Progress", "assigned_to": "tech@example.com", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.put(
    "https://api.aquabotany.com/v1/workorder/5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f",
    headers={"x-api-key": API_KEY},
    json={'status': 'In Progress', 'assigned_to': 'tech@example.com'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.put(
    "https://api.aquabotany.com/v1/workorder/5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'status': 'In Progress', 'assigned_to': 'tech@example.com', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/workorder/5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"status": "In Progress", "assigned_to": "tech@example.com"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/workorder/5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"status": "In Progress", "assigned_to": "tech@example.com", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/workorder/5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"status": "In Progress", "assigned_to": "tech@example.com"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/workorder/5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f", {
  method: "PUT",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"status": "In Progress", "assigned_to": "tech@example.com", "platform": "my_garden"})
});
200 Response
{
  "status": "updated",
  "workorder_id": "5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f"
}
400 Response
{
  "error": "invalid orgId (letters, digits, and _.:- only)",
  "code": "INVALID_ORG_ID"
}
403 Response
{
  "error": "You are not a member of this organization",
  "code": "NOT_ORG_MEMBER"
}
PATCHapi.aquabotany.com/workorder/{workorderId}

Update a work order. Identical to PUT; both apply a partial update.

Supply at least one of title, department, assigned_to, priority, status or notes; a request with no fields returns 400. Returns {"status": "updated", "workorder_id": "<uuid>"}.

ParameterInDescription
workorderIdpathrequired Work order UUID.
titlebodyOptional. Short description of the work.
departmentbodyOptional. Owning department.
assigned_tobodyOptional. Email address of the person responsible.
prioritybodyOptional.
statusbodyOptional, for example In Progress.
notesbodyOptional free text.
orgIdbodyOptional. Update an organization's shared work order instead of your own (see Organizations). Any member role can update.
platformbodyOptional. Namespace to write to. Defaults to default; * is not allowed on writes.
# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/workorder/5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f \
  -X PATCH \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "In Progress", "assigned_to": "tech@example.com"}'

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl https://api.aquabotany.com/v1/workorder/5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f \
  -X PATCH \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "In Progress", "assigned_to": "tech@example.com", "platform": "my_garden"}'
import requests

# Default namespace (platform omitted)
resp = requests.patch(
    "https://api.aquabotany.com/v1/workorder/5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f",
    headers={"x-api-key": API_KEY},
    json={'status': 'In Progress', 'assigned_to': 'tech@example.com'},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.patch(
    "https://api.aquabotany.com/v1/workorder/5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    json={'status': 'In Progress', 'assigned_to': 'tech@example.com', 'platform': 'my_garden'},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/workorder/5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"status": "In Progress", "assigned_to": "tech@example.com"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/workorder/5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"status": "In Progress", "assigned_to": "tech@example.com", "platform": "my_garden"})
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/workorder/5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"status": "In Progress", "assigned_to": "tech@example.com"})
});
const data = await resp.json();

// Scoped to one platform
const scoped = await fetch("https://api.aquabotany.com/v1/workorder/5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f", {
  method: "PATCH",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  // platform can also be the Aqua Botany product slug "bb_aquabotany"
  // ("*" is not allowed on writes)
  body: JSON.stringify({"status": "In Progress", "assigned_to": "tech@example.com", "platform": "my_garden"})
});
200 Response
{
  "status": "updated",
  "workorder_id": "5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f"
}
400 Response
{
  "error": "invalid orgId (letters, digits, and _.:- only)",
  "code": "INVALID_ORG_ID"
}
403 Response
{
  "error": "You are not a member of this organization",
  "code": "NOT_ORG_MEMBER"
}
DELETEapi.aquabotany.com/workorder/{workorderId}

Delete a work order.

Returns {"status": "deleted", "workorder_id": "<uuid>"}. Scoped to the authenticated user unless orgId is given; another account's ids return 404. This is the one route in the group that is not open to every member: deleting shared inventory requires the admin role or higher, and a plain member gets 403 INSUFFICIENT_ORG_ROLE.

ParameterInDescription
workorderIdpathrequired Work order UUID.
orgIdquery or bodyOptional. Delete an organization's shared work order instead of your own (see Organizations). Accepted in the query string or the JSON body. Requires the admin role or higher.
platformqueryOptional. Namespace the work order lives in. Omitted means your default namespace.
# Default namespace (platform omitted)
curl https://api.aquabotany.com/v1/workorder/5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"

# Scoped to one platform
# platform can also be the Aqua Botany product slug "bb_aquabotany"
# ("*" is not allowed on writes)
curl "https://api.aquabotany.com/v1/workorder/5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f?platform=my_garden" \
  -X DELETE \
  -H "x-api-key: $BBL_API_KEY"
import requests

# Default namespace (platform omitted)
resp = requests.delete(
    "https://api.aquabotany.com/v1/workorder/5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())

# Scoped to one platform
resp = requests.delete(
    "https://api.aquabotany.com/v1/workorder/5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f",
    headers={"x-api-key": API_KEY},
    # platform can also be the Aqua Botany product slug "bb_aquabotany"
    # ("*" is not allowed on writes)
    params={"platform": "my_garden"},
)
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/workorder/5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/workorder/5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
// Default namespace (platform omitted)
const resp = await fetch("https://api.aquabotany.com/v1/workorder/5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();

// Scoped to one platform
// platform can also be the Aqua Botany product slug "bb_aquabotany"
// ("*" is not allowed on writes)
const scoped = await fetch("https://api.aquabotany.com/v1/workorder/5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f?platform=my_garden", {
  method: "DELETE",
  headers: { "x-api-key": API_KEY }
});
200 Response
{
  "status": "deleted",
  "workorder_id": "5d1c8a4e-2f3b-4c6d-9e0f-1a2b3c4d5e6f"
}
400 Response
{
  "error": "invalid orgId (letters, digits, and _.:- only)",
  "code": "INVALID_ORG_ID"
}
403 Response
{
  "error": "You are not a member of this organization",
  "code": "NOT_ORG_MEMBER"
}
403 Response
{
  "error": "This action requires the 'admin' role or higher",
  "code": "INSUFFICIENT_ORG_ROLE"
}

PocketQuant

Base URL https://api.pocketquant.io

GETapi.pocketquant.io/dashboard

The caller's dashboard summary.

curl https://api.pocketquant.io/dashboard \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.pocketquant.io/dashboard",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.pocketquant.io/dashboard", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.pocketquant.io/dashboard", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "message": "Dashboard"
}
POSTapi.pocketquant.io/search

Search stocks and symbols.

curl https://api.pocketquant.io/search \
  -X POST \
  -H "x-api-key: $BBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"searchTerm": "Renewable"}'
import requests

resp = requests.post(
    "https://api.pocketquant.io/search",
    headers={"x-api-key": API_KEY},
    json={'searchTerm': 'Renewable'},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.pocketquant.io/search", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"searchTerm": "Renewable"})
});
const data = await resp.json();
const resp = await fetch("https://api.pocketquant.io/search", {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({"searchTerm": "Renewable"})
});
const data = await resp.json();
200 Response
[
  {
    "Ticker": "BEP",
    "Date": "2026-07-08",
    "Company Name": "Brookfield Renewable Partners L.P.",
    "About": "Brookfield Renewable Partners L.P. owns and operates one of the world's largest publicly traded platforms for renewable power, with hydroelectric, wind, solar, and storage facilities in North America, South America, Europe, and Asia.",
    "CEO": "Connor Teskey",
    "Country": "Canada",
    "IPO Year": "2011",
    "Volume": "614,532",
    "Sector": "Utilities",
    "Industry": "Renewable Utilities",
    "Ticker Verbose": "BEP:NYSE",
    "Employees": "3,400",
    "Founded": "1999",
    "Market cap": "16.42B USD",
    "Primary exchange": "NYSE",
    "Website": "https://bep.brookfield.com",
    "Headquarters": "Toronto, Ontario, Canada"
  }
]
GETapi.pocketquant.io/stats/{ticker}

Statistics for a ticker.

ParameterInDescription
tickerpathrequired Stock symbol.
curl https://api.pocketquant.io/stats/AAPL \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.pocketquant.io/stats/AAPL",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.pocketquant.io/stats/AAPL", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.pocketquant.io/stats/AAPL", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "ticker": "AAPL",
  "stats": {
    "marketCap": 3182456750080,
    "trailingPE": 33.12,
    "dividendYield": 0.49,
    "averageVolume": 54832100,
    "dayHigh": 214.65,
    "dayLow": 211.02,
    "open": 211.85,
    "volume": 48213450,
    "fiftyTwoWeekHigh": 260.1,
    "fiftyTwoWeekLow": 169.21
  }
}
GETapi.pocketquant.io/stocks/{ticker}

Quote and data for a ticker.

ParameterInDescription
tickerpathrequired Stock symbol, e.g. AAPL.
curl https://api.pocketquant.io/stocks/AAPL \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.pocketquant.io/stocks/AAPL",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.pocketquant.io/stocks/AAPL", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.pocketquant.io/stocks/AAPL", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "ticker": "AAPL",
  "prices": [
    207.82,
    210.01,
    213.55
  ],
  "dates": [
    "2026-07-02",
    "2026-07-06",
    "2026-07-07"
  ],
  "info": {
    "symbol": "AAPL",
    "exchange": "NasdaqGS",
    "currency": "USD",
    "type": "EQUITY",
    "name": "Apple Inc",
    "sector": "TECHNOLOGY",
    "industry": "Consumer Electronics",
    "description": "Apple Inc. designs, manufactures, and markets smartphones, personal computers, tablets, wearables, and accessories worldwide."
  },
  "stats": {
    "market_cap": 3182456750080,
    "pe_ratio": 33.12,
    "dividend_yield": 0.0049,
    "fifty_two_week_high": 260.1,
    "fifty_two_week_low": 169.21,
    "day_high": 214.65,
    "day_low": 211.02,
    "volume": 48213450,
    "previous_close": 211.5,
    "latest_price": 213.55
  },
  "related": []
}

Other

Base URL https://bottlebluellc.com

GETbottlebluellc.com/health

Liveness probe. Returns an ok status; requires no authentication.

curl https://bottlebluellc.com/health \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://bottlebluellc.com/health",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://bottlebluellc.com/health", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://bottlebluellc.com/health", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "status": "ok",
  "message": "Endpoint is live"
}
GETapi.pocketquant.io/health

Liveness probe. Returns an ok status; requires no authentication.

curl https://api.pocketquant.io/health \
  -H "x-api-key: $BBL_API_KEY"
import requests

resp = requests.get(
    "https://api.pocketquant.io/health",
    headers={"x-api-key": API_KEY},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://api.pocketquant.io/health", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
const resp = await fetch("https://api.pocketquant.io/health", {
  method: "GET",
  headers: { "x-api-key": API_KEY }
});
const data = await resp.json();
200 Response
{
  "status": "ok",
  "service": "PocketQuant API",
  "message": "API is healthy and responding.",
  "timestamp": "2026-07-08T14:32:07.412896+00:00"
}