# Sales Pages

A sales page is a public page (`url`) where clients buy a single
product: one-time when `interval` is null, or a subscription billed
every `interval`. Each purchase creates an invoice (and, for
subscriptions, a subscription) for the buyer's client.

Statuses: `draft` (only visible to the account), `published` (anyone
with the `url` can buy). Status can't be set through update. Use
`/publish` and `/unpublish` instead.

To deliver a digital product, link to a file hosted elsewhere with
`file: { url }`. Images are managed in the app.

Deleted sales pages can't be bought and are hidden until you restore
them.

## The sales page object

- `id` (string): Example: `0abcDEF1234567890xyzAB`.
- `status` (string): One of `draft`, `published`.
- `title` (string)
- `description` (string or null)
- `url` (string): Public page where clients buy.
- `image_url` (string or null)
- `file` (object or null): Digital product the buyer can download after paying.
- `file.type` (string): One of `upload`, `url`.
- `file.url` (string): The linked URL for `url` files. For uploads, a temporary download link valid for 5 minutes, until `expires_at`. Fetch the sales page again for a new one.
- `file.expires_at` (timestamp or null): When `url` stops working. `null` for `url` files. Example: `2024-06-15T12:00:00.000000Z`.
- `currency` (string): Three-letter currency code, in lowercase. Example: `usd`.
- `language` (string): Two-letter language code, in lowercase. One of `da`, `en`, `fr`, `de`, `it`, `no`, `pt`, `es`.
- `interval` (string or null): Billing interval for subscriptions. `null` means a one-time purchase. One of `weekly`, `biweekly`, `four_weeks`, `monthly`, `eight_weeks`, `twelve_weeks`, `quarterly`, `biannual`, `yearly`.
- `amount` (integer): Price before discounts and taxes. Example: `10000`. Min 0.
- `discounts` (array of object)
- `discounts[].amount` (integer or null): Fixed amount off, in cents. Example: `10000`. Min 0.
- `discounts[].percent` (string or null): Percent off the subtotal, as a decimal string without trailing zeros. Example: `12.5`.
- `discount_amount` (integer): Amount in the smallest currency unit (cents). Example: `10000`. Min 0.
- `taxable_amount` (integer): Amount in the smallest currency unit (cents). Example: `10000`. Min 0.
- `tax_rates` (array of object)
- `tax_rates[].id` (string): The tax rate's ID. Use it in `tax_rates`. Example: `0abcDEF1234567890xyzAB`.
- `tax_rates[].name` (string): Example: `TPS`.
- `tax_rates[].percentage` (string): Percentage as a decimal string, without trailing zeros. Example: `5`.
- `tax_rates[].amount` (integer): Tax amount for this rate. Example: `10000`. Min 0.
- `tax_amount` (integer): Amount in the smallest currency unit (cents). Example: `10000`. Min 0.
- `total` (integer): What the buyer pays (per interval for subscriptions). Example: `10000`. Min 0.
- `accept_cards` (boolean): Whether buyers can pay by card online.
- `custom_field` (object or null): Extra question asked to the buyer at checkout.
- `custom_field.label` (string)
- `custom_field.description` (string or null)
- `custom_field.required` (boolean)
- `created_at` (timestamp): Date and time in UTC. Example: `2024-06-15T12:00:00.000000Z`.
- `updated_at` (timestamp): Date and time in UTC. Example: `2024-06-15T12:00:00.000000Z`.
- `is_deleted` (boolean)

```json
{
  "id": "0abcDEF1234567890xyzAB",
  "status": "published",
  "title": "Photography course",
  "description": "Ten lessons to master your camera.",
  "url": "https://pay.tallymark.com/p/0abcDEF1234567890xyzAB",
  "image_url": null,
  "file": {
    "type": "url",
    "url": "https://example.com/course.pdf",
    "expires_at": null
  },
  "currency": "usd",
  "language": "en",
  "interval": null,
  "amount": 10000,
  "discounts": [],
  "discount_amount": 0,
  "taxable_amount": 10000,
  "tax_rates": [],
  "tax_amount": 0,
  "total": 10000,
  "accept_cards": true,
  "custom_field": null,
  "created_at": "2024-06-15T12:00:00.000000Z",
  "updated_at": "2024-06-15T12:00:00.000000Z",
  "is_deleted": false
}
```

