Change log

September 30 2026

  • The embedded checkout (askell.js) now accepts defaultOffer (or default_offer), which pre-selects an offer when a sales channel offers more than one product. Unlike offer, which fixes the offer and skips the step where it is chosen, the selection stays on screen and the buyer can change it. A price or bundle template that is not in the session’s offer_catalog, or is sold out, is ignored and the widget then uses its normal default selection; a promotion_code on the offer is applied from the start. See Embedded checkout.

September 17 2026

  • The payment page receipt is now itemized from the quote the sale was charged from, including the shipping fee and the total it is part of. The shipping option is chosen after the summary last fetched a quote, so that quote on its own was missing the fee. POST /public/api/payment-pages/process_payment_and_finalize/ now returns quote — the stored quote the charge was based on, in the same shape that POST /public/api/payment-pages/quote/ returns (including quote_token, expires_at and promotion). On the older path, where payment is made without quote_token, quote is null and the receipt shows the quote the page already had.

September 16 2026

  • Fulfillment orders can now be fulfilled in an external system. Under Settings › Shipping you choose between three options: no orders, fulfillment in Áskell, or fulfillment in an external system. With the last option, orders are still created and sent out with fulfillment_order.* events as before, but the seller’s warehouse system books the shipment itself — so the booking button and the shipping label disappear from the order view, although orders can still be fulfilled and canceled there.

  • Two new endpoints close the loop from the other side: POST /api/v2/fulfillment-orders/<id>/fulfill/ records the shipment (tracking_number, tracking_url, provider_order_id, carrier and weight_grams, all optional), fulfills the order, sends the customer the notification that the order is on its way and sends fulfillment_order.fulfilled; POST /api/v2/fulfillment-orders/<id>/cancel/ cancels an order. Both are idempotent, return the full order just like GET does, and return 409 with order_cancelled or order_fulfilled when the status conflicts. They are available in both fulfillment modes, so a seller who fulfills in Áskell can also automate the last step from their own system.

September 15 2026

  • Payment pages now calculate the price once and bind every later step to it. The quote (POST /public/api/payment-pages/quote/) now returns quote_token and expires_at, and card sessions (Adyen/Teya), verify_card and process_payment_and_finalize accept the token and price from the stored quote instead of recalculating the quote from browser data. The amount the customer sees, the card authorization and the first billing run are therefore always the same number, shipping fee and discounts included.

  • A stored quote is checked against the current pricing at every step and rejected if anything has changed: quote_changed, promotion_terms_changed, combo_eligibility_changed or invalid_promotion_code. An expired quote, an already paid quote or another page’s quote returns quote_expired, quote_consumed, quote_page_mismatch or quote_not_found, and the payment page sends the customer back to the payment step, where a new price is fetched. A card verified for one quote does not pay for another (quote_mismatch). The quote returns promotion with the code the price was calculated with, so the summary never shows a discount the charge does not carry, and the quote endpoint is limited to 120 requests per minute per IP address.

  • Item lines, promotion_code, shipping and zip_code as pricing inputs on verify_card, process_payment_and_finalize and card sessions are deprecated. They keep working for one more release for older browser clients and will be removed in the next release.

  • The shipping fee now shows in the total before payment, both on payment pages and in the widget. Quote requests (POST /public/api/payment-pages/quote/ and POST /api/v2/checkout-sessions/<token>/quote/) accept the shipping object, add the fee to subtotal_amount, tax_amount and total_amount, and return a shipping_fee breakdown; free shipping comes back with zeros so it can be shown as costing nothing. The free-shipping threshold (free_above_amount) is evaluated after promotion codes, as when a checkout is created, and the summaries show shipping as a separate line. Payment page card verification (verify_card) now also receives shipping and delivery_address, so the authorization covers the fee.

  • The shipping fee is no longer part of the purchase amount when the first period ships no products. A trial period defers the products to the first renewal, which is then the first shipment and charges the agreed fee; the quote, checkout, card authorization and first billing run therefore all show the same amount without the fee. A trial subscription that ships a one-time item right away still pays for that shipment, and a promotion code that skips the trial period brings both the products and the fee forward.

  • Shipping rate tables (step four): Dropp zones (home delivery and Flytjandi) are now fetched directly from Dropp. The zones are read-only in the dashboard — the prices in the rate table still belong to the seller. Under Settings › Shipping, the “Refresh Dropp zones” button fetches the lists right away, and the zone form shows when they were last synced. The same refresh can run nightly as a scheduled task (django_celery_beat), documented in docs/shipping-rate-tables-plan.md; it is set up manually for each environment. If Dropp fails, or returns an empty list, the previous postal codes stay unchanged.

  • Synced Dropp zones now control availability: if a Dropp connection has an active synced zone for home delivery (or Flytjandi), that shipping option is not offered for postal codes outside the zone, regardless of the rate table — the prices still belong to the seller. If there is no synced zone, nothing changes. Renewals that fall outside the zone are still charged the fixed fee, and a warning is logged.

