Start
Quickstart
Key, first typed call, next page, one record. The example uses academic.search, a free source, so it costs 0 credits and works on the Free plan.
1. Get a key
Sign up or log in, then create a key under API keys in the dashboard. It looks like apa_live_… and can be copied again from the dashboard at any time. Send it as Authorization: Bearer on every call. Keys are for servers and command lines; browsers on other origins cannot call the API (see keys and security).
2. Call it with curl
sh
export AIPA_API_KEY=apa_live_… # dashboard → API keys → Create a key; a server-side secret
API=https://api.powerups-ai.store
# 1. Confirm the key and balance
curl -s $API/v1/account -H "Authorization: Bearer $AIPA_API_KEY"
# 2. Execute a typed search (free source, 0 credits)
curl -s -X POST $API/v1/capabilities/academic.search/execute \
-H "Authorization: Bearer $AIPA_API_KEY" -H "Content-Type: application/json" \
-d '{"query":"transformer attention","sort":"cited","num":3}'
# 3. Next page (same search_id from step 2)
curl -s -X POST $API/v1/follow-up -H "Authorization: Bearer $AIPA_API_KEY" \
-H "Content-Type: application/json" -d '{"search_id":"srch_…","action":"more"}'
# 4. Open a record (free for this source; a repeat is served from cache)
curl -s -X POST $API/v1/follow-up -H "Authorization: Bearer $AIPA_API_KEY" \
-H "Content-Type: application/json" -d '{"record_id":"rec_…","action":"details"}'The execute response is the execute envelope:
execute envelope (illustrative values)
{
"search_id": "srch_ePaazJiKYqmgc0hK",
"capability": "academic.search",
"results": [
{
"record_id": "rec_siQNelpEPOWay4PB",
"title": "Attention Is All You Need",
"url": "https://doi.org/10.48550/arxiv.1706.03762",
"snippet": "…",
"year": 2017,
"authors": [
"Ashish Vaswani",
"…"
],
"cited_by": 12345,
"openalex_id": "W2963403868"
}
],
"result_count": 3,
"has_more": true,
"next": {
"url": "/v1/follow-up",
"actions": [
"more",
"update"
],
"example": {
"search_id": "srch_ePaazJiKYqmgc0hK",
"action": "more"
}
},
"record_actions": [
"details"
],
"charged_credits": 0,
"balance": {
"used": 12,
"cap": 500,
"period_end": "2026-10-19T17:35:40.303Z"
}
}search_idand everyrecord_idare minted by the API and live for 24 hours after last use.next.actionslists the search actions this capability accepts andnext.exampleis a follow-up body that is valid right now (ornullwhen nothing applies).charged_creditsandbalanceare on every metered response.
3. The same in TypeScript, typed
Generate types from the public OpenAPI document once at build time; the document has one operation per capability, so the body of each path is typed:
sh
# In a new Node.js 20+ project, install this checked generator/compiler pair
npm install --save-dev --save-exact openapi-typescript@7.13.0 typescript@5.9.3 @types/node@20.19.0
npm install --save-exact openapi-fetch@0.17.0
# Generate types; preserve optional request fields with server-side defaults
npx openapi-typescript https://api.powerups-ai.store/v1/openapi.public.json -o ./ai-power-ups.d.ts --default-non-nullable=falseusage.mts
import createClient from "openapi-fetch";
import type { paths } from "./ai-power-ups.js";
const client = createClient<paths>({
baseUrl: "https://api.powerups-ai.store",
headers: { Authorization: `Bearer ${process.env.AIPA_API_KEY}` }, // server-side only
});
const { data, error, response } = await client.POST("/v1/capabilities/academic.search/execute", {
body: { query: "transformer attention", sort: "cited", num: 3 },
});
if (error) throw new Error(`${response.status} ${error.error.code}: ${error.error.message}`);
for (const record of data.results) console.log(record.title, record.url);
if (data.has_more) {
const more = await client.POST("/v1/follow-up", {
body: { search_id: data.search_id, action: "more" },
});
// more.data is the same envelope with the same search_id
}Check the example
npx tsc usage.mts --module NodeNext --target ES2022 --strict --noEmit4. The same in Python
Python 3 (standard library only)
import json, os, urllib.request
API = "https://api.powerups-ai.store"
KEY = os.environ["AIPA_API_KEY"] # server-side secret
def call(method, path, body=None):
data = None if body is None else json.dumps(body).encode()
req = urllib.request.Request(API + path, data=data, method=method, headers={
"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"})
try:
with urllib.request.urlopen(req, timeout=60) as res:
return res.status, json.load(res)
except urllib.error.HTTPError as err:
return err.code, json.load(err)
status, page = call("POST", "/v1/capabilities/academic.search/execute",
{"query": "transformer attention", "sort": "cited", "num": 3})
if status != 200:
raise SystemExit(f"{status} {page['error']['code']}: {page['error']['message']}")
for record in page["results"]:
print(record["title"], record.get("url"))
if page["has_more"]:
status, page = call("POST", "/v1/follow-up", {"search_id": page["search_id"], "action": "more"})What to expect
GET /v1/accountreturns 200 withcredits.capof at least 500 on the Free plan.- Execute returns 200,
capabilityacademic.search,result_count3,charged_credits0. morereturns 200 with the samesearch_idand new record ids.- A second
detailson the same record returnscached: trueandcharged_credits0. - An unknown key in the body returns 400 with
violations[0].pathnaming it. - A bad key returns 401 with an
x-ratelimit-limit: 60header.
Run calls for one account sequentially: the API admits one metered call per account at a time and answers a concurrent one with
429 rate_limited. Details in credits and limits.