Subscription Contracts V2
Subscription Contracts V2 is Askell’s new subscription model. It is separate from the older PlanVariant / Subscription flow and is instead built around a catalog, prices, bundles, quotes, checkout flows, and billing runs.
All calls to V2 API endpoints require a secret API key:
Authorization: Api-Key your-secret-api-key
Older token-based authentication is still supported in some V2 calls for compatibility with older integrations. New integrations should use secret API keys.
Quick overview of a typical flow
A common integration flow is this:
Fetch products, prices, or bundles from the catalog.
Calculate a quote with
POST /api/v2/subscription-offer-quotes/.Fetch eligible payment processors with
POST /api/v2/payment-processor-options/.Create a
checkoutwithPOST /api/v2/checkouts/.Finalize the
checkoutwithPOST /api/v2/checkouts/{token}/finalize/.Monitor the status of the
checkoutand, when applicable, the initial billing run.
If the integration does not need payment pre-processing, steps 3-5 can be skipped and the contract can be created directly with POST /api/v2/subscription-contracts/.
See also
For the legacy subscription flow based on PlanVariant and Subscription, see Subscriptions.
Warning
checkout_url in V2 is not a public hosted payment page. It points to the API URL for the checkout object itself and is therefore suitable for system-to-system integrations, not for directly redirecting a user in a browser.
When to use V2
V2 should be used when selling products and subscriptions from a catalog instead of older plans and PlanVariant identifiers. This includes:
When selling individual products or a combination of products as a subscription contract.
When using bundles with selectable prices and quantities.
When price changes need to be scheduled with price versions.
When recurring billing runs need clearer history and retry support.
Legacy payment pages and the older /api/subscriptions/ flow continue to use the legacy Subscription model and are documented separately under Subscriptions.
Core objects
The main V2 objects are these:
Object |
Description |
|---|---|
|
A product in the catalog. |
|
A stable price identifier that a contract is linked to. |
|
A time-bounded price version that defines an amount for a period. |
|
A sellable bundle that combines one or more products. |
|
The new persistent subscription contract. |
|
A single billing run on a contract. |
|
A single attempt to collect a billing run. |
|
An intermediate object that stores the quote and payment prerequisites before the contract is created. |
Before you start
Before a payment flow can be completed or a contract can be created, the following must already exist:
The customer must already exist in Askell.
A payment processor (
AccountPaymentProcessor) must be configured for the relevant currency and payment method.If the
checkout/finalizeflow is used, the customer must already have a verified payment method that matches the selected payment processor.
See also Prerequisites before calling finalize, which explains in more detail what is validated immediately before finalize creates the contract and billing.
Catalog lookup
Use the following endpoints to fetch products and prices from the catalog:
curl https://askell.is/api/v2/catalog/products/ \
-H "Authorization: Api-Key your-secret-api-key"
curl "https://askell.is/api/v2/catalog/prices/?billing_type=recurring¤cy=ISK" \
-H "Authorization: Api-Key your-secret-api-key"
Common catalog filters:
Filter |
Description |
|---|---|
|
By default only active records are shown. |
|
Product or bundle reference, depending on the endpoint. |
|
Product ID or product reference on the price endpoint. |
|
Price currency. |
|
|
|
Recurrence type for recurring prices. |
Price versions
In V2, a contract is linked to a stable CatalogPrice identifier, while the amount itself can change over time through CatalogPriceVersion. This makes it possible to schedule price changes in advance without moving the customer to a new price identifier.
Responses from catalog price endpoints include, among other things, the following fields:
Field |
Description |
|---|---|
|
The current display amount for the price. |
|
The ID of the price version currently considered active. |
|
Past, current, and future price versions. |
|
The start of the validity period for the currently displayed version. |
|
The end of the validity period for the currently displayed version. |
Integrations that only need the current amount can generally read unit_amount. Integrations that want to display or schedule price changes should use versions.
Bundles and price selection
Bundles are fetched from dedicated endpoints:
curl https://askell.is/api/v2/bundle-templates/ \
-H "Authorization: Api-Key your-secret-api-key"
curl https://askell.is/api/v2/bundle-templates/42/ \
-H "Authorization: Api-Key your-secret-api-key"
A bundle can contain a fixed product setup, allow price selection for specific bundle items, or use automatically active recurring prices on a related product.
Add-ons
Once a bundle has been selected and the relevant price selections provided, you can query which add-on products or add-on bundles are available:
curl https://askell.is/api/v2/bundle-templates/42/addons/ \
-H "Authorization: Api-Key your-secret-api-key" \
-H "Content-Type: application/json" \
-d '{
"bundle_item_selections": [
{
"bundle_item": 100,
"selected_price": 200
}
]
}'
Quote and request data
The same basic fields are used repeatedly in quotes, checkout flows, and direct contract creation.
Field |
Description |
|---|---|
|
The ID of the selected bundle. |
|
Bundle quantity. |
|
Price or quantity selections for individual items within a bundle. |
|
Directly selected recurring products without a bundle. |
|
One-time products or products that should only appear on the first billing run. |
|
Add-on products selected through add-on rules. |
|
Add-on bundles selected through add-on rules. |
Only one primary input form for recurring products may be used at a time:
bundle_templateitems
initial_items are only used on the initial billing. They do not become persistent SubscriptionContractItem rows. See also Direct contract creation.
Quote before creating the contract
POST /api/v2/subscription-offer-quotes/ builds a non-persistent quote from the request data and returns a summary of recurring lines, initial lines, taxes, totals, and the estimated billing schedule.
Example quote for a bundle:
curl https://askell.is/api/v2/subscription-offer-quotes/ \
-H "Authorization: Api-Key your-secret-api-key" \
-H "Content-Type: application/json" \
-d '{
"bundle_template": 42,
"bundle_quantity": 2,
"bundle_item_selections": [
{
"bundle_item": 100,
"selected_price": 200
}
],
"additional_items": [
{
"rule_id": 300,
"price": 400,
"quantity": 1
}
],
"initial_items": [
{
"price": 500,
"quantity": 1
}
]
}'
A shortened response might look like this:
{
"input_mode": "bundle",
"bundle_template_id": 42,
"bundle_quantity": 2,
"currency": "ISK",
"period_start_at": "2026-05-20T00:00:00Z",
"period_end_at": "2026-06-20T00:00:00Z",
"subtotal_amount": "4500.0000",
"tax_amount": "0.0000",
"total_amount": "4500.0000",
"recurring_subtotal_amount": "4000.0000",
"recurring_tax_amount": "0.0000",
"recurring_total_amount": "4000.0000",
"billing_schedule_preview": {
"kind": "interval"
},
"recurring_items": [
{
"key": "bundle-item-100",
"source": "bundle",
"creates_contract_item": true,
"price_id": 200,
"product_id": 10,
"product_name": "Vefáskrift",
"billing_type": "recurring",
"quantity": 2,
"unit_amount": "2000.0000",
"line_total_amount": "4000.0000"
}
],
"initial_lines": [
{
"key": "initial-item-500",
"source": "initial_items",
"creates_contract_item": false,
"price_id": 500,
"product_id": 11,
"product_name": "Áskrifendagjöf",
"billing_type": "one_time",
"quantity": 1,
"unit_amount": "500.0000",
"line_total_amount": "500.0000"
}
]
}
Example quote for direct products without a bundle:
curl https://askell.is/api/v2/subscription-offer-quotes/ \
-H "Authorization: Api-Key your-secret-api-key" \
-H "Content-Type: application/json" \
-d '{
"currency": "ISK",
"items": [
{
"price": 200,
"quantity": 1
}
],
"initial_items": [
{
"price": 500,
"quantity": 1
}
]
}'
Direct contract creation
POST /api/v2/subscription-contracts/ creates a V2 contract directly. This flow is suitable when the integration has already confirmed the order and does not need to run payment pre-processing in the same call.
You can submit, among other things:
customer_referencecurrencybundle_templateoritemsbundle_item_selectionsadditional_itemsadditional_bundlesinitial_itemsmetadatapayment_processor_overrideif the contract should be pinned to a specific payment processor
initial_items do not become persistent SubscriptionContractItem rows. They are only placed on the first billing run lines if the initial billing is based on them.
Fetch and update a contract
A contract is fetched from:
GET /api/v2/subscription-contracts/{id}/
Lists and filters support, among other things:
statecustomer_referencereferencelegacy_subscription_id
List pagination
V2 list endpoints only use pagination if page_size is provided.
page_sizeenables paginationthe default
page_sizeis10when pagination is enabledthe maximum
page_sizeis1000pageselects the page
Example:
GET /api/v2/subscription-contracts/?page_size=25&page=2
When pagination is enabled, the response uses the standard shape with count, next, previous, and results. If page_size is not provided, an unpaginated list is returned.
The V2 detail endpoint only allows limited updates. The contract is not intended to be a freely editable order draft, but rather a business-critical state that should evolve through defined lifecycle calls.
Lifecycle actions
V2 supports the following lifecycle calls on contracts:
Path |
Description |
|---|---|
|
Cancels the contract, either immediately or at the end of the period. |
|
Restarts a contract that has been stopped. |
|
Temporarily pauses a contract. |
|
Resumes a paused contract. |
|
Deletes a scheduled pause. |
|
Activates an inactive contract if it can move to the active state. |
Main request fields:
Action |
Fields |
Required |
|---|---|---|
|
|
Both optional |
|
|
Optional |
|
|
|
|
|
Optional |
Example of canceling a contract at the end of the period:
curl -X POST https://askell.is/api/v2/subscription-contracts/123/cancel/ \
-H "Authorization: Api-Key your-secret-api-key" \
-H "Content-Type: application/json" \
-d '{
"cancel_at_period_end": true,
"reason": "Viðskiptavinur óskaði eftir lokun"
}'
Checkout flow
When payment pre-processing is required before a contract becomes active, the checkout flow is recommended:
Calculate or confirm the quote.
Fetch eligible payment processors with
POST /api/v2/payment-processor-options/.Create a
checkoutwithPOST /api/v2/checkouts/.Finalize the
checkoutwithPOST /api/v2/checkouts/{token}/finalize/.
POST /api/v2/payment-processor-options/ returns the payment processors that are valid for the given quote, currency, and collection method. If only one processor is available, it can be used as the default. If more than one is eligible, the integration should allow a choice.
A shortened response might look like this:
{
"currency": "ISK",
"collection_method": "card",
"selected_account_payment_processor_id": 7,
"selection_reason": "single_eligible",
"requires_selection": false,
"results": [
{
"account_payment_processor_id": 7,
"display_name": "Straumur / Adyen",
"payment_processor": "adyen",
"collection_method": "card",
"resolution_source": "eligible",
"supports_initial_charge": true,
"supports_recurring_charge": true,
"render_mode": "adyen_checkout",
"payment_processor_type": "adyen",
"is_3d_secure": true,
"card_collection_in_frontend": true,
"supports_checkout": true,
"address_required": false,
"registration_mode": "delayed_tokenization",
"public_registration_config": {}
}
]
}
POST /api/v2/checkouts/ creates a checkout object that stores the quote snapshot, customer, selected payment processor, and other prerequisite data.
GET /api/v2/checkouts/{token}/ fetches the status of the checkout object. checkout_url points to this endpoint.
POST /api/v2/checkouts/{token}/finalize/ attempts to complete contract creation and the initial billing.
Prerequisites before calling finalize
finalize now performs a preflight check before creating the contract. The following must therefore be valid before attempting finalize:
The customer must exist.
The selected payment processor must be valid for the quote.
The customer must have a verified payment method that matches the selected payment processor, unless the initial billing amount is 0 ISK.
If these prerequisites are not met, an error response is returned and no contract or billing run is created.
It is important to distinguish between two kinds of 400 responses from finalize:
Precondition failure: no contract and no billing run are created.
Failed payment attempt: the contract and billing run may already have been created, but
checkout.statusbecomesfailed.
A shortened finalize response might look like this:
{
"id": 15,
"token": "f2dd7db2-4ae1-45c0-b8f6-2f98d1b70a9a",
"checkout_url": "https://askell.is/api/v2/checkouts/f2dd7db2-4ae1-45c0-b8f6-2f98d1b70a9a/",
"status": "succeeded",
"customer_id": 1008,
"customer_reference": "customer-123",
"currency": "ISK",
"subtotal_amount": "4500.0000",
"tax_amount": "0.0000",
"total_amount": "4500.0000",
"contract_id": 33,
"initial_billing_run_id": 32,
"account_payment_processor_id": 7,
"quote_snapshot": {
"input_mode": "bundle",
"total_amount": "4500.0000"
}
}
Outcomes from finalize
Status |
HTTP |
Description |
Next step |
|---|---|---|---|
|
|
The contract has been created and the initial billing has completed. |
Store |
|
|
An external processor must complete its flow before the billing is considered complete. |
Monitor the |
|
|
The payment attempt failed after the contract and billing run were created. |
Inspect |
Precondition failure |
|
The request data or payment prerequisites were invalid; no contract was created. |
Correct the request data or payment method before trying again. |
Billing runs and retries
V2 billing runs are read from:
GET /api/v2/billing-runs/
GET /api/v2/billing-runs/{id}/
A failed billing run can be retried with the following endpoint:
POST /api/v2/billing-runs/{id}/retry/
The billing run detail endpoint response includes, among other things, lines, attempts, related transactions, and status.
A shortened response might look like this:
{
"id": 32,
"contract_id": 33,
"customer_id": 1008,
"customer_reference": "customer-123",
"period_start_at": "2026-05-20T00:00:00Z",
"period_end_at": "2026-06-20T00:00:00Z",
"state": "succeeded",
"currency": "ISK",
"subtotal_amount": "4500.0000",
"tax_amount": "0.0000",
"total_amount": "4500.0000",
"attempt_count": 1,
"lines": [
{
"id": 71,
"price_id": 200,
"price_version_id": 15,
"product_name": "Vefáskrift",
"quantity": 2,
"line_total_amount": "4000.0000",
"service_period_start_at": "2026-05-20T00:00:00Z",
"service_period_end_at": "2026-06-20T00:00:00Z"
}
],
"attempts": [
{
"id": 44,
"attempt_no": 1,
"state": "succeeded",
"transaction_id": 19,
"fail_code": null,
"fail_message": null
}
]
}
Versioning policy and rate limits
V2 is versioned by URL, that is, under /api/v2/. New breaking changes should therefore appear under a new major-version path rather than silently changing the meaning of existing endpoints.
No documented rate limits are defined on this page. Integrations should still expect transient failures, use retries with backoff, and log responses with status codes such as 400, 404, and 5xx.
Example end-to-end flow in Python
import requests
API_KEY = 'your api key here'
headers = {
"Authorization": f"Api-Key {API_KEY}",
"Content-Type": "application/json",
}
quote_payload = {
"customer_reference": "customer-123",
"currency": "ISK",
"bundle_template": 42,
"bundle_item_selections": [
{
"bundle_item": 100,
"selected_price": 200,
}
],
"initial_items": [
{
"price": 500,
"quantity": 1,
}
],
}
try:
quote_response = requests.post(
"https://askell.is/api/v2/subscription-offer-quotes/",
json=quote_payload,
headers=headers,
)
quote_response.raise_for_status()
processor_response = requests.post(
"https://askell.is/api/v2/payment-processor-options/",
json=quote_payload,
headers=headers,
)
processor_response.raise_for_status()
processor_options = processor_response.json()["results"]
# Hér er gert ráð fyrir að aðeins einn færsluhirðir komi til greina.
# Ef fleiri en einn er í boði þarf samþættingin að leyfa val.
checkout_payload = {
**quote_payload,
"collection_method": "card",
"account_payment_processor": processor_options[0]["account_payment_processor_id"],
}
checkout_response = requests.post(
"https://askell.is/api/v2/checkouts/",
json=checkout_payload,
headers=headers,
)
checkout_response.raise_for_status()
checkout = checkout_response.json()
finalize_response = requests.post(
f"https://askell.is/api/v2/checkouts/{checkout['token']}/finalize/",
json={},
headers=headers,
)
finalized_checkout = finalize_response.json()
if finalize_response.status_code == 400:
contract_id = finalized_checkout.get("contract_id")
initial_billing_run_id = finalized_checkout.get("initial_billing_run_id")
if contract_id:
# Greiðslutilraun mistókst, en samningur og innheimtulota gætu þegar verið til.
# Hér ætti raunveruleg samþætting að skrá þetta og meta hvort reyna eigi aftur.
print(
"Checkout failed after contract creation",
contract_id,
initial_billing_run_id,
)
else:
# Forsendubrestur: enginn samningur var stofnaður.
finalize_response.raise_for_status()
else:
finalize_response.raise_for_status()
except requests.HTTPError as exc:
response = exc.response
try:
error_body = response.json()
except ValueError:
error_body = {"raw": response.text}
print(response.status_code, error_body)
raise
Common errors
Error |
HTTP |
Description |
|---|---|---|
|
|
The customer was not found or is missing from the request data. |
|
|
The selected payment processor is not valid for the quote. |
|
|
No verified payment method was found for the customer. |
|
|
A required price selection is missing or does not match the bundle. |
|
|
The selected add-on product does not match an active add-on rule. |
|
|
The direct price selection is invalid or does not match the currency. |
Not found |
|
The contract, |