---
title: "Push Headless Version"
method: POST
path: "/v2/paywalls/{id}/versions"
tags: ["Paywalls V2"]
---

# Push Headless Version

`POST /v2/paywalls/{id}/versions`

Pushes a self-contained headless (code-first) paywall snapshot as a new immutable version: stores the content-addressed artifact and creates the version + snapshot. Production is untouched — promote makes a pushed version live. Pushing identical content returns the existing version. Requires paywalls:write scope.

## Path parameters

- `id` string, required — Paywall ID

## Request body

- object
  - `html` string, required — The self-contained HTML snapshot to push, at most 5 MB. Stored content-addressed: pushing identical content returns the existing version.
  - `surface` string — The framework surface identifier (directory name) this snapshot was built from — stamped into paywall metadata so repeated pushes bind to the same paywall.
  - `assets` object[] — Hosted assets the snapshot references. Stored with the version and stamped into the paywall's pre-cache manifest on promote so SDKs can cache them ahead of presentation.
    - `url` string, required — Hosted asset URL referenced by the snapshot
    - `mime_type` string, required — MIME type of the asset
  - `products` object[] — The products this snapshot's config declares, as reference-name to store-identifier entries. Stored with the version and stamped onto the paywall on promote — the headless equivalent of attaching products in the editor, so serve-time price resolution works identically to editor paywalls.
    - `store` union, required — Store this product belongs to
      - 'app-store' — Apple App Store product
      - 'play-store' — Google Play product
      - 'stripe' — Stripe product
      - 'custom' — Custom (external purchase controller)
    - `identifier` string, required — Platform-specific product identifier
    - `reference_name` string, required — Stable reference name used in paywall editor/runtime (for example: primary)
  - `web_checkout_destination` 'EXTERNAL' | 'IN_APP_BROWSER' | 'PAYMENT_SHEET' | 'PAYWALL' | 'PAYWALL_EMBEDDED' | 'PAYWALL_APPLE_PAY' — How this snapshot's config completes web checkout (`checkout: sheet` → PAYWALL_EMBEDDED, `applePay` → PAYWALL_APPLE_PAY, `external` → EXTERNAL). Stored with the version and stamped onto the paywall on promote — the same column editor paywalls set, so hosts request checkout directives identically for both.
  - `background_color_hex` string — The paywall's light-mode background color as a hex string (`#RRGGBB` or `#RRGGBBAA`). Stored with the version and stamped onto the paywall on promote — the same column editor paywalls set. Native SDKs paint it behind the webview and derive the loading spinner from it, so it must match the paywall's own page background.
  - `dark_background_color_hex` string — The paywall's dark-mode background color as a hex string (`#RRGGBB` or `#RRGGBBAA`). Stored with the version and stamped onto the paywall on promote. Optional — SDKs fall back to the light color in dark mode when absent.
  - `presentation_style` union — How the SDK presents this snapshot's paywall, from its config. Stored with the version and stamped onto the paywall on promote (the legacy style column and the v3 geometry) — a headless paywall has no dashboard editor, so this is the only way to set it. Omitted means fullscreen.
    - object
      - `type` 'FULLSCREEN' | 'MODAL' | 'PUSH' | 'NO_ANIMATION', required
    - object
      - `type` 'DRAWER', required
      - `height` number, required — a positive number
      - `corner_radius` number, required — a positive number
    - object
      - `type` 'POPUP', required
      - `width` number, required — a positive number
      - `height` number, required — a positive number
      - `corner_radius` number, required — a positive number
  - `feature_gating` union — Whether the placement's feature is gated on a purchase. Stored with the version and stamped onto the paywall on promote. Omitted means non_gated.
    - 'gated' — Access is gated behind a purchase
    - 'non_gated' — Access is not gated
  - `on_device_cache` boolean — Whether SDKs cache this paywall on device. Stored with the version and stamped onto the paywall on promote. Omitted means enabled.
  - `scroll_enabled` boolean — Whether the paywall webview scrolls. Stored with the version and stamped onto the paywall on promote. Omitted means enabled.
  - `game_controller_enabled` boolean — Whether game controller input is forwarded to the paywall. Stored with the version and stamped onto the paywall on promote. Omitted means disabled.
  - `introductory_offer_eligibility` 'automatic' | 'always_eligible' | 'always_ineligible' — How SDKs decide trial eligibility for this paywall's products. Stored with the version and stamped onto the paywall on promote. Omitted means automatic.

## Response `200`

Success

- object
  - `object` 'paywall.version', required — Object type, always `paywall.version`
  - `id` string, required — ID of the paywall
  - `version` number, required — The pushed version number
  - `created_at` string, required — ISO 8601 timestamp of when the version was created
  - `snapshot_id` string, required — Content hash (sha256) identifying the pushed snapshot
  - `url` string, required — Immutable URL the pushed snapshot is served from

## Other responses

- `400` — The request did not match the expected schema
- `401` — No API key was provided in the request
- `403` — The API key does not have permission to perform this action
- `404` — The requested resource was not found
- `422` — @superwall/api-schema/v2/errors/PaywallArchivedError
- `429` — Too many requests have been made in a short period
- `500` — An unexpected error occurred on the server

---

[API](https://skmtc.dev/superwall/apis/superwall-api-v2.md) · [All operations](https://skmtc.dev/superwall/apis/superwall-api-v2/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/superwall/superwall-api-v2/revisions/44900ad77d2c/schema)
