Base 3:16
API

Using the API

Authentication, errors, idempotency and pagination.

Base API: https://api.base316.com

Authentication

curl https://api.base316.com/v1/whoami \
  -H "Authorization: Bearer $BASE316_SECRET_KEY"

The key decides which environment you touch — there is no environment parameter to get wrong. Most endpoints require a secret key; a publishable key reaches only the public catalog and sign-in flows.

Confirm what a key points at before anything else:

GET /v1/whoami

Errors

Every failure returns the same shape:

{
	"error": {
		"type": "invalid_request",
		"code": "INVALID_REQUEST",
		"message": "Provide either `customerId` or `externalId`."
	},
	"requestId": "req_..."
}

Branch on code. It is part of the contract and changes only additively; message is written for a human and may be reworded.

CodeStatusMeaning
MISSING_API_KEY401No key was sent
INVALID_API_KEY401Revoked, or not for this environment
SECRET_KEY_REQUIRED403A publishable key cannot do this
FORBIDDEN403The key is valid and this action is not allowed for it
NOT_FOUND404No such resource in this environment
INVALID_REQUEST400The request was malformed
CONFLICT409Conflicts with the current state — a duplicate key, an overpayment
IDEMPOTENCY_KEY_REUSED409The same key was sent with a different body
IDEMPOTENCY_KEY_IN_FLIGHT409The first attempt is still running — retry, and it settles
PLAN_LIMIT402The workspace's plan does not include enough of something
PLAN_FEATURE402The workspace's plan does not include the capability at all
RATE_LIMITED429Too many requests — honour Retry-After
SERVICE_UNAVAILABLE503A service the call depends on did not answer — retry with backoff
INTERNAL500Our fault

Quote requestId when asking for help; it identifies the exact call.

When a plan runs out

PLAN_LIMIT and PLAN_FEATURE both carry 402, and they mean different things. A limit is a number that ran out; a feature is one the tier never had. Waiting helps with the first only when the allowance resets monthly.

param names the allowance, so you can tell them apart without reading the message:

{
	"error": {
		"type": "plan_limit",
		"code": "PLAN_LIMIT",
		"message": "Your plan allows 100 customers. Upgrade to add more.",
		"param": "customers"
	},
	"requestId": "req_..."
}

The calls that can return it are the ones that create something countable or consume an allowance: POST /v1/customers, POST /v1/users, POST /v1/organizations and POST /v1/events.

Never retry a 402. The answer does not change until the plan does, or — for meterEventsPerMonth — until the month does.

These limits were advertised before they were enforced. An integration written against an earlier version may meet a 402 for the first time without anything in it having changed.

Idempotency

POST /v1/events requires an idempotencyKey. The same key records the event once, however many times you send it — a retry after a network timeout is safe by construction.

It is required rather than optional because only you know what "the same work" means. A request id is usually the right answer.

Pagination

List endpoints return:

{ "data": [ ... ], "nextCursor": "..." }

Pass cursor to continue and limit to size a page. A nextCursor of null means you have reached the end.

Money

Amounts are strings, always. "1234.56", never 1234.56.

A float is how a total ends in .30000000000000004 on somebody's invoice. Parse them with a decimal library, not Number, if you are going to do arithmetic on them.

Currency is a three-letter code, and Base316 never sums across currencies. Adding dollars to euros needs an exchange rate, a rate needs a date, and a billing engine that quietly picks one produces a number nobody can reconcile.

Timestamps

ISO 8601 in UTC. Send them the same way.

On this page