Skip to main content

Monetization

A monetized task deducts credits from the user's balance whenever it runs. This page covers how to:

  • Enable credit charging for a task
  • Specify the amount of tool usage to charge for, including partial confirmations
  • Propagate credit charging to sub-tasks invoked via flow.executeTask() / flow.streamTask()

Monetization Configs​

To enable monetization, define monetization in your config as shown below:

tasks:
- name: example-task
publish: true
monetization:
toolId: myToolId # ID used for credit tracking; contact the Monetization team to obtain one
enableSubTaskCredits: false # optional, default false — see "Sub-Task Charging" below

Tool Usage Amount​

It is possible to specify the total amount of tool usage to be billed by providing usageAmount parameter in options (defaults to 1). The toolId can also be returned dynamically from options() — useful when the tool depends on the request (e.g. different models bill against different tools). A toolId returned from options() overrides the one in yaml.

@Executor("example-task")
export class ExampleExecutor implements TaskExecutor {

async options(exampleCommand: ExampleCommand): Promise<TaskOptions> {
const toolId = exampleCommand.model === 'model1' ?
'model1_tool_id' : 'default_toolId';

const totalUsageAmount = countTotalToolUsageAmount();

return {
monetization: {
toolId
},
usageAmount: totalUsageAmount,
}
}
}
Preflight required

For options() to be invoked, the task must have enablePreflight set to true.

usageAmount

usageAmount can be set dynamically based on your usage logic and defaults to 1 if not provided.

Ensure your project uses version 3.7.0 or higher of @picsart/pa-pluggable-workers-core to send usageAmount from options.

Minimum Balance​

Return minCredits from options() to declare the balance a user must already hold for the task to run. The balance is checked before any credits are deducted and before the task starts; below the minimum, /submit, /execute and /stream fail with a 402 (NOT_ENOUGH_AVAILABLE_CREDITS) and nothing is charged. A user allowed to go into overdraft always clears the minimum.

@Executor("example-task")
export class ExampleExecutor implements TaskExecutor {

async options(exampleCommand: ExampleCommand): Promise<TaskOptions> {
return {
monetization: { toolId: 'example_tool_id' },
// Refuse to start unless the user can cover a full-length generation.
minCredits: 50,
}
}
}

Use it when the final cost is only known after the task runs — the up-front deduction is then too small to turn away a user who cannot pay for the whole job. It is a check, not a reservation: the minimum is not held, so a balance that clears it can still be spent by something else while the task runs.

The minimum is enforced wherever the task runs: for a workflow called directly, before its deduction; for a sub-task invoked with flow.executeTask() / flow.streamTask(), by the worker running it, before its own deduction. It is skipped for a call a caller marked chargeCredits: false, which costs its user nothing anyway.

/options does not fail on a shortfall — it quotes. The response reports the answer as hasEnoughCredits next to the workflow's own minCredits, so a client can show the user what they are short of before they commit:

{
"monetization": { "toolId": "example_tool_id" },
"credits": 12,
"minCredits": 50,
"hasEnoughCredits": false
}

The field is absent when the workflow declares no minimum — absent never means the balance failed.

Preflight required

Like usageAmount, minCredits only takes effect when the task has enablePreflight: true — without it, options() never runs.

Whole-Credit Billing​

Return roundCredits: true from options() to bill in whole credits instead of fractional ones. Amounts round up — a price of 2.5 credits is quoted and charged as 3.

async options(exampleCommand: ExampleCommand): Promise<TaskOptions> {
return {
monetization: { toolId: 'example_tool_id' },
roundCredits: true,
}
}

It covers the /options quote, the task's own deduction, and every sub-task below it — a sub-task can't turn it off. A task without a toolId can still set it to round the sub-tasks it calls. Post-charges and partialUsageAmount settlements stay fractional.

Quote-Only Pricing​

Return chargeCredits: false from options() to report the task's price without deducting it — the price still shows up in /options and in the usage, but the user isn't charged for the task itself. This is not the same as the per-call chargeCredits option, which is passed by a parent and turns off charging for a whole sub-tree.

async options(exampleCommand: ExampleCommand): Promise<TaskOptions> {
return {
monetization: { toolId: 'example_tool_id' },
chargeCredits: false,
}
}

Post-Charging​

When the billable quantity is only known after the work is done (e.g. LLM token counts), charge it from processTask() with flow.charge():

const [input, output] = await flow.charge([
{ toolId: 'llm_input_tokens', amount: usage.inputTokens },
{ toolId: 'llm_output_tokens', amount: usage.outputTokens },
]);

Each request charges pricePerUnit × amount against its own toolId and records the matching cost units. flow.charge() returns one result per request, in order.

