Migrating to v3 of the API
v3 of the Salable API has a much more simplified implementation. Data access patterns are now aligned across all types of subscriptions meaning less endpoints are required to achieve tasks.
It is our recommendation that all customers migrate to v3 as soon as possible.
Overview
How do I use v3 of the API?
To start using the new and improved V3 endpoints, provide a version header with a value of v3
to each request you send
to the API.
How do I use v3 in the Node SDK?
Update the package to v5.0.0
. Replace the Salable
class with the new initSalable
function and set the version to v3
.
In v5.0.0
the API version v2
will still be supported. For more information read the v5.0.0 changelog.
v4.0.0 implementation
const salable = new Salable('your-api-key', 'v2');
v5.0.0 implementation
const salableV2 = initSalable('your-api-key', 'v2'); // v2 still supported
const salableV3 = initSalable('your-api-key', 'v3');
License vs Seat
The term ‘license’ has been replaced with ‘seat’. Previously, it was confusing referring to 'license' for flat-rate and usage-based subscriptions and then only using 'seat' for per-seat subscriptions. Using 'seat' aligns our documentation, API and app across all subscription types.
Critical breaking changes
License endpoints deprecated
In v3, all license endpoints have been removed. Previously, seats created without a checkout did not have a parent subscription and had to be managed through the license endpoints. In contrast, seats created through a checkout always had a parent subscription and were managed through the subscription endpoints. This inconsistency created two different implementation paths.
For consistency, we have now created a parent subscription for all seats, meaning all seats are now managed through the subscription endpoints. This change simplifies API implementation by providing a single, consistent approach and eliminates the need for separate license endpoints.
Capabilities deprecated
Capabilities used to be stored on the License at the point of creation with no way of editing them. Instead, we have now opted to use the plan's features which allow you to update a grantee’s access on-the-fly through the Salable dashboard.
License check deprecated
The license check endpoint has been replaced with a new endpoint called entitlements check. Any reference to capabilities
in the response has been replaced with features. The principle of the endpoint remains unchanged from v2, it still
checks what the grantee(s) has access to. To learn more on how to migrate from capabilities to features read
this detailed guide. To use the new endpoint, ensure your api key
has the entitlements:check
scope.
v2 Response
{
"signature": "3044022004f243b5bb524689498537ff6bf865d847b8f6c6f65db687036814281fe4c1cc022043e645238050c587b6a79343a4acad58a925d3fbf3ba914d2f4c38a1f8439993",
"capabilities": [
{
"capability": "plan_name",
"expiry": "2024-07-09T20:08:26.685Z"
}
]
}
v3 Response
{
"signature": "3044022004f243b5bb524689498537ff6bf865d847b8f6c6f65db687036814281fe4c1cc022043e645238050c587b6a79343a4acad58a925d3fbf3ba914d2f4c38a1f8439993",
"features": [
{
"feature": "plan_name",
"expiry": "2024-07-09T20:08:26.685Z"
}
]
}
v3 Implementation
- JS SDK
- Node SDK
- Fetch
- cURL
import { getGrantee } from '@salable/js';
const { hasFeature } = await getGrantee({
apiKey: 'your-salable-api-key',
productUuid: 'your-products-uuid',
granteeId: 'your-grantees-id',
});
// Check for a feature
const isUserLicensedToPerformAction = hasFeature('csv-export');
// or a plan
const isUserLicensedToPerformAction = hasFeature('pro');
import { initSalable } from '@salable/node-sdk';
const salable = initSalable('your-salable-api-key', 'v3');
const { features } = await salable.entitlements.check({
productUuid: 'your-product-uuid',
granteeIds: ['your-grantees-id'],
});
const canUserPerformAction = features.some((f) => {f.feature === 'your-feature-name'});
const params = new URLSearchParams();
params.append("granteeIds", "id-of-grantee");
params.append("productUuid", "your-product-uuid");
const response = await fetch(`https://api.salable.app/entitlements/check?${params}`, {
headers: {
"x-api-key": "your-salable-api-key",
version: "v3"
},
})
const check = response.status === 204 ? null : await response.json();
curl
-XGET
-H 'x-api-key: your_salable_api_key'
-H 'version: v3'
'https://api.salable.app/entitlements/check?productUuid=your_product_uuid&granteeIds=your-grantees-id'
Breaking changes on existing endpoints
Get All Products
The get all products endpoint now uses cursor based pagination.
v2 Response
[
// products...
]
v3 Response
{
"first": "",
"last": "",
"data": [
// products...
],
}
Deprecated endpoints and Node SDK version updates
Some endpoints have been replaced in v3. Note the examples include v5.0.0
of the Node SDK.
Product
Product Features
Moved to a new paginated get features endpoint with the productUuid
filter applied.
v2 Response
[
// features...
]
v3 Response
{
"first": "",
"last": "",
"data": [
// features...
],
}
v3 Implementation
- Node SDK
- Fetch
- cURL
import { initSalable } from '@salable/node-sdk';
const salable = initSalable('your-salable-api-key', 'v3');
const productFeatures = await salable.features.getAll({
productUuid: 'your-product-id'
});
const params = new URLSearchParams();
params.append("productUuid", "your-product-id");
const response = await fetch(`https://api.salable.app/features?${params}`, {
headers: {
"x-api-key": "your-salable-api-key",
version: "v3"
},
})
const productFeatures = await response.json();
curl
-XGET
-H 'x-api-key: your_salable_api_key'
-H 'version: v3'
'https://api.salable.app/features?productUuid=your_product_uuid'
Product Currencies
Moved to expand currencies on the get product endpoint.
v2 Response
[
// ...product currencies
]
v3 Response
{
// ...product data
"currencies": [
// ...product currencies
],
}
v3 Implementation
- Node SDK
- Fetch
- cURL
import { initSalable } from '@salable/node-sdk';
const salable = initSalable('your-salable-api-key', 'v3');
const productWithCurrencies = await salable.products.getOne('your-product-id', {
expand: ['currencies']
});
const params = new URLSearchParams();
params.append("expand", "currencies");
const response = await fetch(`https://api.salable.app/products/your-product-id?${params}`, {
headers: {
"x-api-key": "your-salable-api-key",
version: "v3"
},
})
const productWithCurrencies = await response.json();
curl
-XGET
-H 'x-api-key: your_salable_api_key'
-H 'version: v3'
'https://api.salable.app/products/your_product_uuid?expand=currencies'
Product Plans
Moved to a new get plans endpoint with the productUuid
filter applied.
v2 Response
[
// ...product plans
]
v3 Response
{
// ...product data
"plans": [
// ...product plans
],
}
v3 Implementation
- Node SDK
- Fetch
- cURL
import { initSalable } from '@salable/node-sdk';
const salable = initSalable('your-salable-api-key', 'v3');
const plans = await salable.plans.getAll({
productUuid: 'your-product-id'
});
const params = new URLSearchParams();
params.append("productUuid", "your-product-id");
const response = await fetch(`https://api.salable.app/plans?${params}`, {
headers: {
"x-api-key": "your-salable-api-key",
version: "v3"
},
})
const plans = await response.json();
curl
-XGET
-H 'x-api-key: your_salable_api_key'
-H 'version: v3'
'https://api.salable.app/plans?productUuid=your_product_uuid'
Product Capabilities
Endpoint removed in v3.
Plan
Plan Features
Moved to expand features on the get plan endpoint.
v2 Response
[
// ...plan features
]
v3 Response
{
// ...plan data
"features": [
// ...plan features
],
}
v3 Implementation
- Node SDK
- Fetch
- cURL
import { initSalable } from '@salable/node-sdk';
const salable = initSalable('your-salable-api-key', 'v3');
const planWithFeatures = await salable.plans.getOne('your-plan-id', {
expand: ['features']
});
const params = new URLSearchParams();
params.append("expand", "features");
const response = await fetch(`https://api.salable.app/plans/your-plan-id?${params}`, {
headers: {
"x-api-key": "your-salable-api-key",
version: "v3"
},
})
const planWithFeatures = await response.json();
curl
-XGET
-H 'x-api-key: your_salable_api_key'
-H 'version: v3'
'https://api.salable.app/plans/your_plan_uuid?expand=features'
Plan Currencies
Moved to expand currencies on the get plan endpoint.
v2 Response
[
// ...plan currencies
]
v3 Response
{
// ...plan data
"currencies": [
// ...plan currencies
],
}
v3 Implementation
- Node SDK
- Fetch
- cURL
import { initSalable } from '@salable/node-sdk';
const salable = initSalable('your-salable-api-key', 'v3');
const planWithCurrencies = await salable.plans.getOne('your-plan-id', {
expand: ['currencies']
});
const params = new URLSearchParams();
params.append("expand", "currencies");
const response = await fetch(`https://api.salable.app/plans/your-plan-id?${params}`, {
headers: {
"x-api-key": "your-salable-api-key",
version: "v3"
},
})
const planWithCurrencies = await response.json();
curl
-XGET
-H 'x-api-key: your_salable_api_key'
-H 'version: v3'
'https://api.salable.app/plans/your_plan_id?expand=currencies'
Plan Capabilities
Endpoint removed in v3.
Subscription
Remove Seats
The remove seats request body remains the same in v3. Moving it to the POST endpoint means adding and removing seats are now performed on the same endpoint.
v3 Implementation
- Node SDK
- Fetch
- cURL
import { initSalable } from '@salable/node-sdk';
const salable = initSalable('your-salable-api-key', 'v3');
await salable.subscriptions.updateSeatCount('your-subscription-id', {
decrement: 1
});
await fetch(`https://api.salable.app/subscriptions/your-subscription-id/seats`, {
method: "POST",
headers: {
"x-api-key": "your-salable-api-key",
version: "v3"
},
body: JSON.stringify({
decrement: 1
})
})
curl
-XPOST
-H 'x-api-key: your_salable_api_key'
-H 'version: v3'
-d '{"decrement": 1}'
'https://api.salable.app/subscriptions/your_subscription_id/seats'
License (Seat)
Licenses (Seats) Count
Moved to subscriptions get count endpoint, this returns an aggregate count of all seats on the subscription that are not
status CANCELED
v3 Implementation
- Node SDK
- Fetch
- cURL
import { initSalable } from '@salable/node-sdk';
const salable = initSalable('your-salable-api-key', 'v3');
await salable.subscriptions.getSeatCount('your-subscription-id');
const response = await fetch(`https://api.salable.app/subscriptions/your-subscription-uuid/count`, {
headers: {
"x-api-key": "your-salable-api-key",
version: "v3"
}
})
const count = await response.json();
curl
-XGET
-H 'x-api-key: your_salable_api_key'
-H 'version: v3'
'https://api.salable.app/subscriptions/your_subscription_id/count'
Licenses Get All (Seats)
Moved to subscriptions get seats endpoint, this returns all seats on the subscription that are not status CANCELED
.
v3 Implementation
- Node SDK
- Fetch
- cURL
import { initSalable } from '@salable/node-sdk';
const salable = initSalable('your-salable-api-key', 'v3');
await salable.subscriptions.getSeats('your-subscription-id');
const response = await fetch(`https://api.salable.app/subscriptions/your-subscription-uuid/seats`, {
headers: {
"x-api-key": "your-salable-api-key",
version: "v3"
}
})
const seats = await response.json();
curl
-XGET
-H 'x-api-key: your_salable_api_key'
-H 'version: v3'
'https://api.salable.app/subscriptions/your_subscription_id/seats'
Update License (Seat)
Moved to update all subscription endpoint. Fields that apply to both seats and the subscription will both be updated.
For example, if the subscription expiryDate
was updated this would cascade down to the child seats too.
v3 Implementation
- Node SDK
- Fetch
- cURL
import { initSalable } from '@salable/node-sdk';
const salable = initSalable('your-salable-api-key', 'v3');
await salable.subscriptions.update('your-subscription-id', {
expiryDate: new Date()
});
const params = new URLSearchParams();
params.append("owner", "owner-id");
await fetch(`https://api.salable.app/subscriptions/your-subscription-id`, {
method: "PUT",
headers: {
"x-api-key": "your-salable-api-key",
version: "v3"
},
body: JSON.stringify({
expiryDate: new Date()
})
})
curl
-XPUT
-H 'x-api-key: your_salable_api_key'
-H 'version: v3'
-d '{"expiryDate": "2025-09-08T14:55:18.655Z"}'
'https://api.salable.app/subscriptions/your-subscription-id'
The below deprecated license endpoints are only applicable to vendors that have created licenses (seats) WITHOUT a checkout.
Create License (Seat)
Creating a subscription will create a child seat. The granteeId
will be applied to the seat, the owner
field will populate the owner
on the subscription and the purchaser
on the seat.
v3 Implementation
- Node SDK
- Fetch
- cURL
import { initSalable } from '@salable/node-sdk';
const salable = initSalable('your-salable-api-key', 'v3');
await salable.subscriptions.create({
planUuid: 'your-plan-id',
owner: 'owner-id',
granteeId: 'userId_1',
});
await fetch(`https://api.salable.app/subscriptions`, {
method: "POST",
headers: {
"x-api-key": "your-salable-api-key",
version: "v3"
},
body: JSON.stringify({
planUuid: 'your-plan-id',
owner: 'owner-id',
granteeId: 'userId_1',
})
})
curl
-XPOST
-H 'x-api-key: your_salable_api_key'
-H 'version: v3'
-d '{"planUuid": "your-plan-id", "owner": "owner-id", "granteeId": "userId_1"}'
'https://api.salable.app/subscriptions'
Licenses (Seats) By Purchaser
Moved to get all subscriptions with the filter owner
applied. The v3 response uses cursor based pagination.
v3 Implementation
- Node SDK
- Fetch
- cURL
import { initSalable } from '@salable/node-sdk';
const salable = initSalable('your-salable-api-key', 'v3');
await salable.subscriptions.getAll({
owner: 'owner-id'
});
const params = new URLSearchParams();
params.append("owner", "owner-id");
const response = await fetch(`https://api.salable.app/subscriptions?${params}`, {
headers: {
"x-api-key": "your-salable-api-key",
version: "v3"
}
})
const subscriptions = await response.json();
curl
-XGET
-H 'x-api-key: your_salable_api_key'
-H 'version: v3'
'https://api.salable.app/subscriptions?owner=owner-id'
License (Seat) By UUID
Endpoint removed in v3.
Update Many Licenses (Seats)
In v3, the only way to update many seats is to use the subscriptions manage seats endpoint. These updates are limited to
assigning, unassigning or replacing the seat's granteeId
.
v3 Implementation
- Node SDK
- Fetch
- cURL
import { initSalable } from '@salable/node-sdk';
const salable = initSalable('your-salable-api-key', 'v3');
await salable.subscriptions.manageSeats('your-subscription-id', [
{ assign: 'new-grantee-id' },
{ unassign: 'old-grantee-id' },
{
replace: 'grantee-id-to-be-replaced',
newGranteeId: 'replaced-grantee-id'
},
]);
await fetch(`https://api.salable.app/subscriptions/your-subscription-id/manage-seats`, {
method: "PUT",
headers: {
"x-api-key": "your-salable-api-key",
version: "v3"
},
body: JSON.stringify([{
assign: 'new-grantee-id'
}])
})
curl
-XPUT
-H 'x-api-key: your_salable_api_key'
-H 'version: v3'
-d '[{"assign": "new-grantee-id"}]'
'https://api.salable.app/subscriptions/your-subscription-id/manage-seats'
Cancel License (Seat)
You can no longer cancel a seat directly, to cancel a seat, cancel it's parent subscription. If you need to remove a seat on a per-seat subscription, first unassign it, then remove it by updating the seat count on the subscription.
v3 Implementation
- Node SDK
- Fetch
- cURL
import { initSalable } from '@salable/node-sdk';
const salable = initSalable('your-salable-api-key', 'v3');
await salable.subscriptions.cancel('your-subscription-uuid', {
when: 'now'
});
const params = new URLSearchParams();
params.append("when", "now");
await fetch(`https://api.salable.app/subscriptions/your-subscription-uuid/cancel?${params}`, {
method: "PUT",
headers: {
"x-api-key": "your-salable-api-key",
version: "v3"
},
})
curl
-XPUT
-H 'x-api-key: your_salable_api_key'
-H 'version: v3'
'https://api.salable.app/subscriptions/your_subscription_id/cancel?when=now'
Cancel Many Licenses (seats)
When you cancel a subscription, all of it's child seats will also cancel. For example, if you were to cancel a per-seat subscription with five active seats, all five seats would cancel along with the parent subscription. Many subscriptions cannot be cancelled in the same request.
Renamed endpoints
Some endpoints were missing hyphens in their url paths. This has been addressed, the functionality of the endpoints remain unchanged. The method names in v5.0.0 of the Node SDK are also unchanged.
Get product pricing table
v2 - GET /products/your-product-id/pricingtable
v3 - GET /products/your-product-id/pricing-table
Generate checkout link
v2 - GET /plans/your-plan-id/checkoutlink
v3 - GET /plans/your-plan-id/checkout-link
Get update payment link
v2 - 'GET /subscriptions/your-subscription-id/updatepaymentlink
v3 - 'GET /subscriptions/your-subscription-id/update-payment-link
Get cancel payment link
v2 - GET /subscriptions/{uuid}/cancelpaymentlink
v3 - GET /subscriptions/{uuid}/cancel-payment-link
Deprecated fields
Fields that have been removed in v3.
Product
name
appType
Plan
planType
name
trialDays
- useevalDays
evaluation
- this can be determined ifevalDays
is more than 0active
- use the plan'sstatus
environment
paddlePlanId
salablePlan
Seat
capabilities
Pricing Table
customTheme
title
text
theme
Organisation Payment Integration
accountData