Authentication
Authenticate your integration with an API key and select your store.
Use an API key for your own integrations. The official SellApp CLI uses OAuth on supported v2 endpoints and manages its credentials for you. Two headers identify the caller and select a store:
Authorization: Bearer …contains your API key or OAuth access token.X-STOREselects the store by its slug, such aslaunch-lab.
The CLI sends its OAuth access token and X-STORE on store business requests.
The granted admin scope covers current and future accessible stores. Current membership and store-role permissions constrain each request. Follow CLI installation, then connect it with
sellapp login and browser consent. General-purpose REST OAuth applications are not supported.
Sell MCP uses a separate browser OAuth connection; its tokens cannot
be used as REST credentials.
Identity, store discovery, store detail by ID, and store creation accept API keys or official CLI OAuth without X-STORE. Permission inspection requires an explicit X-STORE. OAuth installation management remains OAuth only.
Customer Portal endpoints use a 15-minute customer-session bearer token and
return only that customer's redacted resources. Do not swap these token types:
each endpoint reference names the credential it expects.
The base URL is https://sell.app/api. Requests operate on the real data of
the selected store; SellApp does not provide a separate API sandbox.
Authentication with a secret API key
Send your key in the Authorization header, with the word Bearer and a space
before it. Keep this on your server: never put a secret key in browser code,
mobile app bundles, public repositories, or screenshots.
Set SELLAPP_API_BASE_URL, SELLAPP_API_KEY, and SELLAPP_STORE as shown
in the quickstart.
curl --request GET \
--url "${SELLAPP_API_BASE_URL}/v2/orders" \
--header "Authorization: Bearer ${SELLAPP_API_KEY}" \
--header "X-STORE: ${SELLAPP_STORE}" \
--header 'Content-Type: application/json'Creating and managing a secret API key
Create and manage your own keys in API keys, including when your account has no stores. Always keep your API key safe and rotate it if you suspect it has been compromised.
API keys belong to an account, not a storefront. Each key has abilities:
permissions such as listing for catalog access or invoice for purchases.
Enable only the abilities your integration needs. Existing keys are unchanged:
account:read enables identity, discovery and permission inspection;
stores:create separately enables store creation. The older read, store,
listing and invoice abilities do not imply these account abilities.
A key with * satisfies new abilities, but still obeys membership, role and
store restrictions. OAuth's admin grant is separate from this wildcard.
The dashboard shows abilities under group headings with short labels. Find an ability key in Create API Key, or in a key's Permissions dialog, with this table:
| Group | Dashboard label | Ability key |
|---|---|---|
| Account | Read account | account:read |
| Account | Create stores | stores:create |
| Store | Settings | store |
| Store | Payment methods | payment-method |
| Catalog | Products | listing |
| Catalog | Product groups | group |
| Catalog | Product sections | section |
| Catalog | Stories | highlight |
| Catalog | Bundles | bundle |
| Catalog | Licenses | license |
| Orders | Orders | invoice |
| Orders | Charges | charge |
| Marketing | Coupons | coupon |
| Marketing | Promotions | promotion |
| Marketing | Upsells | upsell |
| Marketing | Rewards | reward |
| Customers | Feedback | feedback |
| Customers | Blacklist | blacklist |
| Customers | Customer credits | credit |
| Support | Tickets | ticket |
| Support | Automation | support.automation |
| Support | Refunds | support.refund |
| Wallet | Settings | wallet |
| Wallet | Balances | wallet:write |
| Affiliates | View | affiliate |
| Affiliates | Manage | affiliate:write |
| Affiliates | Manage payouts | affiliate:payout |
| Affiliates | View referral sources | affiliate:sensitive |
| Billing | Balance | seller-balance |
| Billing | Payouts | seller-payout |
| Integrations | Community | community |
| Integrations | Webhooks | webhook |
An ability missing from this table appears under Other. A key that already
has * shows it in Permissions as its own All API abilities (*)
checkbox. Select all and Clear change only the listed abilities, so they
never remove *. Unticking that checkbox and saving removes full access, and
the dashboard cannot add * back.
Each key in the list shows its store access and when it was last used. Open the key's ⋯ menu and click Permissions to change its abilities and store access, or Delete to delete it.
Choose All accessible stores for current and future stores, or Selected
stores only for a fixed list. An empty selection grants no store access.
Membership removals and restriction edits apply to subsequent requests without
rotating the key. Selected-store keys cannot create stores, even with
stores:create; use an unrestricted key with that ability. The API cannot widen
its own list. These key restrictions do not change CLI or MCP OAuth consent.
Both the key and the account must have permission for an operation. Store owners still need the relevant key abilities; staff accounts also need the matching store permissions. Provider limits and resource-state rules still apply.
Affiliate payout endpoints manage records of payments arranged by the merchant. Their permissions do not authorize SellApp to transfer funds. Customer wallet and credit endpoints separately manage customer prepaid credit.
If your account is part of a storefront as support staff, you can only access resources that your account has been given permission to manage. For example, a support staff account limited to tickets cannot modify products or create groups through the API.
Every response includes an X-Request-ID. Log it for support and debugging,
but never log the bearer key.
Multiple stores
Send X-STORE on every store business request to select the intended store.
OAuth requires it. Ordinary API-key routes can fall back to the account's current
or first available store when it is omitted; routes that explicitly require the
header reject its omission. Always send it so a change in the account's current
store cannot redirect your integration's work.
The value is the storefront slug, not its numeric ID. A wrong or
unknown slug returns 404; a valid store that the account cannot manage returns
403.
If you want to access launch-lab.sell.app, you would pass the slug launch-lab via the
X-STORE header:
curl --request GET \
--url "${SELLAPP_API_BASE_URL}/v2/orders" \
--header "Authorization: Bearer ${SELLAPP_API_KEY}" \
--header "X-STORE: ${SELLAPP_STORE}" \
--header 'Content-Type: application/json'Using an SDK
An SDK handles request setup. Configure a store for business calls; account calls omit the store header even when a default store is configured. A library does not change the permissions your key needs.
For currently known packages and integrations, see SDKs & Extensions.
Discover your account before selecting a store
Create a key with account:read. No existing store is needed. Set
SELLAPP_API_KEY in your server environment and use this read-only request:
export SELLAPP_API_BASE_URL="https://sell.app/api"
curl --fail-with-body --silent --show-error \
"$SELLAPP_API_BASE_URL/v2/me" \
--header "Authorization: Bearer $SELLAPP_API_KEY"
curl --fail-with-body --silent --show-error \
"$SELLAPP_API_BASE_URL/v2/stores" \
--header "Authorization: Bearer $SELLAPP_API_KEY"The identity response contains data.id, data.username, data.email, and
data.credential_type: "sanctum". Store discovery returns data with each
store's string id, slug, and name. {"data":[]} means you have no
accessible stores for this key; it is a successful request.
Create your first store
This creates a real store owned by your account. Enable stores:create on an
unrestricted key. Keep one idempotency key and identical body for the intended
creation; reuse them after a lost response, including after key rotation.
Receipts last 24 hours and are shared by the same user and operation across
supported credential types. Current authorization is checked before replay.
curl --fail-with-body --silent --show-error \
--request POST "$SELLAPP_API_BASE_URL/v2/stores" \
--header "Authorization: Bearer $SELLAPP_API_KEY" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: create-launchlab-01" \
--data '{"name":"Launch Lab","slug":"launchlab"}'Choose an available slug before sending the request. Slugs contain letters and
digits and are normalized to lowercase. A successful 201 returns the store
ID, slug, name, visibility, storefront URL and dashboard URL. Visibility defaults
to PUBLIC. Retain data.id and data.slug; creation does not change your
dashboard selection. Send the returned slug in X-STORE for subsequent product
and variant requests, with the relevant key abilities and current store role.
Continue with Create a product and variant.
An identical retry returns the original result and Idempotent-Replayed: true.
A changed body with the same key returns 409; check the intended request
before retrying. Missing required fields, reserved or duplicate slugs return
422. A key without creation permission or restricted to selected stores
returns 403. Revoked or expired credentials return 401. Keep
X-Request-ID when investigating failures; do not log keys.
First store with the Node SDK
Install the Node SDK from its current source instructions.
Set SELLAPP_API_KEY on your server with account:read, stores:create and
listing. Use an unrestricted key. This example creates a real store and reads
at most one product. Set SELLAPP_NEW_STORE_SLUG to an available slug and
SELLAPP_CREATION_KEY to one stable value for this intended creation. Retain both
values when retrying after a lost response.
import { SellApp } from 'sellapp';
const apiKey = process.env.SELLAPP_API_KEY;
const slug = process.env.SELLAPP_NEW_STORE_SLUG;
const creationKey = process.env.SELLAPP_CREATION_KEY;
if (!apiKey || !slug || !creationKey) {
throw new Error('Set SELLAPP_API_KEY, SELLAPP_NEW_STORE_SLUG and SELLAPP_CREATION_KEY');
}
const account = new SellApp({ apiKey, store: '', maxRetries: 0 });
const identity = await account.account.getAccount();
const existing = await account.account.listStores();
console.log('Account:', identity.data.id, 'Accessible stores:', existing.data.length);
const created = await account.account.createStore({
name: 'Launch Lab',
slug,
requestOptions: { headers: { 'Idempotency-Key': creationKey } },
});
console.log('Created store:', created.data.id, created.data.slug);
const store = new SellApp({ apiKey, store: created.data.slug, maxRetries: 0 });
const page = await store.products.list({ limit: 1 });
console.log('Products returned:', page.data.length);The new store starts without products, so Products returned: 0 is expected.
Keep the returned ID and slug in your integration's configuration, then follow
Create a product and variant for catalog setup.
Read across several stores
Use account:read and listing on one key. This script reads one product page
of at most five products from each of at most ten discovered stores. It includes
store identity in every result and reports failures individually. Membership,
role or key restrictions can change between discovery and a business request.
These requests do not form one transaction.
import { SellApp, SellAppApiError } from 'sellapp';
const apiKey = process.env.SELLAPP_API_KEY;
if (!apiKey) throw new Error('Set SELLAPP_API_KEY');
const account = new SellApp({ apiKey, store: '', maxRetries: 0 });
const discovered = await account.account.listStores();
const selected = discovered.data.slice(0, 10);
const results = [];
for (const store of selected) {
const client = new SellApp({ apiKey, store: store.slug, maxRetries: 0 });
try {
const page = await client.products.list({ limit: 5 });
results.push({ storeId: store.id, slug: store.slug, products: page.data });
} catch (error) {
results.push({
storeId: store.id,
slug: store.slug,
error: error instanceof SellAppApiError
? { status: error.status, code: error.code, requestId: error.requestId }
: { message: 'Request failed; inspect the connection or client configuration' },
});
}
}
console.log(JSON.stringify({
results,
storesNotRead: discovered.data.length - selected.length,
}, null, 2));An empty results array means discovery succeeded with no accessible stores.
A failed discovery stops the script. A store entry with error is a partial
failure; check that entry's status and request ID before retrying it. Do not
interpret a partial read as a complete cross-store report.
Inspect permissions
Call GET /v2/permissions with account:read and the intended X-STORE.
For API keys, key_abilities records the key's abilities,
store_permissions records the current role, and effective_abilities
contains their intersection. store_access is all or selected.
The OAuth response instead retains approved_scopes, token_scopes, and
effective_scopes; the last field lists reviewed operation permission
categories, not granted scopes. Use credential_type to identify the response.
These are authorization diagnostics. Individual operations can require additional permissions and record-state or provider checks; the response does not establish payment eligibility or guarantee that a mutation will succeed. A store denial does not invalidate a credential that remains usable elsewhere.