One-off Products
A one-off product is a digital product that can be bought once. Products can be created via the API or in the Vatly dashboard, and added to checkouts.
Looking for subscription plans? See the Subscription Plans API instead.
The one-off product model
Below you'll find all properties for the Vatly One-off Product API resource.
Properties
| Name | Type | Description |
|---|---|---|
id | string | Unique identifier for the product (starts with one_off_product_). |
resource | string | The resource type. Always one_off_product. |
testmode | boolean | Whether this product is in test mode. |
name | string | Display name of the product. |
description | string | Detailed description of the product. |
basePrice | Money | Default price of the product (can be overridden in checkout). A Money object with value (decimal string) and currency (ISO 4217 code). Interpretation depends on taxBehavior: when exclusive, basePrice is the net amount and tax is added at checkout; when inclusive, basePrice already includes tax. |
taxBehavior | string | Whether basePrice is tax-exclusive (exclusive, B2B convention; tax added on top at checkout) or tax-inclusive (inclusive, B2C convention; price already includes tax). Immutable after product creation. A checkout may not mix products with different taxBehavior values. |
productType | string | Tax product classification. One of saas (software supplied as a service) or ebook (an electronic publication supplied as a download or stream). |
status | string | Current status of the product. Can be active (product is active and can be purchased), pending (awaiting approval), or rejected (has been rejected). |
archivedAt | string | null | When this product was archived (ISO 8601), or null while it is on sale. Always present. An archived product is hidden from GET /v1/one-off-products and refused by POST /v1/checkouts. |
pendingUpdates | object | null | The changes that will take effect once a submitted update is approved, or null when there is no pending update. Only the fields that differ from the live product are present. |
updateStatus | string | null | Lifecycle of a pending update, or null when there is none. Can be pending (an update was submitted and is awaiting review) or reviewing (the update is being reviewed). |
createdAt | string | When this product was created (ISO 8601 format). |
links | object | HATEOAS links to related resources. Contains self link. |
List all one-off products
GET /v1/one-off-products
This endpoint retrieves a paginated list of all one-off products.
Only products with active status can be used in checkouts. Archived products are excluded unless includeArchived=true is passed.
Optional parameters
| Name | Type | Description |
|---|---|---|
limit | integer | The number of products to return (default: 10, max: 100). |
startingAfter | string | A cursor for use in pagination. Returns results after this product ID. |
endingBefore | string | A cursor for use in pagination. Returns results before this product ID. |
includeArchived | boolean | Include archived products in the listing (default: false). Tell them apart by the non-null archivedAt. |
curl -G https://api.vatly.com/v1/one-off-products \
-H "Authorization: Bearer live_your_api_key_here" \
-d limit=10
$vatly = new \Vatly\API\VatlyApiClient();
$vatly->setApiKey('live_your_api_key_here');
$products = $vatly->oneOffProducts->page();
{
"data": [
{
"id": "one_off_product_Vr8kQdFhSrG4Y3DnfsdqH",
"resource": "one_off_product",
"testmode": false,
"name": "Premium License",
"description": "Lifetime access to all premium features",
"basePrice": {
"value": "299.00",
"currency": "EUR"
},
"taxBehavior": "exclusive",
"productType": "saas",
"status": "active",
"archivedAt": null,
"createdAt": "2024-01-15T10:30:00Z",
"links": {
"self": {
"href": "https://api.vatly.com/v1/one-off-products/one_off_product_Vr8kQdFhSrG4Y3DnfsdqH",
"type": "application/json"
}
}
}
],
"count": 1,
"links": {
"self": {
"href": "https://api.vatly.com/v1/one-off-products",
"type": "application/json"
},
"next": null,
"prev": null
}
}
Create a one-off product
POST /v1/one-off-products
Creates a new one-off product for the authenticated merchant, in the testmode determined from the API token.
A product created with a live_ token starts in pending status and must be approved by Vatly before it can be used in checkouts — the same review that applies to products created in the dashboard. A product created with a test_ token is auto-approved (active) so you can trial checkout immediately.
Required attributes
| Name | Type | Description |
|---|---|---|
name | string | Display name of the product (3–255 characters). |
description | string | Detailed description of the product. |
basePrice | Money | Price of the product. A Money object with value (decimal string) and currency (ISO 4217 code). |
productType | string | Tax product classification. One of saas (Software as a Service) or ebook (electronic book). |
Optional attributes
| Name | Type | Description |
|---|---|---|
taxBehavior | string | Whether basePrice is tax-exclusive (exclusive, B2B convention) or tax-inclusive (inclusive, B2C convention). Defaults to exclusive. Immutable after product creation. |
curl https://api.vatly.com/v1/one-off-products \
-H "Authorization: Bearer live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Premium License",
"description": "Lifetime access to all premium features",
"basePrice": { "value": "299.00", "currency": "EUR" },
"productType": "saas"
}'
$vatly = new \Vatly\API\VatlyApiClient();
$vatly->setApiKey('live_your_api_key_here');
$product = $vatly->oneOffProducts->create([
'name' => 'Premium License',
'description' => 'Lifetime access to all premium features',
'basePrice' => ['value' => '299.00', 'currency' => 'EUR'],
'productType' => 'saas',
]);
{
"id": "one_off_product_Vr8kQdFhSrG4Y3DnfsdqH",
"resource": "one_off_product",
"testmode": false,
"name": "Premium License",
"description": "Lifetime access to all premium features",
"basePrice": {
"value": "299.00",
"currency": "EUR"
},
"taxBehavior": "exclusive",
"productType": "saas",
"status": "pending",
"archivedAt": null,
"createdAt": "2024-01-15T10:30:00Z",
"links": {
"self": {
"href": "https://api.vatly.com/v1/one-off-products/one_off_product_Vr8kQdFhSrG4Y3DnfsdqH",
"type": "application/json"
}
}
}
Get a specific one-off product
GET /v1/one-off-products/:oneOffProductId
This endpoint retrieves a specific one-off product by its ID.
Parameters
| Name | Type | Description |
|---|---|---|
oneOffProductId | string | The ID of the one-off product to retrieve. |
curl https://api.vatly.com/v1/one-off-products/one_off_product_Vr8kQdFhSrG4Y3DnfsdqH \
-H "Authorization: Bearer live_your_api_key_here"
$vatly = new \Vatly\API\VatlyApiClient();
$vatly->setApiKey('live_your_api_key_here');
$product = $vatly->oneOffProducts->get('one_off_product_Vr8kQdFhSrG4Y3DnfsdqH');
{
"id": "one_off_product_Vr8kQdFhSrG4Y3DnfsdqH",
"resource": "one_off_product",
"testmode": false,
"name": "Premium License",
"description": "Lifetime access to all premium features",
"basePrice": {
"value": "299.00",
"currency": "EUR"
},
"taxBehavior": "exclusive",
"productType": "saas",
"status": "active",
"archivedAt": null,
"createdAt": "2024-01-15T10:30:00Z",
"links": {
"self": {
"href": "https://api.vatly.com/v1/one-off-products/one_off_product_Vr8kQdFhSrG4Y3DnfsdqH",
"type": "application/json"
}
}
}
Request an update to a one-off product
PATCH /v1/one-off-products/:oneOffProductId
Submits an update to a live one-off product. Because products drive VAT-bearing sales, the change is held as a pending update and reviewed by Vatly before it takes effect (updateStatus moves pending → reviewing → applied). In test mode the update is approved automatically.
Each request is the complete set of changes relative to the current live product. Fields equal to the live value are ignored, and a request that nets to no change clears any pending update. A new request replaces the not-yet-reviewed one; while an update is being reviewed, further requests return 409.
Parameters
| Name | Type | Description |
|---|---|---|
oneOffProductId | string | The ID of the one-off product to update. |
Optional attributes
At least one attribute must be provided. taxBehavior is immutable and cannot be changed here.
| Name | Type | Description |
|---|---|---|
name | string | New display name of the product (3–255 characters). |
description | string | New description of the product. |
basePrice | Money | New price of the product. A Money object with value (decimal string) and currency (ISO 4217 code). |
productType | string | New tax product classification. One of saas or ebook. |
curl -X PATCH https://api.vatly.com/v1/one-off-products/one_off_product_Vr8kQdFhSrG4Y3DnfsdqH \
-H "Authorization: Bearer live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Premium License v2",
"basePrice": { "value": "349.00", "currency": "EUR" }
}'
$vatly = new \Vatly\API\VatlyApiClient();
$vatly->setApiKey('live_your_api_key_here');
$product = $vatly->oneOffProducts->update('one_off_product_Vr8kQdFhSrG4Y3DnfsdqH', [
'name' => 'Premium License v2',
'basePrice' => ['value' => '349.00', 'currency' => 'EUR'],
]);
{
"id": "one_off_product_Vr8kQdFhSrG4Y3DnfsdqH",
"resource": "one_off_product",
"testmode": false,
"name": "Premium License",
"description": "Lifetime access to all premium features",
"basePrice": {
"value": "299.00",
"currency": "EUR"
},
"taxBehavior": "exclusive",
"productType": "saas",
"status": "active",
"archivedAt": null,
"pendingUpdates": {
"name": "Premium License v2",
"basePrice": {
"value": "349.00",
"currency": "EUR"
}
},
"updateStatus": "pending",
"createdAt": "2024-01-15T10:30:00Z",
"links": {
"self": {
"href": "https://api.vatly.com/v1/one-off-products/one_off_product_Vr8kQdFhSrG4Y3DnfsdqH",
"type": "application/json"
}
}
}
Archive a one-off product
POST /v1/one-off-products/:oneOffProductId/archive
Archives the product so it can no longer be sold. It is hidden from GET /v1/one-off-products (unless includeArchived=true) and refused by POST /v1/checkouts; existing orders and refunds are untouched.
Archiving applies to new checkouts only. A checkout created before the product was archived snapshots its product data at creation time and can still be completed — the same window that applies when a product is rejected after a checkout was opened.
Nothing is deleted — the product remains readable by ID, now carrying a non-null archivedAt. Archiving is not queued behind Vatly's product review, and leaves any pending update in place. Repeating the request is a no-op that returns 204 and does not move archivedAt. Reverse it with DELETE /v1/one-off-products/:oneOffProductId/archive.
Parameters
| Name | Type | Description |
|---|---|---|
oneOffProductId | string | The ID of the one-off product to archive. |
curl -X POST https://api.vatly.com/v1/one-off-products/one_off_product_Vr8kQdFhSrG4Y3DnfsdqH/archive \
-H "Authorization: Bearer live_your_api_key_here"
$vatly = new \Vatly\API\VatlyApiClient();
$vatly->setApiKey('live_your_api_key_here');
$vatly->oneOffProducts->archive('one_off_product_Vr8kQdFhSrG4Y3DnfsdqH');
Returns 204 No Content on success (or when the product is already archived).
Unarchive a one-off product
DELETE /v1/one-off-products/:oneOffProductId/archive
Puts an archived product back on sale: it reappears in GET /v1/one-off-products and can be added to checkouts again. Calling it on a product that is not archived is a no-op.
Parameters
| Name | Type | Description |
|---|---|---|
oneOffProductId | string | The ID of the one-off product to unarchive. |
curl -X DELETE https://api.vatly.com/v1/one-off-products/one_off_product_Vr8kQdFhSrG4Y3DnfsdqH/archive \
-H "Authorization: Bearer live_your_api_key_here"
$vatly = new \Vatly\API\VatlyApiClient();
$vatly->setApiKey('live_your_api_key_here');
$product = $vatly->oneOffProducts->unarchive('one_off_product_Vr8kQdFhSrG4Y3DnfsdqH');
{
"id": "one_off_product_Vr8kQdFhSrG4Y3DnfsdqH",
"resource": "one_off_product",
"testmode": false,
"name": "Premium License",
"description": "Lifetime access to all premium features",
"basePrice": {
"value": "299.00",
"currency": "EUR"
},
"taxBehavior": "exclusive",
"productType": "saas",
"status": "active",
"archivedAt": null,
"createdAt": "2024-01-15T10:30:00Z",
"links": {
"self": {
"href": "https://api.vatly.com/v1/one-off-products/one_off_product_Vr8kQdFhSrG4Y3DnfsdqH",
"type": "application/json"
}
}
}