## List sales pages

`GET /sales-pages`

### Query parameters

- `search` (string): Matches title and description. Not case-sensitive. Example: `photography`.
- `status` (string): One of `draft`, `published`.
- `limit` (integer): Number of results per page. Default: `10`. Min 1. Max 100.
- `cursor` (string): To get the next page, pass the `next_cursor` from the previous response.
- `created_at` (object): Keys `gt`, `gte`, `lt`, `lte`. Example: `created_at[gt]=2024-06-15T12:00:00.000000Z`.
- `updated_at` (object): Keys `gt`, `gte`, `lt`, `lte`. Example: `updated_at[gt]=2024-06-15T12:00:00.000000Z`.

### Response

- `200`: Paginated sales pages. Returns a [paginated list](/docs/pagination) of [sales page objects](#the-sales-page-object).

### Errors

- `400` `request_failed`: Your request could not be processed.
- `400` `api_version_unsupported`: The requested API version is not supported. Current version: 2026-08-21
- `401` `api_key_missing`: The API key is missing.
- `401` `api_key_invalid`: The API key is invalid or has been revoked.
- `422`: The request body or query parameters are invalid.
- `429`: Too many requests. The limit is 300 requests per 5 minutes.

### Example request

```bash
curl https://api.tallymark.com/sales-pages \
  -H "Authorization: Bearer $TALLYMARK_API_KEY"
```

### Example response

```json
{
  "data": [
    {
      "id": "0abcDEF1234567890xyzAB",
      "status": "published",
      "title": "Photography course",
      "description": "Ten lessons to master your camera.",
      "url": "https://pay.tallymark.com/p/0abcDEF1234567890xyzAB",
      "image_url": null,
      "file": {
        "type": "url",
        "url": "https://example.com/course.pdf",
        "expires_at": null
      },
      "currency": "usd",
      "language": "en",
      "interval": null,
      "amount": 10000,
      "discounts": [],
      "discount_amount": 0,
      "taxable_amount": 10000,
      "tax_rates": [],
      "tax_amount": 0,
      "total": 10000,
      "accept_cards": true,
      "custom_field": null,
      "created_at": "2024-06-15T12:00:00.000000Z",
      "updated_at": "2024-06-15T12:00:00.000000Z",
      "is_deleted": false
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

## Create a sales page

`POST /sales-pages`

Creates a `draft` sales page, or a `published` one with
`publish: true`.

### Request body

Send at most one discount.

- `publish` (boolean): Create the sales page `published` instead of `draft`. Default: `false`.
- `title` (string, required): Also used as the item description on invoices. Max length 255.
- `description` (string or null)
- `amount` (integer, required): Price before discounts and taxes. Example: `10000`. Min 0.
- `discounts` (array of object): Max 1 items.
- `discounts[].amount` (integer or null): Fixed amount off, in cents. Example: `10000`. Min 0.
- `discounts[].percent` (number or null): Percent off the subtotal, up to 4 decimals. Example: `12.5`. Min 0. Max 100.
- `interval` (string or null): Billing interval for subscriptions. `null` for a one-time purchase. On create, omitting it also makes a one-time purchase; on update, omitting it keeps the current interval. One of `weekly`, `biweekly`, `four_weeks`, `monthly`, `eight_weeks`, `twelve_weeks`, `quarterly`, `biannual`, `yearly`.
- `currency` (string, required): Three-letter currency code, in lowercase. Example: `usd`.
- `language` (string, required): Two-letter language code, in lowercase. One of `da`, `en`, `fr`, `de`, `it`, `no`, `pt`, `es`.
- `tax_rates` (array of string): Tax rate IDs belonging to the account. Deleted tax rates cannot be added, but ones already on the sales page can be kept.
- `include_default_tax_rates` (boolean): Adds the account's default tax rates on top of `tax_rates`. On update, if `tax_rates` is omitted, the defaults are added to the tax rates already on the sales page. A tax rate is never applied twice. Default: `false`.
- `accept_cards` (boolean, required)
- `custom_field` (object or null): Extra question asked to the buyer at checkout. On update, replaces the current custom field as a whole, and `null` removes it.
- `custom_field.label` (string, required): Max length 255.
- `custom_field.description` (string or null)
- `custom_field.required` (boolean): Whether the buyer must answer. Default: `false`.
- `file` (object or null): Digital product hosted elsewhere, given to the buyer after paying. Replaces the current file, including one uploaded in the app. On update, `null` removes the current file.
- `file.url` (string, required): Max length 255.

### Response

- `201`: Created sales page. Returns a [sales page object](#the-sales-page-object).

### Errors

- `400` `request_failed`: Your request could not be processed.
- `400` `api_version_unsupported`: The requested API version is not supported. Current version: 2026-08-21
- `401` `api_key_missing`: The API key is missing.
- `401` `api_key_invalid`: The API key is invalid or has been revoked.
- `403` `api_key_read_only`: This API key is read-only and cannot make write requests.
- `404`: The record doesn't exist or was deleted.
- `422`: The request body or query parameters are invalid.
- `429`: Too many requests. The limit is 300 requests per 5 minutes.

### Example request

```bash
curl -X POST https://api.tallymark.com/sales-pages \
  -H "Authorization: Bearer $TALLYMARK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "title": "Photography course",
  "description": "Ten lessons to master your camera.",
  "amount": 10000,
  "interval": null,
  "currency": "usd",
  "language": "en",
  "accept_cards": true,
  "file": {
    "url": "https://example.com/course.pdf"
  },
  "publish": true
}'
```

### Example response

```json
{
  "id": "0abcDEF1234567890xyzAB",
  "status": "published",
  "title": "Photography course",
  "description": "Ten lessons to master your camera.",
  "url": "https://pay.tallymark.com/p/0abcDEF1234567890xyzAB",
  "image_url": null,
  "file": {
    "type": "url",
    "url": "https://example.com/course.pdf",
    "expires_at": null
  },
  "currency": "usd",
  "language": "en",
  "interval": null,
  "amount": 10000,
  "discounts": [],
  "discount_amount": 0,
  "taxable_amount": 10000,
  "tax_rates": [],
  "tax_amount": 0,
  "total": 10000,
  "accept_cards": true,
  "custom_field": null,
  "created_at": "2024-06-15T12:00:00.000000Z",
  "updated_at": "2024-06-15T12:00:00.000000Z",
  "is_deleted": false
}
```

## Show a sales page

`GET /sales-pages/{id}`

### Path parameters

- `id` (string, required): The sales page ID. Example: `0abcDEF1234567890xyzAB`.

### Response

- `200`: Sales page. Returns a [sales page object](#the-sales-page-object).

### Errors

- `400` `request_failed`: Your request could not be processed.
- `400` `api_version_unsupported`: The requested API version is not supported. Current version: 2026-08-21
- `401` `api_key_missing`: The API key is missing.
- `401` `api_key_invalid`: The API key is invalid or has been revoked.
- `404`: The record doesn't exist or was deleted.
- `429`: Too many requests. The limit is 300 requests per 5 minutes.

### Example request

```bash
curl https://api.tallymark.com/sales-pages/0abcDEF1234567890xyzAB \
  -H "Authorization: Bearer $TALLYMARK_API_KEY"
