Skip to content

Install a purchase’s apps

POST
/teams/{teamId}/bundle-purchases/{bundlePurchaseId}/apps
curl --request POST \
--url https://qinto-api.blotout.io/teams/7b8d1a52-6c1e-4b6f-9a30-2f8a54d1c3e9/bundle-purchases/9c1d2e3f-4a5b-4c6d-8e7f-0a1b2c3d4e5f/apps \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "appSlugs": [ "facebook-capi" ], "installDependencies": false, "acknowledgeNecessary": [], "approvedCharges": { "pixel-sync": { "upToCents": 4900, "trialDays": 14 } } }'

Installs the chosen apps of a bundle purchase on every site the purchase covers, at no charge beyond the bundle: step 2 of buying a bundle, which the team can run at any time. A site that already runs an app keeps it as it is.

How it works

The installs commit together, and the sites update in the background. acknowledgement_required names an app that runs without visitor consent; repeat the call with its slug in acknowledgeNecessary. dependencies_required carries the plan of the apps outside your choice the chosen apps need; repeat with installDependencies. Such an app outside the bundle installs at its own price, so charge_approval_required gives the charge for approvedCharges. cap_reached names the site that would go over 50 apps, and nothing is installed. The caller needs manage_domain_apps on every covered site; a team API token is refused.

Required permissions

  • domain:manage_domain_apps:write on all domains, or on the addressed domain when its id is in the route
teamId
required

Unique id (UUID) of the team that owns the addressed resources, as shown in the team settings on the Blotout platform.

string format: uuid

Example

7b8d1a52-6c1e-4b6f-9a30-2f8a54d1c3e9

Unique id (UUID) of the team that owns the addressed resources, as shown in the team settings on the Blotout platform.

bundlePurchaseId
required

Unique id (UUID) of the purchase, as the list operation returns it.

string format: uuid

Example

9c1d2e3f-4a5b-4c6d-8e7f-0a1b2c3d4e5f

Unique id (UUID) of the purchase, as the list operation returns it.

The covered apps to install and the confirmations the operation may require.

Media typeapplication/json

The covered apps to install, with the confirmations the operation may require.

object
appSlugs
required

The covered apps to install on every site the purchase covers; duplicates are collapsed.

Array<string>
>= 1 items <= 50 items
installDependencies

Confirms the apps outside your choice that the chosen apps need, which install with them; required when the call answers dependencies_required.

boolean

Example

false
acknowledgeNecessary

Slugs of the apps that run without visitor consent, among the chosen apps and the apps they need, that a person acknowledged.

Array<string>
<= 60 items

Example

[]
approvedCharges

The charge a person was shown for each app that charges the team, by app slug: this app and every app the call installs with it, as charge_approval_required or the dependencies_required plan gave them. Approving it also accepts the developer’s terms. The call is refused with the current charge when an entry is missing, the amount went up or the free days went down since it was shown.

object
key
additional properties
object
upToCents
required

The most the install can charge one site in a month, in US cents: the base price plus the usage cap.

integer

Example

4900
trialDays
required

The free days before the first charge; 0 when the app has no trial.

integer

Example

14

Example

{
"pixel-sync": {
"upToCents": 4900,
"trialDays": 14
}
}

The installs made.

Media typeapplication/json
object
installed
required

One entry per install made, site by site; empty when every chosen app already ran on every covered site.

Array<object>

One install step 2 of a bundle purchase made.

object
domainId
required

Id of the site the app was installed on.

string
domain
required

The site’s hostname.

string
appSlug
required

Slug of the installed app.

string
version
required

The version installed.

string

Example

{
"installed": [
{
"domainId": "3f2a9c81-5d47-4e0b-8c66-91b2a7f04d15",
"domain": "q.shop.example.com",
"appSlug": "facebook-capi",
"version": "1.4.1"
}
]
}

invalid_params — A path parameter failed validation (for example a malformed UUID); details pinpoints the offending parameter. invalid_body — The JSON body is unparseable or failed schema validation; details pinpoints the offending fields when the body could be parsed.

Media typeapplication/json
object
error
required

Machine-readable error slug; switch on this value rather than on the HTTP status alone.

string
Allowed values: invalid_params invalid_body
details

Field-level validation errors: a tree keyed by input property, each node carrying an errors array of human-readable messages. Render it to help the caller correct the request.

object
key
additional properties

Example

{
"error": "invalid_params",
"details": {
"errors": [],
"properties": {
"teamId": {
"errors": [
"Invalid UUID"
]
}
}
}
}

unauthenticated — The Authorization: Bearer token is missing, malformed, or no longer valid. Generate a team API token or obtain a new OAuth access token.

Media typeapplication/json
object
error
required

Machine-readable error slug; switch on this value rather than on the HTTP status alone.

string
Allowed values: unauthenticated

Example

{
"error": "unauthenticated"
}

