Quick start
The API gives the same records as your account: products, variants, prices, stock and every change,
as JSON. It is on every plan, and reads only the sources your plan opens. Every call goes to https://carryspec.com/api/v1.
Get a key
Log in and open the API page of your account. Give the key a name you will recognise, such as the program that will use it, and choose Make a key. The free plan works.
The key starts bk_ and is shown once: put it in an environment variable, which every example
in these docs reads.
export CARRYSPEC_API_KEY="bk_…"
Keep keys on a server. A key in a web page or a public repository can be used by anyone who finds it. An account can have 5 working keys.
Make your first request
GET /api/v1/me is a good first call: it says which account the key belongs to, its plan, the
sources it may read, and how many calls are left today.
GET /api/v1/me
- cURL
- JavaScript
- Python
curl --request GET \
--url 'https://carryspec.com/api/v1/me' \
--header "Authorization: Bearer $CARRYSPEC_API_KEY"
const response = await fetch("https://carryspec.com/api/v1/me", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.CARRYSPEC_API_KEY}`,
},
});
if (!response.ok) {
throw new Error(`${response.status}: ${await response.text()}`);
}
console.log(JSON.stringify(await response.json(), null, 2));
import json
import os
import urllib.error
import urllib.request
request = urllib.request.Request(
"https://carryspec.com/api/v1/me",
method="GET",
headers={
"Authorization": "Bearer " + os.environ["CARRYSPEC_API_KEY"],
},
)
try:
with urllib.request.urlopen(request) as response:
print(json.dumps(json.load(response), indent=2))
except urllib.error.HTTPError as error:
print(error.code, error.read().decode())
Read the answer
A key that works is answered 200 with the account:
{
"api_calls_today": 0,
"by": "string",
"created_at": "2026-01-01T00:00:00Z",
"email": "string",
"every_source": true,
"id": "00000000-0000-0000-0000-000000000000",
"limits": {
"alerts": 0,
"api_calls_per_day": 0
},
"plan": {
"name": "string",
"slug": "string",
"until": "2026-01-01T00:00:00Z"
},
"sources": [
"string"
]
}
api_calls_todayintegerRequiredCalls its keys have made today (the day is UTC's), this one included.
bystringRequiredHow this request was recognised: "key" or "session".
every_sourcebooleanRequiredTrue for an account held to no list: it reads every source there is, and
sourcesis empty.limitsLimitsRequired2 fields
limits.alertsintegerRequiredHow many alerts the account may keep.
limits.api_calls_per_dayinteger or nullRequiredCalls a day its keys may make. Null: they are not counted.
planPlanRequired3 fields
plan.namestringRequiredplan.slugstringRequiredplan.untildate-time or nullRequiredWhen it ends. Null: it has no end.
sourcesarray of stringRequiredThe sources the account may read, by short name. Each is in /api/v1/sources with what is held from it.
401 with an error instead. Errors & limits lists every code.
Next steps
List products with
GET /api/v1/products, or read what changed withGET /api/v1/events.- Authentication: How a key is sent, which calls need one, and what a refused key looks like.
- Errors & limits: Every error code, what each plan allows, and the rate-limit headers.
- Pagination: Numbered pages for products; cursors for the change feed, back or forward.
- Concepts: Sources, products and variants, money, time, alerts, and the ten kinds of change.
- Webhooks: What is sent, how to check the signature, retries, duplicates and testing.
- Slack & ClickUp: Send alerts to a Slack channel or a ClickUp automation, without writing code.
API reference: every endpoint and field, generated from the API itself.
Prefer to have changes sent to you? Set up an alert with a webhook, or send alerts to Slack or ClickUp.