```

### Example response

```json
{
  "id": "0abcDEF1234567890xyzAB",
  "status": "published",
  "title": "Photography course",
  "description": "Ten lessons to master your camera.",
  "url": "https://pay.tallymark.com/p/0abcDEF1234567890xyzAB",
  "image_url": null,
  "file": {
    "type": "url",
    "url": "https://example.com/course.pdf",
    "expires_at": null
  },
  "currency": "usd",
  "language": "en",
  "interval": null,
  "amount": 10000,
  "discounts": [],
  "discount_amount": 0,
  "taxable_amount": 10000,
  "tax_rates": [],
  "tax_amount": 0,
  "total": 10000,
  "accept_cards": true,
  "custom_field": null,
  "created_at": "2024-06-15T12:00:00.000000Z",
  "updated_at": "2024-06-15T12:00:00.000000Z",
  "is_deleted": false
}
```

## Update a sales page

`POST /sales-pages/{id}/update`

Updates a sales page in any status. Only the fields you send are
changed; omitted fields keep their current value. Send `null` to
clear `description`, `interval` (makes it one-time), `custom_field`,
and `file`; send `discounts: []` or `tax_rates: []` to remove them.
Changes apply to purchases made from now on. To change the status, use
`/publish` and `/unpublish`.

### Path parameters

- `id` (string, required): The sales page ID. Example: `0abcDEF1234567890xyzAB`.

### Request body

Send at most one discount.

- `title` (string): Also used as the item description on invoices. Max length 255.
- `description` (string or null)
- `amount` (integer): Price before discounts and taxes. Example: `10000`. Min 0.
- `discounts` (array of object): Max 1 items.
- `discounts[].amount` (integer or null): Fixed amount off, in cents. Example: `10000`. Min 0.
- `discounts[].percent` (number or null): Percent off the subtotal, up to 4 decimals. Example: `12.5`. Min 0. Max 100.
- `interval` (string or null): Billing interval for subscriptions. `null` for a one-time purchase. On create, omitting it also makes a one-time purchase; on update, omitting it keeps the current interval. One of `weekly`, `biweekly`, `four_weeks`, `monthly`, `eight_weeks`, `twelve_weeks`, `quarterly`, `biannual`, `yearly`.
- `currency` (string): Three-letter currency code, in lowercase. Example: `usd`.
- `language` (string): Two-letter language code, in lowercase. One of `da`, `en`, `fr`, `de`, `it`, `no`, `pt`, `es`.
- `tax_rates` (array of string): Tax rate IDs belonging to the account. Deleted tax rates cannot be added, but ones already on the sales page can be kept.
- `include_default_tax_rates` (boolean): Adds the account's default tax rates on top of `tax_rates`. On update, if `tax_rates` is omitted, the defaults are added to the tax rates already on the sales page. A tax rate is never applied twice. Default: `false`.
- `accept_cards` (boolean)
- `custom_field` (object or null): Extra question asked to the buyer at checkout. On update, replaces the current custom field as a whole, and `null` removes it.
- `custom_field.label` (string, required): Max length 255.
- `custom_field.description` (string or null)
- `custom_field.required` (boolean): Whether the buyer must answer. Default: `false`.
- `file` (object or null): Digital product hosted elsewhere, given to the buyer after paying. Replaces the current file, including one uploaded in the app. On update, `null` removes the current file.
- `file.url` (string, required): Max length 255.

### Response

- `200`: Updated sales page. Returns a [sales page object](#the-sales-page-object).

### Errors

- `400` `request_failed`: Your request could not be processed.
- `400` `api_version_unsupported`: The requested API version is not supported. Current version: 2026-08-21
- `401` `api_key_missing`: The API key is missing.
- `401` `api_key_invalid`: The API key is invalid or has been revoked.
- `403` `api_key_read_only`: This API key is read-only and cannot make write requests.
- `404`: The record doesn't exist or was deleted.
- `422`: The request body or query parameters are invalid.
- `429`: Too many requests. The limit is 300 requests per 5 minutes.

### Example request

```bash
curl -X POST https://api.tallymark.com/sales-pages/0abcDEF1234567890xyzAB/update \
  -H "Authorization: Bearer $TALLYMARK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "title": "Photography masterclass",
  "amount": 15000
}'
```

### Example response

```json
{
  "id": "0abcDEF1234567890xyzAB",
  "status": "published",
  "title": "Photography course",
  "description": "Ten lessons to master your camera.",
  "url": "https://pay.tallymark.com/p/0abcDEF1234567890xyzAB",
  "image_url": null,
  "file": {
    "type": "url",
    "url": "https://example.com/course.pdf",
    "expires_at": null
  },
  "currency": "usd",
  "language": "en",
  "interval": null,
  "amount": 10000,
  "discounts": [],
  "discount_amount": 0,
  "taxable_amount": 10000,
  "tax_rates": [],
  "tax_amount": 0,
  "total": 10000,
  "accept_cards": true,
  "custom_field": null,
  "created_at": "2024-06-15T12:00:00.000000Z",
  "updated_at": "2024-06-15T12:00:00.000000Z",
  "is_deleted": false
}
```

## Publish a sales page

`POST /sales-pages/{id}/publish`

Makes the sales page available to buy at its `url`.

### Path parameters

- `id` (string, required): The sales page ID. Example: `0abcDEF1234567890xyzAB`.

### Response

- `200`: Published sales page. Returns a [sales page object](#the-sales-page-object).

### Errors

- `400` `request_failed`: Your request could not be processed.
- `400` `api_version_unsupported`: The requested API version is not supported. Current version: 2026-08-21
- `401` `api_key_missing`: The API key is missing.
- `401` `api_key_invalid`: The API key is invalid or has been revoked.
- `403` `api_key_read_only`: This API key is read-only and cannot make write requests.
- `404`: The record doesn't exist or was deleted.
- `429`: Too many requests. The limit is 300 requests per 5 minutes.

### Example request

```bash
curl -X POST https://api.tallymark.com/sales-pages/0abcDEF1234567890xyzAB/publish \
  -H "Authorization: Bearer $TALLYMARK_API_KEY"
