Digital products and subscriptions
Sell digital goods, configure recurring plans, and synchronize subscription access.
Digital E-Commerce must be available to the shop and activated. Administrative /api/admin/digital/* routes require an admin session; existing catalog:read/catalog:write scopes do not grant access to these add-on routes. Product creation itself still uses the product API.
The current API deliberately uses German route and response names such as plaene, abos, and kuendigen. Keep those identifiers unchanged in requests.
Check activation
GET /api/admin/digital/status returns erlaubt (available), aktiv (enabled), abo_provision, and abo_provision_monate. PUT to the same path updates aktiv and subscription commission settings when the add-on is available. The commission modes are erste, monate, and immer.
One-time digital purchases
Create a normal product with is_digital: true. Digital products default to track_stock: false on creation; specify track_stock: true if supply is limited. Use GET /api/admin/products?digital=ja to list them, or digital=alle for the entire catalog.
Sell a one-time digital item through POST /api/store/checkout-sessions, using its product ID and customer.billing_address. Digital-only orders need no shipping address or shipping method and return requires_shipping: false. Mixed carts still need delivery details for physical goods.
The hosted payment flow collects the digital-content acknowledgement; direct checkout calls use widerruf_verzicht where required. Deliver the digital entitlement from verified payment/order information in your own application. Marking a product as digital does not upload a file or implement your storefront's access control.
Create a recurring plan
Use an existing digital product belonging to the shop:
| Method | Path | Result |
|---|---|---|
GET | /api/admin/digital/plaene | plaene, including product names and active counts |
POST | /api/admin/digital/plaene | Created plan |
PATCH | /api/admin/digital/plaene/:id | Updated plan and optional hinweis |
Example create body:
{
"product_id": "prod_digital_example",
"name": "Monthly access",
"price_cents": 1490,
"interval_unit": "month",
"interval_count": 1,
"trial_days": 7,
"is_active": true
}
price_cents must be 50–10,000,000. Intervals use day, week, month, or year; interval_count defaults to 1 and must satisfy the provider's maximum interval. trial_days defaults to 0 and can be up to 730. Optional intro_discount_percent and intro_discount_months must be supplied together (or both cleared).
Responses include currency, interval_text, first_charge_cents, and intro_price_cents. The create schema does not accept a caller-selected currency. Read the returned values for display. Updating a plan's price/interval affects new subscriptions; existing provider subscriptions keep their price. A plan's product cannot be changed; disable it with is_active: false when it should no longer be sold.
Create a subscription checkout
Use POST /api/store/abo-sessions, rather than adding a plan to the one-time session's items array. This endpoint is served by the shop and checkout backends and uses the shop's pk_sf_ key with the same origin/return-URL rules as checkout sessions.
curl --fail-with-body "$SELLFORGE_CHECKOUT_URL/api/store/abo-sessions" \
-H "Authorization: Bearer $SELLFORGE_PUBLIC_KEY" \
-H "Origin: $STOREFRONT_ORIGIN" \
-H 'Content-Type: application/json' \
-d '{
"plan_id": "spl_example",
"return_url": "https://shop.example.com/checkout/result",
"customer_email": "[email protected]"
}'
The plan must be active and its digital product published. Obtain the real plan ID through the administrative plan configuration; there is currently no public plan-list endpoint in this module.
The response is { "id": "cs_example", "url": "https://checkout.sellforge.ai/c/cs_example" }. Save the session association and redirect to the returned URL. A subscription session always uses the hosted flow. Its creation schema accepts plan_id, return_url, customer_email, and optional affiliate_code; it does not accept the one-time checkout's nested customer address prefill. Billing details are collected in the hosted checkout.
affiliate_code attributes the subscription to a partner; introductory discounts belong to the plan. Subscription checkout does not expose a separate discount-code input or the one-time express shortcut. During a free trial, the amount charged today can be zero while a payment method is collected for later billing.
Synchronize access
Use the signed subscription events sent to the shop's checkout webhook destination. checkout.completed can carry a subscription_id without an order_id during a trial. Ongoing lifecycle events use abo.aktiv, abo.bezahlt, abo.gekuendigt, abo.fortgesetzt, abo.beendet, and abo.zahlung_fehlgeschlagen.
Persist the subscription ID and current period in your application. A paid subscription invoice creates an order with subscription_id; the abo.bezahlt event identifies that order. Keep is_test subscriptions separate from real customer access and make repeated event deliveries idempotent.
Administration and cancellation
| Method | Path | Purpose |
|---|---|---|
GET | /api/admin/digital/abos | List up to 500 subscriptions in abos |
GET | /api/admin/digital/abos/statistik | Subscription statistics |
GET | /api/admin/digital/abos/:id | abo plus related bestellungen |
POST | /api/admin/digital/abos/:id/kuendigen | Cancel; accepts sofort (default false) and optional grund |
POST | /api/admin/digital/abos/:id/fortsetzen | Withdraw a scheduled cancellation |
List filters include plan_id, q, test (ja, nein, alle), and status (laufend, testzeit, zahlung_offen, gekuendigt, beendet, alle). Response subscription states use provider-style values such as active, trialing, and past_due, rather than those list-filter names.
Customer-account routes are GET /api/customer/abos and POST /api/customer/abos/:id/kuendigen, served by the shop backend with a customer session. Email management links use the token-protected /api/checkout/abo/:token routes on the checkout backend, including POST /:token/kuendigen and /:token/fortsetzen.
Customer cancellations take effect at the end of the paid period. Read cancel_at_period_end, current_period_end, and endet_am; a cancellation request is not the same as immediate loss of access.
Found something unclear? Let us know