Which route?
| You want to… | Use |
|---|---|
| Ask questions of your books in plain English, draft quotes or journals with an assistant | The Beeswax MCP server in Claude Desktop, Claude Code, Cursor or any MCP app. No code. |
| React to events in another tool (a new deal, a form submission) by creating something in Beeswax | Zapier, Make or n8n with an HTTP request step (below). |
| Pull Beeswax data on a schedule into a spreadsheet, warehouse or dashboard | A short script (below) or n8n on a schedule, using updated_since. |
| Build a product feature or a two-way integration | Your own code against the API. Start with Best practices. |
All of them use the same token from Account Settings → API Tokens and the same endpoints.
Zapier
Zapier has no native Beeswax app yet, so use Webhooks by Zapier.
- Create the token in Beeswax with only the scopes the Zap needs, for example
invoices:write,companies:read,transaction_accounts:readfor "new deal → draft invoice". - In the Zap, add an action Webhooks by Zapier → Custom Request.
- Method
POST(orGETto read). URLhttps://app.beeswaxapp.com/new_api/v1/invoices(or the endpoint you need). - Headers:
Authorization: Bearer YOUR_API_TOKEN,Content-Type: application/json, andIdempotency-Keymapped to a unique field from the trigger (the deal id, the form submission id). That last one is what stops a re-run of the Zap creating a second invoice. - Data: the JSON body from the relevant recipe, with trigger fields mapped in. Wrap it in the singular resource key (
{"invoice": {...}}). - Test. The response is the created resource; map
idandnumberinto later steps (a Slack message, a CRM note).
Reading data in Zapier. A
GETreturns a page of up to 100 rows. Zapier's line-item handling copes withinvoices[]; for anything larger, schedule a script instead.
Make (Integromat)
- Add an HTTP → Make a request module.
- URL as above, method
POST, Body type Raw, Content type JSON. - Headers:
Authorization: Bearer YOUR_API_TOKENandIdempotency-Keybound to a unique upstream id. - Enable Parse response so the returned JSON is available to later modules.
n8n
- Add an HTTP Request node.
- Method and URL as above.
- Authentication: Generic Credential Type → Header Auth, name
Authorization, valueBearer YOUR_API_TOKEN. Store it as a credential so it is not in the workflow JSON. - Headers: add
Idempotency-Keyas an expression, for example{{ 'invoice:' + $json.order_id }}. - Body: JSON, from the recipe.
- For scheduled syncs, put a Schedule Trigger in front, store
meta.server_timein a workflow static-data field, and pass it back asupdated_sincenext run (minus one minute; see Sync). Use Pagination on the HTTP node withpageincrementing untilmeta.total_pagesis reached.
Scripts
Everything below is plain HTTPS plus JSON; no SDK is required. There is no official client library yet, so pick your language's standard HTTP client and wrap these three things once: the base URL and token, an error handler that branches on status, and a retry loop for 409/429/5xx that keeps the same Idempotency-Key.
curl
curl "https://app.beeswaxapp.com/new_api/v1/invoices?outstanding=true&per_page=100" \
-H "Authorization: Bearer $BEESWAX_TOKEN" \
-H "User-Agent: acme-report/1.0"
Python
import os, requests
BASE = "https://app.beeswaxapp.com/new_api/v1"
S = requests.Session()
S.headers.update({"Authorization": f"Bearer {os.environ['BEESWAX_TOKEN']}",
"User-Agent": "acme-report/1.0"})
r = S.get(f"{BASE}/invoices", params={"outstanding": "true", "per_page": 100})
r.raise_for_status()
for inv in r.json()["invoices"]:
print(inv["number"], inv["payable"], inv["due_on"])
Ruby
require "net/http"; require "json"; require "bigdecimal"
uri = URI("https://app.beeswaxapp.com/new_api/v1/invoices?outstanding=true&per_page=100")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{ENV.fetch("BEESWAX_TOKEN")}"
req["User-Agent"] = "acme-report/1.0"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
raise "#{res.code}: #{res.body}" unless res.is_a?(Net::HTTPSuccess)
JSON.parse(res.body)["invoices"].each do |inv|
puts [inv["number"], BigDecimal(inv["payable"]).to_s("F"), inv["due_on"]].join(" ")
end
JavaScript (Node 18+)
const BASE = "https://app.beeswaxapp.com/new_api/v1";
const headers = { Authorization: `Bearer ${process.env.BEESWAX_TOKEN}`, "User-Agent": "acme-report/1.0" };
const res = await fetch(`${BASE}/invoices?outstanding=true&per_page=100`, { headers });
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const { invoices, meta } = await res.json();
console.log(meta.total_count, "outstanding");
for (const inv of invoices) console.log(inv.number, inv.payable, inv.due_on);
Google Sheets (Apps Script)
function pullOutstanding() {
const token = PropertiesService.getScriptProperties().getProperty("BEESWAX_TOKEN");
const res = UrlFetchApp.fetch("https://app.beeswaxapp.com/new_api/v1/invoices?outstanding=true&per_page=100",
{ headers: { Authorization: "Bearer " + token }, muteHttpExceptions: true });
if (res.getResponseCode() !== 200) throw new Error(res.getContentText());
const rows = JSON.parse(res.getContentText()).invoices.map(i => [i.number, i.title, i.due_on, Number(i.payable)]);
const sheet = SpreadsheetApp.getActiveSheet();
sheet.clearContents();
sheet.getRange(1, 1, rows.length + 1, 4).setValues([["Number", "Title", "Due", "Owing"], ...rows]);
}
Store the token in script properties, not in the sheet.
AI assistants (MCP)
The Beeswax MCP server (beeswax-mcp on npm, and a one-click extension for Claude Desktop) wraps this API for Claude and other MCP-capable apps. Every question the assistant asks becomes a normal, authenticated API call with the token you give it, so the same scopes and the same ledger rule apply. Setup for each app, connecting more than one account, and a full list of what the assistant can do are in Connect to Claude & AI assistants.
If you are building your own agent rather than using an MCP app: the ledger rule, Idempotency-Key on every write, and a scoped read-mostly token are the three things that keep an agent from doing damage. The MCP server's source is a worked example of all three.
Making an automation trustworthy
- Scope the token to the job. A "new invoice → Slack" Zap needs
invoices:read, nothing else. - Always send
Idempotency-Keyon writes from tools that can re-run a step. Zapier replays, n8n retries and Make re-executions are exactly the duplicate-creating events the header exists for. - Watch the token's *last used* in Account Settings; a live automation should keep it moving. A stale one has broken silently.
- Keep an eye on the 300-requests-a-minute limit. A Zap that fires per row of a spreadsheet import can hit it; batch, or add a delay step.
- Never put a token in a shared spreadsheet, a public workflow template or a browser extension. Use each tool's credential store.