```

### Example response

```json
{
  "id": "0abcDEF1234567890xyzAB",
  "status": "published",
  "title": "Photography course",
  "description": "Ten lessons to master your camera.",
  "url": "https://pay.tallymark.com/p/0abcDEF1234567890xyzAB",
  "image_url": null,
  "file": {
    "type": "url",
    "url": "https://example.com/course.pdf",
    "expires_at": null
  },
  "currency": "usd",
  "language": "en",
  "interval": null,
  "amount": 10000,
  "discounts": [],
  "discount_amount": 0,
  "taxable_amount": 10000,
  "tax_rates": [],
  "tax_amount": 0,
  "total": 10000,
  "accept_cards": true,
  "custom_field": null,
  "created_at": "2024-06-15T12:00:00.000000Z",
  "updated_at": "2024-06-15T12:00:00.000000Z",
  "is_deleted": false
}
```

## Unpublish a sales page

`POST /sales-pages/{id}/unpublish`

Moves the sales page back to `draft`: it can no longer be bought.
Existing subscriptions created from it keep running.

### Path parameters

- `id` (string, required): The sales page ID. Example: `0abcDEF1234567890xyzAB`.

### Response

- `200`: Draft sales page. Returns a [sales page object](#the-sales-page-object).

### Errors

- `400` `request_failed`: Your request could not be processed.
- `400` `api_version_unsupported`: The requested API version is not supported. Current version: 2026-08-21
- `401` `api_key_missing`: The API key is missing.
- `401` `api_key_invalid`: The API key is invalid or has been revoked.
- `403` `api_key_read_only`: This API key is read-only and cannot make write requests.
- `404`: The record doesn't exist or was deleted.
- `429`: Too many requests. The limit is 300 requests per 5 minutes.

### Example request

```bash
curl -X POST https://api.tallymark.com/sales-pages/0abcDEF1234567890xyzAB/unpublish \
  -H "Authorization: Bearer $TALLYMARK_API_KEY"