FieldDescription
toolIdPricing id to charge against.
amountThe quantity used. 0 is a no-op.
emitMetricAlso emit the cost-tracking metric. Default false.
trackCostRecord the cost units for this charge. Default true; set false only when the same units are already reported via flow.costTracking().add(), so they aren't counted twice. It doesn't change what is charged.

Post-charging is best-effort: it charges what the balance covers and never fails the task over an insufficient balance — check charged / partial on each result if you need to know. Pair it with minCredits to turn away users who can't afford the job up front. It throws when there is no user to bill or the toolId has no pricing, and it can't be combined with flow.defer().

Partial Confirmation​

When submitting results, include partialUsageAmount to indicate the exact amount of tool usage to be charged. This lets you confirm only the number of times the tool was used successfully, rather than charging for all attempts.

@Executor("example-task")
export class ExampleExecutor implements TaskExecutor {
async processTask(taskCommand: ExampleCommand): Promise<TaskResult> {
const successfulUsages = countSuccessfulGenerations();

return {
result: { exampleKey : 'exampleValue' },
meta: {
partialUsageAmount: successfulUsages,
}
};
}
}

For example, if usageAmount is set to 3 but only 2 out of 3 tool executions succeed, you can set partialUsageAmount to 2 to charge for just those successful uses. If partialUsageAmount is omitted, the full usageAmount is charged.

note

Ensure your project uses version 4.2.0 or higher of @picsart/pa-pluggable-workers-core to send meta in response.

warning

The lesser value between partialUsageAmount and usageAmount will be applied. Please ensure to send accurate values for both to avoid unexpected outcomes.

warning

If the user’s balance can’t cover the number of uses specified by usageAmount, the /submit or /execute request will fail with a 402 error.

Failure Behavior​

Credits are pre-deducted and only finalized once the task completes successfully. If the task fails the deduction is rolled back automatically and the user is not charged.

Sub-Task Charging​

By default, when a workflow invokes another task with flow.executeTask() or flow.streamTask(), the sub-task is not charged for credits — even if the sub-task has its own monetization.toolId configured. Set enableSubTaskCredits: true on the parent task to charge for sub-tasks it invokes (each charged using its own toolId).

tasks:
# Parent that charges itself AND its sub-tasks
- name: billable-parent
publish: true
monetization:
toolId: parent_tool_id
enableSubTaskCredits: true

# Pure orchestrator: not billed itself, but its sub-tasks are charged
- name: orchestrator-parent
publish: true
monetization:
enableSubTaskCredits: true

toolId and enableSubTaskCredits are independent. Omit toolId if the parent is just an orchestrator that shouldn't be billed itself; keep it if the parent should also charge for its own work.

Per-Call Override​

Pass chargeCredits in the call options to override enableSubTaskCredits for a single call:

// Turn on credit charging for this sub-task even if enableSubTaskCredits is false
await flow.executeTask("child-task", params, { chargeCredits: true });

// Make this call free, even if enableSubTaskCredits is true
const stream = flow.streamTask("child-task", params, { chargeCredits: false });

chargeCredits: false makes the whole call free for the user: the sub-task, every task it calls in turn, and any post-charges they make. Nothing further down can turn charging back on. Costs are still tracked.

Sub-task SDK version

Sub-tasks invoked with credit management enabled must use @picsart/pa-pluggable-workers-core version 7.2.7 or higher

Deferred execution not supported

flow.defer() is not supported when sub-task credit charging is enabled. Use flow.executeTask() or flow.streamTask() instead.

Rollback Behavior​

Credit rollbacks for charged sub-tasks follow the outcome of the parent task:

  • Parent task fails — by default both the parent and its sub-tasks are rolled back, and no credits are charged.
  • Parent task completes successfully — only the sub-tasks that failed are rolled back; successful sub-tasks (and the parent) remain charged.
Charging successful sub-tasks on parent failure​

Some workflows deliver a user-visible result in a sub-task (e.g. an image saved to the user's Drive) before a later step fails the parent. Rolling those sub-tasks back would give the user the output for free. Set chargeSubtasksOnParentFailure: true to keep charging them even when the parent fails:

tasks:
- name: billable-parent
publish: true
monetization:
toolId: parent_tool_id
enableSubTaskCredits: true # one way to charge sub-tasks (see also per-call chargeCredits)
chargeSubtasksOnParentFailure: true # optional, default false

This flag only affects sub-tasks that are actually charged — it does not, on its own, enable sub-task charging. Charging can come from either enableSubTaskCredits: true or the per-call chargeCredits: true override.

When the parent fails with this enabled:

  • The parent deduction is still rolled back.
  • Sub-tasks that succeeded are charged; sub-tasks that failed on their own are still rolled back.

When chargeSubtasksOnParentFailure is false or omitted, the default behavior applies and a parent failure rolls back everything.