no_payment_method — The team pays by card and has no payment method on file, so nothing was deployed or installed. Add a card on the team’s Billing page, then retry. team_unpaid — The team has an unpaid invoice, so it cannot install or enable an app that charges, buy a bundle, or add a site that charges. Nothing changed. Pay what is owed on the team’s Billing page, then retry.

Media typeapplication/json
object
error
required

Machine-readable error slug; switch on this value rather than on the HTTP status alone.

string
Allowed values: no_payment_method team_unpaid
message

Human-readable reason for this specific failure, intended for display — do not parse it programmatically.

string

Example

{
"error": "no_payment_method",
"message": "The Cloudflare API token is invalid or expired."
}

forbidden — The bearer credential lacks the required permission or belongs to another team, or its user lacks the organization role a developer operation needs. Update its policies, use a credential bound to the addressed team, or ask an organization owner for the role; message states the reason when one is known.

Media typeapplication/json
object
error
required

Machine-readable error slug; switch on this value rather than on the HTTP status alone.

string
Allowed values: forbidden
message

Human-readable reason for this specific failure, intended for display — do not parse it programmatically.

string

Example

{
"error": "forbidden",
"message": "The Cloudflare API token is invalid or expired."
}

not_found — The addressed resource does not exist within this team; check the id and retry.

Media typeapplication/json
object
error
required

Machine-readable error slug; switch on this value rather than on the HTTP status alone.

string
Allowed values: not_found

Example

{
"error": "not_found"
}

version_not_ready — The addressed version is still a draft; finalize the push, then retry. install_changed — The app installation was removed or replaced while this request was being prepared. Retry to check the current installation and its charges. version_not_approved — The site got the app from the app store, so it can run only approved versions. Name another version, or omit the version to get the newest approved one. version_below_minimum — Blotout set a minimum version for this app, and the named version is below it. Name the minimum version or a newer one, or omit the version to get the newest one the site may run. version_not_offered — Blotout has suspended this app, and the named version was published during the suspension. Name a version published before the suspension, or retry once the suspension is lifted. cap_reached — The operation would exceed a site-app capacity limit — either the installed-app count or the worker access-token environment. message names the exhausted capacity. subscription_item_limit — The team’s Stripe subscription holds the most lines it can, so no further app or bundle that charges can be added to it; message names the app or bundle. Nothing was installed or bought. Contact support. quota_reached — An object the version declares (a KV namespace, Durable Object class, Pipeline, Hyperdrive config, …) would push the site’s host Cloudflare account past its per-account ceiling. message names the object type and the account’s count; free objects or have the ceiling raised, then retry. multiple_not_allowed — The app allows only one install per site, or a version change would leave more installs than the target version allows. message states the conflict. browser_budget — A version’s browser code, or a large value its browser code reads, would push the site’s q.js past its size budget; shrink the value, or remove or shrink another app first. restore_available — This app was removed from the site recently and can still be brought back with its configuration and data. Call the restore operation to undo the removal, or repeat this call with replaceRemoved set to discard it and install a new one. binding_conflict — The target version binds a resource name that a version still running on this site binds to something else; message names both. Retrying cannot resolve it — the app must use a different binding name, or remove the binding in one version and re-add it in the next. schema_incompatible — The site has completed a contract D1 migration that the target app version does not include unchanged; select a schema-compatible version. variable_retype — The target version changes a variable’s kind or type while a version still running on this site declares the old shape; message names both. Retrying cannot resolve it — the app must use a different variable name, or remove the variable in one version and re-add it in the next. linked — The app’s storage is shared through a storage link: a child site binds this site’s objects, or this site binds a parent’s. message names the sites; change the link first, or make the change on the parent site. When a child site lacks an app the version needs, message names it; install it on that child first. approval_required — The target version requests more access than the installed one — API scopes, data buckets, or newly captured query params and cookies; message names the additions. Repeat the call with acceptCapabilities once a site manager accepts them. acknowledgement_required — The app, or an app it needs, declares necessary consent, so it runs without visitor consent; message names it. Repeat the call with acknowledgeNecessary for the app, or with the slug of the app it needs in acknowledgeNecessaryDependencies. dependency_unavailable — The app needs an app that cannot be installed on this site: it is not in the app store and you are not in its organization, it is suspended or deleted, or it has no version the site may run, or it was removed from this site recently and must be restored or installed on its own first. message names it. Nothing was installed. registry_budget — The change would grow the site’s app configuration past the size the site worker can deploy. Remove an app or an install, or shorten install labels and geo filters, then retry. message states the refusal. app_suspended — Blotout has suspended this app, so nothing ran and nothing changed. The call works again once the suspension is lifted. app_taken_down — Blotout has taken this app down for good, so nothing changed. message states what the call cannot do. Do not retry. purchase_ended — The bundle purchase that covered the app ended while the install ran, so nothing was installed. Retry: the app then installs at its own price. removal_pending — A previous uninstall of this app is torn down as part of this call, and that teardown could not finish — because a deployment for the site is already running, or Cloudflare refused a delete. Nothing was left half-removed and the site’s scheduled cleanup carries on, so retry. sites_paused — Blotout has paused this team’s sites, so nothing was deployed or installed. Every site of the team answers 410 until the pause ends, and uninstalls, switches and settings changes are still recorded. A payment does not end the pause; contact Blotout. deploys_blocked — Blotout has blocked this team’s deploys, so nothing was deployed or installed. The team’s live sites keep running, and uninstalls, switches and settings changes still roll out. A payment does not lift the block; contact Blotout. charge_approval_required — The call charges the team and did not approve that charge, or the charge went up, or its free days went down, after it was shown. charge holds the current charge and message states it. For an app, appSlug names the app the charge belongs to: once a person allowed to purchase apps accepts the charge and the developer’s terms, repeat the call with { "<appSlug>": <charge> } in approvedCharges, keeping the other entries. For a bundle, repeat the call with charge as approvedCharge. Nothing was installed or bought. dependencies_required — The app needs other apps the site does not have yet; plan lists every app it needs, at any depth, and whether the site has it. Nothing was installed. Repeat the call with installDependencies to install the missing apps first, then the app, and approve the plan’s private-function calls.