```

### Example response

```json
{
  "id": "0abcDEF1234567890xyzAB",
  "status": "published",
  "title": "Photography course",
  "description": "Ten lessons to master your camera.",
  "url": "https://pay.tallymark.com/p/0abcDEF1234567890xyzAB",
  "image_url": null,
  "file": {
    "type": "url",
    "url": "https://example.com/course.pdf",
    "expires_at": null
  },
  "currency": "usd",
  "language": "en",
  "interval": null,
  "amount": 10000,
  "discounts": [],
  "discount_amount": 0,
  "taxable_amount": 10000,
  "tax_rates": [],
  "tax_amount": 0,
  "total": 10000,
  "accept_cards": true,
  "custom_field": null,
  "created_at": "2024-06-15T12:00:00.000000Z",
  "updated_at": "2024-06-15T12:00:00.000000Z",
  "is_deleted": false
}
```

## Delete a sales page

`POST /sales-pages/{id}/delete`

Deletes a sales page: it can no longer be bought. Invoices and
subscriptions created from it are not affected. You can restore it
later, with its status.

### Path parameters

- `id` (string, required): The sales page ID. Example: `0abcDEF1234567890xyzAB`.

### Response

- `200`: Deleted sales page (`is_deleted` true). Returns a [sales page object](#the-sales-page-object).

### Errors

- `400` `request_failed`: Your request could not be processed.
- `400` `api_version_unsupported`: The requested API version is not supported. Current version: 2026-08-21
- `401` `api_key_missing`: The API key is missing.
- `401` `api_key_invalid`: The API key is invalid or has been revoked.
- `403` `api_key_read_only`: This API key is read-only and cannot make write requests.
- `404`: The record doesn't exist or was deleted.
- `429`: Too many requests. The limit is 300 requests per 5 minutes.

### Example request

```bash
curl -X POST https://api.tallymark.com/sales-pages/0abcDEF1234567890xyzAB/delete \
  -H "Authorization: Bearer $TALLYMARK_API_KEY"
