Developer API
The CLI and the GitHub Action use this API to publish builds. Your own scripts can use it to read your orders, licenses and reviews. You can also download the same data as CSV files from the dashboard (see CSV downloads).
Tokens
Create a token in Developer → API tokens (two-step sign-in required). Tokens look like axd_ plus 40 hex characters, expire after 30, 90 or 365 days, and have one or more scopes:
builds:read: list apps and read build status. Every token has it.builds:write: upload and submit builds (Can submit builds).sandbox:write: sandbox test licenses and trial time travel through MCP (Can use the sandbox).data:read: read orders, licenses and reviews (Can read orders, licenses and reviews).
A token cannot change prices, listings or payouts. AppsXi stores only a hash; copy the token when it is shown. Send it as Authorization: Bearer axd_….
Endpoints
GET /api/v1/apps
{ "apps": [{ "id": "<uuid>", "public_id": "app_…", "slug": "my-app", "name": "My App", "status": "live", "pricing": "paid" }] }POST /api/v1/uploads
Body { "app": "my-app", "filename": "MyApp-1.2.0.dmg", "size": 123456789 }. Returns a two-hour signed upload:
{
"file_key": "dev/…/MyApp-1.2.0.dmg",
"bucket": "uploads",
"signed_url": "…",
"token": "…",
"tus_endpoint": "https://….storage.supabase.co/storage/v1/upload/resumable/sign",
"apikey": "…",
"chunk_size": 6291456
}Upload files up to 6 MB with one PUT to signed_url. Larger files use TUS at tus_endpoint with the header x-signature: <token> and apikey, metadata bucketName, objectName (= file_key) and contentType, in 6 MB chunks.
POST /api/v1/builds
{ "app": "my-app", "file_key": "dev/…/MyApp-1.2.0.dmg", "version": "1.2.0", "channel": "stable", "ed_signature": "…", "release_notes": "…" }platform is detected from the file when omitted. Returns 201 with { "build_id": "…", "status": "queued", "status_url": "/api/v1/builds/…" }. Send AppXi-Client: action from CI.
GET /api/v1/builds/{id}
{
"id": "…", "app": "my-app", "platform": "macos", "version": "1.2.0", "status": "denied",
"decision_reason": "Not notarized",
"findings": [{ "severity": "deny", "kind": "signing", "title": "…", "file": null, "fix": "…" }],
"url": "https://appsxi.com/dev/apps/…"
}Poll every 20 seconds or more while status is queued or running.
Orders, licenses and reviews
These three endpoints need a token with data:read. They return your rows oldest first, so a script can fetch everything once and then only what is new.
| Query parameter | What it does |
|---|---|
app | One app, by slug, public id (app_…) or id. All your apps when left out. |
since | Rows created at or after this time: a date (2026-10-01, midnight UTC) or a date and time with a zone (2026-10-01T12:00:00Z). |
until | Rows created before this time, in the same format. |
environment | live or sandbox. Both when left out (orders and licenses). |
limit | Rows per page, 1 to 200. Default 100. |
cursor | The next_cursor from the previous page. |
Every response looks like this:
{ "data": [ … ], "has_more": true, "next_cursor": "WyIyMDI2LTEw…" }Keep calling with cursor=<next_cursor> until has_more is false. Keep the same filters while you page. To pick up new rows later, save the last row's created_at and pass it as since, then skip the ids you already have.
Buyers and privacy
A buyer appears only as buyer, an id like buyer_3f9c…. It is the same for one buyer across all rows of one app, and the same as the sub of AppsXi license tokens and buyer.id in license bridge events. It is different for each of your other apps, so it cannot be used to follow a buyer across apps. The API never returns names or emails. The orders CSV in the dashboard does include each buyer's email, because you are the seller and the Orders page already shows it.
GET /api/v1/orders
Paid, refunded, disputed and failed orders. Pending checkouts are left out.
{
"id": "ord_…", "created_at": "2026-10-01T09:12:44.512+00:00", "paid_at": "2026-10-01T09:12:51.020+00:00",
"app": "my-app", "app_id": "app_…", "environment": "live", "status": "paid", "currency": "USD",
"list_price_cents": 2900, "discount_cents": 290, "subtotal_cents": 2610, "tax_cents": 0,
"app_fee_cents": 131, "affiliate_cents": 0, "refunded_cents": 0,
"coupon": "LAUNCH10", "pay_what_you_want": false, "bundle": null, "affiliate": null,
"source": "launch_day", "source_detail": null, "license": "lic_…", "buyer": "buyer_…"
}status is paid, refunded, partially_refunded, disputed or failed. A refund changes the order's status and refunded_cents but not its created_at; use license bridge events to hear about refunds as they happen. Square's processing fee is not included; see your Square Dashboard.
GET /api/v1/licenses
Licenses and trials of your apps, including free ones and sandbox test licenses.
{
"id": "lic_…", "created_at": "…", "app": "my-app", "app_id": "app_…", "environment": "live",
"kind": "paid", "license_type": "personal", "status": "active", "seats_max": 2, "seats_used": 1,
"trial_started_at": null, "trial_expires_at": null, "order": "ord_…", "key_last4": "7KQ2",
"label": null, "buyer": "buyer_…"
}kind is paid, free_grant, trial, bridge or test (sandbox). status is active, trial, expired or revoked. key_last4 helps you match a key a buyer quotes in a support email; the full key is never returned. label is the name you gave a sandbox test license.
GET /api/v1/reviews
Reviews of your apps with your reply. Reviews AppsXi hid for breaking the content policy are left out.
{
"id": "<uuid>", "created_at": "…", "updated_at": "…", "app": "my-app", "app_id": "app_…",
"rating": 5, "title": "Great", "body": "…", "status": "published",
"license": "lic_…", "buyer": "buyer_…", "reply": "Thanks!", "replied_at": "…"
}Example
curl -s -H "Authorization: Bearer $APPXI_TOKEN" \
"https://appsxi.com/api/v1/orders?app=my-app&since=2026-10-01&limit=200"The same lists are MCP tools (list_orders, list_licenses, list_reviews) for AI assistants; see MCP.
CSV downloads
In the dashboard, Orders has buttons for orders and licenses, Reviews for reviews, and each app's overview page for all three for that app. Pick an app (or all apps) and a date range (UTC, both days included; leave empty for everything). Downloads need two-step sign-in in this session and hold up to 100,000 rows per file; use a shorter range for more.
The columns are the API fields above, in the same order. The orders file adds buyer_email. Amounts are in cents. Text that a spreadsheet could run as a formula (starting with =, +, - or @) starts with an apostrophe, so a review cannot run code on your computer.
Errors
Errors are JSON: { "error": "<code>", "message": "…" }. Codes: unauthorized (401), scope (403, the token lacks data:read), unknown_app (404), format (400), size (400), rate_limited (429), bad_request (400), unavailable (503).
Each developer account can make up to 1,200 data requests an hour, and each address up to 600 API requests an hour.