September 14 2026

  • Shipping rate tables (step one): a shipping option can now have a rate table by zone (ShippingZone, postal codes and ranges) and weight band (ShippingRate), in the style of Dropp’s price list. Renewals are priced by the contract’s delivery postal code and the calculated weight of the products; if an option has no rate table, the fixed fee still applies and nothing changes. For now, rate tables are set up in the Django admin; a dashboard interface and postal code selection at purchase come in the next steps.

  • Shipping rate tables (step two): the rate table now applies at purchase. POST /api/v2/checkouts/, checkout sessions, payment pages and embedded Teya/Adyen payments price a shipping option that has a rate table by the postal code of the delivery address and the weight of the products, and reject an option whose rate table does not cover the shipment (shipping_not_available). The checkout session quote request accepts delivery_zip_code and returns shipping_options priced for the shipment; the widget and payment pages fetch the list again when the postal code changes. shipping_selection records the zone_name and weight_band the price was read from.

  • Shipping rate tables (step three): zones and rate tables are now set up in the dashboard instead of the Django admin. Under Settings › Shipping there is a new list of shipping zones where postal codes are entered as ranges and single codes (“101-113, 170, 200-276”); zones synced from a carrier are locked. Each shipping option gets a rate table below the shipping option form — rows are weight bands, columns are zones plus a column for “Everywhere else” — and the table is saved in one go, so if the server rejects one row, the previous table stays unchanged. For Dropp options in ISK, Dropp’s price list (Dropp 2026) can be loaded with one click; it sets up the zones it needs. The catalog now marks products without a weight with “No weight” when any shipping option is priced by a rate table, because such products cannot be shipped with those options.

September 11 2026

  • The shipping fee is now charged. The fee is part of the quote (total_amount on /api/v2/checkouts/ and the shipping_fee breakdown) and is added as a line of type shipping to every billing run that ships products — the first billing run that ships and every renewal. Promotion codes and combo discounts do not reduce the fee, and the shipping option’s free_above_amount threshold is evaluated against the billing run’s product amount after discounts. Renewals use the shipping option’s current price; if the option has been deleted, the agreed fee is used. The fee follows the shipping feature, and shipping selections recorded before this release are not charged.

  • Accounting integrations gained shipping_product_id: the accounting product that shipping fee lines are posted to (its VAT rate must match the fee); the default product is used if it is not set.

  • Shipping options (shipping-options in the dashboard) gained tax_rate: the VAT rate that applies to the shipping fee, since shipping often carries a different rate than the product. If it is not set, the account’s default tax rate applies.

  • lines in billing run responses (/api/v2/billing-runs/ and webhooks) can now contain the line_type value shipping.