```

### Example response

```json
{
  "id": "0abcDEF1234567890xyzAB",
  "status": "published",
  "title": "Photography course",
  "description": "Ten lessons to master your camera.",
  "url": "https://pay.tallymark.com/p/0abcDEF1234567890xyzAB",
  "image_url": null,
  "file": {
    "type": "url",
    "url": "https://example.com/course.pdf",
    "expires_at": null
  },
  "currency": "usd",
  "language": "en",
  "interval": null,
  "amount": 10000,
  "discounts": [],
  "discount_amount": 0,
  "taxable_amount": 10000,
  "tax_rates": [],
  "tax_amount": 0,
  "total": 10000,
  "accept_cards": true,
  "custom_field": null,
  "created_at": "2024-06-15T12:00:00.000000Z",
  "updated_at": "2024-06-15T12:00:00.000000Z",
  "is_deleted": false
}
```

## Restore a deleted sales page

`POST /sales-pages/{id}/restore`

Restores a deleted sales page with its status: a deleted `published`
sales page can be bought again right away.

### Path parameters

- `id` (string, required): The sales page ID. Example: `0abcDEF1234567890xyzAB`.

### Response

- `200`: Restored sales page (`is_deleted` false). Returns a [sales page object](#the-sales-page-object).

### Errors

- `400` `request_failed`: Your request could not be processed.
- `400` `api_version_unsupported`: The requested API version is not supported. Current version: 2026-08-21
- `401` `api_key_missing`: The API key is missing.
- `401` `api_key_invalid`: The API key is invalid or has been revoked.
- `403` `api_key_read_only`: This API key is read-only and cannot make write requests.
- `404`: The record doesn't exist or was deleted.
- `429`: Too many requests. The limit is 300 requests per 5 minutes.

### Example request

```bash
curl -X POST https://api.tallymark.com/sales-pages/0abcDEF1234567890xyzAB/restore \
  -H "Authorization: Bearer $TALLYMARK_API_KEY"
```

### Example response

```json
{
  "id": "0abcDEF1234567890xyzAB",
  "status": "published",
  "title": "Photography course",
  "description": "Ten lessons to master your camera.",
  "url": "https://pay.tallymark.com/p/0abcDEF1234567890xyzAB",
  "image_url": null,
  "file": {
    "type": "url",
    "url": "https://example.com/course.pdf",
    "expires_at": null
  },
  "currency": "usd",
  "language": "en",
  "interval": null,
  "amount": 10000,
  "discounts": [],
  "discount_amount": 0,
  "taxable_amount": 10000,
  "tax_rates": [],
  "tax_amount": 0,
  "total": 10000,
  "accept_cards": true,
  "custom_field": null,
  "created_at": "2024-06-15T12:00:00.000000Z",
  "updated_at": "2024-06-15T12:00:00.000000Z",
  "is_deleted": false
}
```