Media typeapplication/json
object
error
required

Machine-readable error slug; switch on this value rather than on the HTTP status alone.

string
Allowed values: version_not_ready install_changed version_not_approved version_below_minimum version_not_offered cap_reached subscription_item_limit quota_reached multiple_not_allowed browser_budget restore_available binding_conflict schema_incompatible variable_retype linked approval_required acknowledgement_required dependency_unavailable registry_budget app_suspended app_taken_down purchase_ended removal_pending sites_paused deploys_blocked charge_approval_required dependencies_required
message

Human-readable reason for this specific failure, intended for display — do not parse it programmatically.

string
plan

With dependencies_required: what a confirmed call installs and the grants it approves.

object
dependencies
required

Every app the app needs, at any depth, in install order (each before the apps that need it).

Array<object>

One app the installed app needs, directly or through another app it needs.

object
slug
required

Slug of the app that is needed.

string
name
required

Display name of the app that is needed.

string
reason
required

Why it is needed, from the app that needs it, or else the description of the needed app; null when neither gives one.

string | null
neededBy
required

Slug of the app that needs it: the app being installed or another app in the plan.

string
state
required

Whether the site has the app: missing (installed by a confirmed call), installed, or installed-disabled (on the site but switched off).

string
Allowed values: missing installed installed-disabled
version

The version a confirmed call installs; only for a missing app.

string
necessary

Whether the app runs without visitor consent, so its slug must be in acknowledgeNecessaryDependencies; only for a missing app.

boolean
legal

The developer’s legal documents, accepted by approving its charge; only for a missing app.

object
termsUrl
required

The developer’s terms for this app.

string
privacyUrl
required

The developer’s privacy policy for this app.

string
charge

What installing it charges, sent back in approvedCharges under this app’s slug to approve it; null when it charges nothing; only for a missing app.

object
upToCents
required

The most the install can charge one site in a month, in US cents: the base price plus the usage cap.

integer
trialDays
required

The free days before the first charge; 0 when the app has no trial.

integer
rpcCalls
required

The calls the app and the missing apps make to private functions of apps the site would run. A confirmed call approves their grants. Calls to public functions need no grant and are not listed.

Array<object>

Calls from one app to the private functions of another, which need a grant on the site.

object
callerAppSlug
required

Slug of the calling app: the app being installed or a missing app.

string
providerAppSlug
required

Slug of the app whose functions it calls.

string
functions
required

The private functions it calls, sorted by name.

Array<string>
charge

With charge_approval_required: the current charge to send back under appSlug in approvedCharges.

object
upToCents
required

The most the install can charge one site in a month, in US cents: the base price plus the usage cap.

integer
trialDays
required

The free days before the first charge; 0 when the app has no trial.

integer
appSlug

With charge_approval_required: the app the charge belongs to.

string

Example

{
"error": "version_not_ready",
"message": "The Cloudflare API token is invalid or expired.",
"plan": {
"dependencies": [
{
"slug": "consent-manager",
"name": "Consent Manager",
"reason": "Reads the visitor’s consent state",
"neededBy": "facebook-capi",
"state": "missing",
"version": "1.2.0",
"necessary": false,
"legal": {
"termsUrl": "https://example.com/legal/terms",
"privacyUrl": "https://example.com/legal/privacy"
},
"charge": {
"upToCents": 4900,
"trialDays": 14
}
}
],
"rpcCalls": [
{
"callerAppSlug": "facebook-capi",
"providerAppSlug": "consent-manager",
"functions": [
"getConsent"
]
}
]
},
"charge": {
"upToCents": 4900,
"trialDays": 14
},
"appSlug": "pixel-sync"
}

internal — Unexpected server error. Nothing is guaranteed to have been persisted; idempotent operations are safe to retry.

Media typeapplication/json
object
error
required

Machine-readable error slug; switch on this value rather than on the HTTP status alone.

string
Allowed values: internal

Example

{
"error": "internal"
}