September 7 2026

  • New V2 endpoints for creating, fetching, updating and deleting coupons and promotion codes: /api/v2/coupons/, /api/v2/coupons/{couponId}/, /api/v2/promotion-codes/ and /api/v2/promotion-codes/{promotionCodeId}/. Lists support filters (valid, duration, code, coupon, active, customer_reference and more) and page_size pagination. Deletion is a soft delete, and deleting a coupon or code that has already been redeemed is rejected.

  • Expired coupons and promotion codes (redeem_by / expires_at in the past) can now be updated and deleted, both in the dashboard and through the API; a date in the past is only rejected on a coupon or code that is still valid. A max_redemptions value of 0 is now stored as no limit.

  • Swagger (V2 Coupons) and the documentation have been updated; see Coupons and promotion codes.

  • Combo discounts: in the dashboard, a seller can now set a product to get an automatic discount (a percentage or a fixed amount) when the customer also has certain other products — whether they are bought in the same order or are on another active contract. The discount is calculated automatically on every billing run and stops applying when the customer no longer has the qualifying products.

  • /api/v2/subscription-offer-quotes/, /api/v2/checkouts/ and checkout-sessions quotes now return the combo_discounts list with the combo discounts that apply; the subtotal_amount, tax_amount, total_amount and recurring_* amounts already include the discount. The offer quote request (subscription-offer-quotes) now accepts an optional customer to take the customer’s other contracts into account, whether or not a promotion code is sent.

August 19 2026

  • unit_amount_override (a fixed per-unit item price) is now supported in the V2 item operations on an active contract: items/update, items/add and proration-preview on /api/v2/subscription-contracts/{id}/. Previously the field only worked at contract creation. In items/update, omitting the field keeps the item’s current fixed price, while an explicit null removes it so the item reverts to the catalog price. Proration lines resulting from such a change get the new proration_reason value unit_amount_changed.

  • The item operation endpoints (items/add, items/update, items/remove, proration-preview and related operations) now reject unknown fields, and fields not supported by the selected operation, with a 400 error instead of silently ignoring them. Integrations that sent extra fields to these endpoints need to remove them. items/update now only accepts the operation value update_item.

  • Swagger and the documentation have been updated accordingly; see Subscription Contracts V2.

January 26 2026

  • New endpoint /api/subscriptions/{subscriptionId}/set_expiry/ that allows changing the expiry date of a subscription.

December 15 2025

  • Customers (/api/customers/) now accept and return a phone number (phone) when updated through the API and the Swagger documentation has been updated accordingly.

November 25 2025

  • New endpoint for transactions so it is now possible to fetch lists of transactions and filter them.

October 9 2025

  • It is now possible to supply a payment processor id when creating new checkouts. Before, it was only possible to specify a plan to pick a specific payment processor.

  • Payment processor information is now supplied when fetching plans.

August 29 2025

  • New API endpoint /api/payments/{uuid}/refund/ that allows refunds for single one-off payments

September 26 2024

  • New subscription values that indicate whether a subscription has been canceled, cancelled and cancel_date.

  • Discrepancies in Swagger documentation has been fixed according to responses from the web service.

September 3 2024

  • Ordering is now possible in the /api/subscriptions endpoint.

April 18 2024

  • New endpoint /api/webhooks/ which allows users to create and work with web-hooks that can send notifications about events that happen within Áskell.

February 28 2024

  • New endpoint /api/hello/ that confirms that the web-service works and can be used for testing.

  • amount and discount can now be used as optional variables when a subscription is added to a customer within /api/customers/{reference}/subscriptions/add/

January 5 2024

  • Swagger documentation fixed. GET added to individual payments on the URL https://askell.is/api/payments/{uuid}/.

December 7 2023

  • Users can limit the output that the subscription export delivers by adding the variable ?type=light to the URL. By doing this less data is extracted about the customer and transaction history and is better when exporting a large list of subscriptions for handling. More data can then be extracted on individual subscriptions if needed.

December 2 2023

  • New option when fetching a list of subscriptions or customers. You can add the parameter ?page_size=100 to the URL to get 100 records at a time. The output then changes from a list of records to a list of records in results, the number of records in count and links to the next and previous page in next and previous. The maximum page size is 1000 records.