Pay a reward from the campaign pool
const url = 'https://api.harperharbor.com/open/v1/campaign-pool/payouts';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"key":"creator-season-1-card-100001","accountNo":10001,"amount":10000,"campaignRef":"creator-season-1","contentRef":"card-123"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://api.harperharbor.com/open/v1/campaign-pool/payouts \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "key": "creator-season-1-card-100001", "accountNo": 10001, "amount": 10000, "campaignRef": "creator-season-1", "contentRef": "card-123" }'Credits a player’s account with amount credits taken from the campaign pool of the app this token belongs to. Only the app’s owners and admins can pay. accountNo is the player’s public account number. Reuse the same key when retrying; a key that already paid returns the original payout with replayed: true. Reusing a key with a different accountNo, amount, campaignRef, contentRef or validityDays returns idempotency_conflict. Usage scope comes from the platform’s settings for this app; expiry is chosen per payout with validityDays, and leaving it out means the credits never expire.
Authorizations
Section titled “Authorizations”Request Bodyrequired
Section titled “Request Bodyrequired”object
Your own payout identifier: 1 to 128 letters, digits, _ or -. A key pays at most once.
Public account number of the player to credit.
Credits to pay. A positive integer not above maxPayout.
Optional campaign label for your own records, up to 128 characters.
Optional reference to the content the reward is for, up to 128 characters.
Optional. Days until the paid credits expire, from 1 to 3650. Leave it out for credits that never expire. Re-sending a key must carry the same value.
Example
{ "key": "creator-season-1-card-100001", "accountNo": 10001, "amount": 10000, "campaignRef": "creator-season-1", "contentRef": "card-123"}Responses
Section titled “Responses”The payout. replayed is true when key had already paid.
object
The payout key.
Public account number of the player who was credited.
Credits paid.
Identifier of the pool entry that recorded the payout.
When the credits were paid.
When the paid credits expire. null when they do not.
What the paid credits can be spent on: all or agent_only.
True when this response repeats an earlier payout of the same key; no new credits were paid.
Examplegenerated
{ "key": "example", "accountNo": 1, "amount": 1, "entryId": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "paidAt": "2026-04-15T12:00:00Z", "expiresAt": "2026-04-15T12:00:00Z", "usageScope": "example", "replayed": true}invalid_request — key is not 1 to 128 letters, digits, _ or -; accountNo or amount is not a positive integer; or a reference is longer than 128 characters.
object
Error code (snake_case) or, for legacy handlers, a free-text/localized message.
Human-readable sentence. Present only on middleware errors and /me, /me/wallet.
Whether retrying may succeed. Present only on middleware errors, /me, /me/wallet and service/share routes.
Only on a first-turn context_capacity_exceeded: the smallest context tier that fits the whole card with room to talk. Save it as context in player settings and resend. Absent when no tier fits.
The token budget of required_context_tier for the selected model, for display.
Only on a first-turn context_capacity_exceeded: whether saving trimConstantLore=true lets the card play at the current tier. Absent on mid-conversation capacity errors.
Only on insufficient_credits: the credits this turn needs before it can start. Absent when the refusal came without an estimate.
Only on insufficient_credits: the credits the wallet could set aside for it.
Examplegenerated
{ "error": "example", "message": "example", "retryable": true, "required_context_tier": 1, "required_context_tokens": 1, "constant_lore_trim_fits": true, "credits_needed": 1, "credits_available": 1}unauthorized — missing or invalid bearer.
object
Error code (snake_case) or, for legacy handlers, a free-text/localized message.
Human-readable sentence. Present only on middleware errors and /me, /me/wallet.
Whether retrying may succeed. Present only on middleware errors, /me, /me/wallet and service/share routes.
Only on a first-turn context_capacity_exceeded: the smallest context tier that fits the whole card with room to talk. Save it as context in player settings and resend. Absent when no tier fits.
The token budget of required_context_tier for the selected model, for display.
Only on a first-turn context_capacity_exceeded: whether saving trimConstantLore=true lets the card play at the current tier. Absent on mid-conversation capacity errors.
Only on insufficient_credits: the credits this turn needs before it can start. Absent when the refusal came without an estimate.
Only on insufficient_credits: the credits the wallet could set aside for it.
Examplegenerated
{ "error": "example", "message": "example", "retryable": true, "required_context_tier": 1, "required_context_tokens": 1, "constant_lore_trim_fits": true, "credits_needed": 1, "credits_available": 1}forbidden — the caller is not an owner or admin of this app.
object
Error code (snake_case) or, for legacy handlers, a free-text/localized message.
Human-readable sentence. Present only on middleware errors and /me, /me/wallet.
Whether retrying may succeed. Present only on middleware errors, /me, /me/wallet and service/share routes.
Only on a first-turn context_capacity_exceeded: the smallest context tier that fits the whole card with room to talk. Save it as context in player settings and resend. Absent when no tier fits.
The token budget of required_context_tier for the selected model, for display.
Only on a first-turn context_capacity_exceeded: whether saving trimConstantLore=true lets the card play at the current tier. Absent on mid-conversation capacity errors.
Only on insufficient_credits: the credits this turn needs before it can start. Absent when the refusal came without an estimate.
Only on insufficient_credits: the credits the wallet could set aside for it.
Examplegenerated
{ "error": "example", "message": "example", "retryable": true, "required_context_tier": 1, "required_context_tokens": 1, "constant_lore_trim_fits": true, "credits_needed": 1, "credits_available": 1}account_not_found — no active account has this accountNo.
object
Error code (snake_case) or, for legacy handlers, a free-text/localized message.
Human-readable sentence. Present only on middleware errors and /me, /me/wallet.
Whether retrying may succeed. Present only on middleware errors, /me, /me/wallet and service/share routes.
Only on a first-turn context_capacity_exceeded: the smallest context tier that fits the whole card with room to talk. Save it as context in player settings and resend. Absent when no tier fits.
The token budget of required_context_tier for the selected model, for display.
Only on a first-turn context_capacity_exceeded: whether saving trimConstantLore=true lets the card play at the current tier. Absent on mid-conversation capacity errors.
Only on insufficient_credits: the credits this turn needs before it can start. Absent when the refusal came without an estimate.
Only on insufficient_credits: the credits the wallet could set aside for it.
Examplegenerated
{ "error": "example", "message": "example", "retryable": true, "required_context_tier": 1, "required_context_tokens": 1, "constant_lore_trim_fits": true, "credits_needed": 1, "credits_available": 1}pool_not_configured — the platform has not set the rules for this app yet; pool_disabled — payouts are paused for this app; payout_over_limit — amount is above the per-payout limit maxPayout; pool_insufficient — the pool cannot cover this payout, so retry with the same key after it is topped up; idempotency_conflict — this key was already used for a different recipient, amount or reference, so use a new key.
object
Error code (snake_case) or, for legacy handlers, a free-text/localized message.
Human-readable sentence. Present only on middleware errors and /me, /me/wallet.
Whether retrying may succeed. Present only on middleware errors, /me, /me/wallet and service/share routes.
Only on a first-turn context_capacity_exceeded: the smallest context tier that fits the whole card with room to talk. Save it as context in player settings and resend. Absent when no tier fits.
The token budget of required_context_tier for the selected model, for display.
Only on a first-turn context_capacity_exceeded: whether saving trimConstantLore=true lets the card play at the current tier. Absent on mid-conversation capacity errors.
Only on insufficient_credits: the credits this turn needs before it can start. Absent when the refusal came without an estimate.
Only on insufficient_credits: the credits the wallet could set aside for it.
Examplegenerated
{ "error": "example", "message": "example", "retryable": true, "required_context_tier": 1, "required_context_tokens": 1, "constant_lore_trim_fits": true, "credits_needed": 1, "credits_available": 1}temporarily_unavailable — retry later.
object
Error code (snake_case) or, for legacy handlers, a free-text/localized message.
Human-readable sentence. Present only on middleware errors and /me, /me/wallet.
Whether retrying may succeed. Present only on middleware errors, /me, /me/wallet and service/share routes.
Only on a first-turn context_capacity_exceeded: the smallest context tier that fits the whole card with room to talk. Save it as context in player settings and resend. Absent when no tier fits.
The token budget of required_context_tier for the selected model, for display.
Only on a first-turn context_capacity_exceeded: whether saving trimConstantLore=true lets the card play at the current tier. Absent on mid-conversation capacity errors.
Only on insufficient_credits: the credits this turn needs before it can start. Absent when the refusal came without an estimate.
Only on insufficient_credits: the credits the wallet could set aside for it.
Examplegenerated
{ "error": "example", "message": "example", "retryable": true, "required_context_tier": 1, "required_context_tokens": 1, "constant_lore_trim_fits": true, "credits_needed": 1, "credits_available": 1}