Skip to content
Docs
Download Markdown
In this article

Campaign API

Create, change, and launch a Campaign from a program, under an organization's explicit grant.

Download Markdown

The CampaignA developer's data-production project and its settings API lets a program read a Campaign's definition, check a candidate change, create a Campaign or apply a change to its draft, and run a launch that a person has confirmed. It is a separate contract from subscription data delivery. Delivered records come from the data API, and a data-delivery Key carries none of the grants below.

Hosts, credential, and grants

Use https://api.humanbased.ai with a Key issued for production, or https://api.staging.humanbased.ai with a staging Key. Every operation is under /api/v1/campaignapi/ and takes the header Authorization: Bearer <API_KEY>.

An organizationThe Developer-side unit that holds Campaigns, data access, and funds member creates the Key and chooses its Campaign grants in the portal; Create and use an API Key walks through the form. The server checks each grant on its own.

GrantThe Key mayReach
campaign:readList and read Campaigns, their versions, and templates.A read-only Key reaches every Campaign in its organization.
campaign:authorValidate and plan a candidate, create a Campaign, apply a change to a draft, and read apply receipts.The Campaigns named in the Key's managed_campaign_ids: the ones its creator could edit when the Key was created, plus any Campaign the Key creates.
campaign:launchPropose a launch, read back its own proposal, and execute it once a person has confirmed it.The same list, within the spend cap recorded on the Key.

A Campaign outside the Key's organization or outside its list answers 404 with CAMPAIGN_NOT_FOUND, the same answer as a Campaign that does not exist.

The Key form issues campaign:author and campaign:launch only to a member who is an editor or owner of at least one Campaign in the organization. Create an organization's first Campaign in the Developer portal.

Check identity and read scope

Start with a read-only identity check:

curl --silent --show-error \
  'https://api.humanbased.ai/api/v1/auth/whoami' \
  --header "Authorization: Bearer $HB_API_KEY"

Read data.org_id, data.scopes, and data.managed_campaign_ids before continuing. Keep the Key in an environment variable or secret store, never in a copied command or URL. Then list the Campaigns visible to that identity:

curl --silent --show-error \
  'https://api.humanbased.ai/api/v1/campaignapi/campaigns' \
  --header "Authorization: Bearer $HB_API_KEY"

Every response carries success, errorCode, errorMessage, and data. The Campaign list accepts optional status, limit (1–200), and opaque cursor query parameters. Read data.items; if data.has_more is true, pass data.next_cursor unchanged in the next request. An empty page alone is not the end or proof that the organization has no Campaigns. Keep identifiers as strings.

Choose the operation

PurposeAPI operationRequired grant
Find a template a Campaign File may referenceGET /api/v1/campaignapi/templates, template, catalogcampaign:read
List or inspect Campaigns and stored versionsGET /api/v1/campaignapi/campaigns, campaign, versions, version, versiondiffcampaign:read
Check a candidate without changing statePOST /api/v1/campaignapi/candidatevalidate, candidateplancampaign:author
Create a Campaign, or apply a change to a draftPOST /api/v1/campaignapi/candidateapplycampaign:author
Read the receipt of an earlier applyGET /api/v1/campaignapi/actionreceiptcampaign:author
Propose a launch, and read the proposal backPOST /api/v1/campaignapi/launchpropose, GET /api/v1/campaignapi/launchproposalcampaign:launch
Confirm a proposed launchPOST /api/v1/campaignapi/launchconfirmA signed-in person; a Key is refused
Execute a confirmed launchPOST /api/v1/campaignapi/launchcampaign:launch

Identifiers travel in the query string of a GET and in the JSON body of a POST. The CLI wraps the same operations as commands.

Write a Campaign File

A candidate is a Campaign File: one JSON object that holds the whole Campaign definition. Every write sends the complete document, not a patch.

SectionHolds
spec_versionThe string "1.0".
campaignThe Campaign's name. The server assigns campaign_id, owner, and created_at.
eligibilityWho may contribute: gates, kyc_tier (none, light, or full), and reputation.
workflowThe steps, as nodes, and the order between them, as next.
reward_policyThe base reward, its factors, and its caps.
quotasbudget_usd, the total the Campaign may spend, and per_node limits.
data_contractscarcity_strata and lineage.
auditA one-sentence intent, and versions.

All eight sections are required. The platform publishes no standalone JSON Schema for this document: candidatevalidate is the authority, and it reports each problem by path.

To change an existing Campaign, read its latest version back and edit that document, as the next section shows. To create one, start from the document below. It describes one contributor step behind an access gate, with each submissionThe record created when work is submitted reviewed by a person. The name, amounts, and counts are illustrations; decide your own. Take template_id from GET /api/v1/campaignapi/templates.

{
  "spec_version": "1.0",
  "campaign": { "name": "Returns triage, round 1" },
  "eligibility": { "gates": [], "kyc_tier": "none", "reputation": {} },
  "workflow": {
    "nodes": [
      { "key": "gate", "node_key": "access_gate", "params": { "spec_version": 1 } },
      {
        "key": "collect",
        "node_key": "human_supply",
        "params": {
          "form_template_ref": { "template_id": "784210002001" },
          "task": { "totalCap": 200, "perUserQuota": 20 },
          "reward": { "asset_code": "USDC", "amount": 0.5 }
        }
      },
      { "key": "accept", "node_key": "data_accept", "params": { "acceptance": { "mode": "manual" } } }
    ],
    "next": [
      { "from": "gate", "to": "collect" },
      { "from": "collect", "to": "accept" },
      { "from": "accept", "to": "done" }
    ]
  },
  "reward_policy": {
    "base": { "asset_code": "USDC", "amount": 0.5 },
    "factors": [],
    "caps": {}
  },
  "quotas": { "per_node": {}, "budget_usd": 100 },
  "data_contract": { "scarcity_strata": [], "lineage": {} },
  "audit": {
    "intent": "Collect triage decisions for returned-goods cases.",
    "versions": []
  }
}

In the collect step, task.totalCap is how many submissions the step takes from all contributors together, and task.perUserQuota is how many one contributor may make. Both count tasksA specific item a contributor claims or enters in progress and submissions waiting for review. A submission that is rejected or found to be a duplicate stops counting, which frees its place. reward.amount is what one accepted submission pays.

In the accept step, acceptance.mode set to manual holds every submission until a person reviews it. Without that setting the step accepts each submission when it arrives.

hb campaign init --template writes the same sections and steps, including task and acceptance, with a <DECIDE: …> placeholder in place of each value you must decide.

Validate and plan a candidate

Both operations read; neither writes. Save the document above as candidate.json in a private working directory, then build the two request bodies for a new Campaign:

python3 - <<'PY'
import json
from pathlib import Path

candidate = json.loads(Path('candidate.json').read_text())
Path('validate.json').write_text(json.dumps({'candidate': candidate}))
Path('plan.json').write_text(json.dumps({'mode': 'create', 'candidate': candidate}))
PY

To plan a change instead, set HB_CAMPAIGN_ID to a Campaign the Key may edit and read its latest version:

curl --fail-with-body --silent --show-error --get \
  'https://api.humanbased.ai/api/v1/campaignapi/version' \
  --header "Authorization: Bearer $HB_API_KEY" \
  --data-urlencode "campaign_id=$HB_CAMPAIGN_ID" \
  --output version.json

data.version.version_number and data.version.spec_hash identify the base. data.campaign_file is the complete stored document, and data.server_assigned_fields names the keys under campaign that a candidate must leave out. This illustrative edit changes only the name:

python3 - <<'PY'
import json
from pathlib import Path

response = json.loads(Path('version.json').read_text())
assert response['success'] and response['data']['campaign_file']
data = response['data']
candidate = data['campaign_file']
for field in data.get('server_assigned_fields', []):
    candidate['campaign'].pop(field, None)
candidate['campaign']['name'] = 'Reviewed example name'
Path('validate.json').write_text(json.dumps({'candidate': candidate}))
Path('plan.json').write_text(json.dumps({
    'mode': 'update', 'campaign_id': data['campaign_id'],
    'candidate': candidate,
    'expected_version_number': data['version']['version_number'],
    'expected_spec_hash': data['version']['spec_hash'],
}))
PY

Send either pair of bodies the same way:

curl --fail-with-body --silent --show-error \
  'https://api.humanbased.ai/api/v1/campaignapi/candidatevalidate' \
  --header "Authorization: Bearer $HB_API_KEY" \
  --header 'Content-Type: application/json' --data-binary @validate.json \
  --output validation.json
curl --fail-with-body --silent --show-error \
  'https://api.humanbased.ai/api/v1/campaignapi/candidateplan' \
  --header "Authorization: Bearer $HB_API_KEY" \
  --header 'Content-Type: application/json' --data-binary @plan.json \
  --output plan-result.json
FileCheckMeaning
validation.jsondata.validtrue when every check passed.
validation.jsondata.checksOne entry each for schema, compile, and dependencies, with ran, passed, and violations. A violation names a path, a stable code, and a message.
validation.jsondata.candidate_hashThe identity of the document you sent.
plan-result.jsondata.applicabletrue when the candidate is valid and, for a change, the base version is still a draft.
plan-result.jsondata.baseThe version the plan compared against; null for a new Campaign.
plan-result.jsondata.changes, data.change_countEach change as op (add, remove, or replace), path, and before or after.
plan-result.jsondata.findingsThe same validation result, repeated for this plan.

A plan with applicable: false is not ready to apply. If the base version or hash has moved, the plan is refused with BASE_VERSION_STALE: read the new version and review a new candidate and plan.

Create a Campaign

After the accountable person approves the document, add a request_key to the create plan body and send it to candidateapply. Choose the key yourself: a stable string of up to 200 characters that names this one write.

python3 - <<'PY'
import json
from pathlib import Path

body = json.loads(Path('plan.json').read_text())
assert body['mode'] == 'create'
body['request_key'] = 'create-returns-triage-2026-10-10'
Path('apply.json').write_text(json.dumps(body))
PY
curl --fail-with-body --silent --show-error \
  'https://api.humanbased.ai/api/v1/campaignapi/candidateapply' \
  --header "Authorization: Bearer $HB_API_KEY" \
  --header 'Content-Type: application/json' --data-binary @apply.json \
  --output receipt.json

A create body holds mode, candidate, and request_key. It names no campaign_id and no expected base.

One create performs these steps in order, under the one request_key:

StepEffect
AllocateA draft Campaign in the Key's organization, owned by the person who created the Key, with the budget from quotas.budget_usd.
ProvisionThe Campaign's Pipeline, holding only an entry gate. A launch writes your draft's steps into it.
StoreThe document, as version 1 in the draft state.
GrantThe new Campaign is added to the Key's managed_campaign_ids, so the same Key can change and launch it. granted_to_key reports whether this step landed.

The response is a receipt. Hashes are shortened in this illustration:

{
  "data": {
    "replayed": false,
    "action_id": "784210008001",
    "action": "campaignfile.apply",
    "status": "succeeded",
    "request_key": "create-returns-triage-2026-10-10",
    "request_hash": "5d0c…a917",
    "campaign_id": "784210001001",
    "result": {
      "campaign_id": "784210001001",
      "external_pipeline_id": "784210011001",
      "file_version_id": "784210007001",
      "version_number": 1,
      "persisted_file_hash": "8a41…c2f0",
      "state": "draft",
      "granted_to_key": true
    }
  },
  "success": true,
  "errorCode": 0,
  "errorMessage": "SUCCESS"
}

Keep data.campaign_id: every later operation names it. data.result.granted_to_key: false means the Campaign exists and this Key was not given it; change it in the Developer portal or with a replacement Key that names it. To confirm the create, read GET /api/v1/campaignapi/campaign?campaign_id=<campaign_id> and check that data.campaign.status is draft.

persisted_file_hash differs from the candidate_hash you validated, because the stored document includes the server-assigned fields.

Apply a change to a draft

Add a request_key to the update plan body and send it to the same operation. An update body holds mode, campaign_id, candidate, expected_version_number, expected_spec_hash, and request_key.

A successful update stores the candidate as that Campaign's newest draft version and changes nothing else. The receipt has the shape shown above; data.result.version_number is the new version, and data.result carries no granted_to_key.

Both pins are compared again at the moment of the write. If another editor saved first, the request is refused with 409 and BASE_VERSION_STALE, and nothing is written: read the new version, rebuild the candidate, and plan again.

Read the receipt and recover

A 200 from candidateapply is always a receipt with status: succeeded. Any other outcome arrives as an error response; the receipt lookup below shows the other three statuses.

Receipt fieldMeaning
replayedtrue when this response describes an earlier attempt under the same request_key, not work done now. A receipt lookup always returns true, whatever that attempt's status.
action_idThe server's identifier for this write.
actioncampaignfile.apply for a create or an update.
statussucceeded, in_progress, failed, or uncertain.
request_key, request_hashYour key, and the server's hash of the whole request.
campaign_id, resultThe Campaign written, and what the write produced.
steps, uncertain_reasonPresent while a write is unresolved or failed: how far it got, and why the outcome is unknown.
in_progress_sincePresent while status is in_progress: when the write last made progress.

The request_key is what makes a retry safe. Keep the request body and its key together, and decide by what the retry returns:

You seeWhat happenedWhat to do now
No response at allThe write may or may not have landed.Read GET /api/v1/campaignapi/actionreceipt?request_key=<your-key>, or resend the identical body with the same key.
A receipt with replayed: true and status: succeededThe first attempt succeeded; nothing was written again.Use this receipt.
409 ACTION_IN_FLIGHT, or a receipt with status: in_progressA request is still writing under this key.Wait, then read the receipt. If it is still in_progress ten minutes after in_progress_since, resend the identical body with the same key.
409 REQUEST_KEY_SPENT, or a receipt with status: failedThe earlier attempt failed and wrote nothing.Fix the cause and send again with a new key.
409 REQUEST_KEY_REUSEDThis key already named a different request.Send the original body to retry it, or choose a new key for the new request.
409 ACTION_UNCERTAIN, or a receipt with status: uncertainA write was attempted and its outcome is unknown.Stop. A person reads the receipt's steps, which name any Campaign a create already allocated, and the Campaign's current version before deciding. A new key would be a second write.

actionreceipt takes exactly one of request_key or action_id. A lookup by request_key searches Campaign File applies unless you add action=template.apply. It does not find a launch.

Launch a Campaign

A launch moves a draft Campaign to live and locks its budget from the organization's balance. A Key cannot decide that alone: the Key proposes, a person confirms the exact proposal, and the Key executes it.

Before you propose, check four things:

PreconditionWhere it is decided
The Key holds campaign:launch and the Campaign is in its managed_campaign_ids.The Key's grant, read with whoami.
The latest stored version has a step where work enters, and every contributor step has a form.The Campaign File.
The Campaign has a budget, and the organization's available balance covers it.The proposal's amount_usd, and the organization's Billing page.
The budget fits the room left under the Key's spend cap.The cap set when the Key was created.

1. Propose

curl --fail-with-body --silent --show-error \
  'https://api.humanbased.ai/api/v1/campaignapi/launchpropose' \
  --header "Authorization: Bearer $HB_API_KEY" \
  --header 'Content-Type: application/json' \
  --data "{\"campaign_id\": \"$HB_CAMPAIGN_ID\"}" \
  --output proposal.json

A proposal writes one record. No money moves and the Campaign does not change. Save the response: it is the only place the proposal_id is returned.

{
  "data": {
    "proposal_id": "784210010001",
    "campaign_id": "784210001001",
    "operation": "launch",
    "state": "proposed",
    "amount_usd": "100.0000",
    "base_version_number": 1,
    "base_spec_hash": "8a41…c2f0",
    "proposal_hash": "clp1_77be…09d3",
    "expires_at": "2026-10-10T10:30:00+00:00",
    "confirmed_by": null,
    "confirmed_at": null,
    "executed_action_id": null
  },
  "success": true,
  "errorCode": 0,
  "errorMessage": "SUCCESS"
}

amount_usd is the budget the launch would commit, as a string with four decimal places. base_version_number and base_spec_hash name the draft it would run. proposal_hash binds all of them together. expires_at is one hour after the proposal was made; confirmation and execution both have to happen before it.

A Key holds one open proposal per Campaign. A second launchpropose for the same Campaign is refused with PROPOSAL_OPEN_ALREADY until the first is executed or expires.

2. A person confirms

Send the person who owns the decision to the Developer portal's confirmation page:

https://developer.humanbased.ai/campaigns/<campaign_id>/launch/confirm?proposal=<proposal_id>

They sign in as an editor or owner of that Campaign. The page shows the Campaign, the amount, the draft version, the expiry, and the proposal hash as the server holds them. Selecting Confirm this launch records their agreement to that exact hash. It does not launch.

Read GET /api/v1/campaignapi/launchproposal?proposal_id=<proposal_id> with the Key that proposed. Continue when data.state is confirmed.

3. Execute

python3 - <<'PY'
import json
from pathlib import Path

proposal = json.loads(Path('proposal.json').read_text())['data']
Path('launch.json').write_text(json.dumps({
    'proposal_id': proposal['proposal_id'],
    'request_key': 'launch-returns-triage-2026-10-10',
}))
PY
curl --fail-with-body --silent --show-error \
  'https://api.humanbased.ai/api/v1/campaignapi/launch' \
  --header "Authorization: Bearer $HB_API_KEY" \
  --header 'Content-Type: application/json' --data-binary @launch.json \
  --output launch-receipt.json

The body holds proposal_id and a request_key you choose. Only the Key that proposed may execute. Before any money moves, the server recomputes the proposal hash from the Campaign as it is now. A budget, draft, or payment term changed after the confirmation makes the launch a different action, and it is refused with PROPOSAL_STALE.

A successful launch returns a receipt with action: campaign.launch and status: succeeded. Its data.result holds campaign_id, the Campaign's new status, the proposal_id, and confirmed_by.

Confirm or recover a launch

You seeWhat happenedWhat to do now
A receipt with status: succeededThe Campaign launched and its budget is locked.Confirm with GET /api/v1/campaignapi/campaign: data.campaign.status is live. The proposal's state is executed.
No response at allThe launch may or may not have run.Resend the identical body with the same request_key. The server replays the first receipt and launches nothing twice. actionreceipt?request_key= does not find a launch.
PROPOSAL_NOT_CONFIRMEDNo person has confirmed yet.Ask the person to confirm, then send the same body again.
PROPOSAL_EXPIRED or PROPOSAL_REVOKEDThe proposal is closed.Propose again and have the new proposal confirmed.
PROPOSAL_STALEThe Campaign changed after the confirmation.Propose again once this proposal's expires_at has passed; until then a new proposal is refused with PROPOSAL_OPEN_ALREADY.
SPEND_CAP_EXCEEDEDThe launch would exceed the Key's spend cap. No money moved.Have a person launch the Campaign in the Developer portal, or propose again with a Key whose cap has room.
CAMPAIGN_GRAPH_MALFORMEDThe draft cannot run as stored: the Pipeline refused its steps, or a form template it names is missing or empty. No money moved.Apply a corrected draft. Propose again once this proposal's expires_at has passed.
503 CAMPAIGN_GRAPH_UNAVAILABLEThe Pipeline did not take the draft. No money moved, and the Campaign is not live.Send the launch again with a new request_key. The proposal is still confirmed.
PROPOSAL_ALREADY_EXECUTEDThis proposal already ran, or is running.Read the proposal and the Campaign's status. Launch nothing again.
409 ACTION_UNCERTAINThe launch was attempted and its outcome is unknown; the budget may be locked.Stop. Read launchproposal: executed means the launch finished, executing means it is unresolved. A person checks the Campaign's status and the organization's Billing page, then contacts hello@humanbased.ai with the proposal_id. A new key would be a second launch.

Errors

A refusal has success: false, data: null, an errorCode equal to the HTTP status, and an errorCodeName. Branch on errorCodeName. errorMessage is usually prose and can be reworded. For CANDIDATE_INVALID it is an object instead, carrying the validation result:

{
  "data": null,
  "success": false,
  "errorCode": 400,
  "errorCodeName": "CANDIDATE_INVALID",
  "errorMessage": {
    "error": "CANDIDATE_INVALID",
    "message": "the candidate did not pass validation, so nothing was applied",
    "findings": {
      "valid": false,
      "checks": [
        {"check": "schema", "ran": true, "passed": false, "violations": [{"path": "…", "code": "…", "message": "…"}]}
      ],
      "candidate_hash": "…"
    }
  }
}

findings is the object candidatevalidate returns in data; the example shortens it. Check whether errorMessage is a string before you print it.

errorCodeNameWhat happenedWhat to do now
KEY_CAPABILITY_MISSINGThe Key lacks the grant this operation needs.Create a Key with that grant.
KEY_HAS_NO_CAMPAIGN_ALLOWLISTThe Key can read the organization's Campaigns and names none it may change.Create a replacement Key with the write grant.
KEY_HAS_NO_ACCOUNTABLE_PERSONThe Key records nobody who created it, so a Campaign it made would have no owner.Create a replacement Key.
CAMPAIGN_NOT_FOUNDThe Campaign does not exist, or this Key cannot see it.Check the ID against campaigns and managed_campaign_ids.
CANDIDATE_MALFORMEDThe candidate is not a JSON object shaped like a Campaign File.Fix the document's structure.
CANDIDATE_TOO_LARGEThe candidate exceeds a size limit: 1,048,576 bytes, 20,000 JSON values, 64 nesting levels, 256 workflow nodes, or 1,024 edges.Shrink the document.
CANDIDATE_INVALIDThe candidate was checked and failed. Nothing was applied, and the request_key is still unused.Read errorMessage.findings, fix each path, and send again.
EXPECTED_VERSION_REQUIRED, EXPECTED_SPEC_HASH_REQUIREDAn update did not pin its base.Send both values from the version you read.
EXPECTED_BASE_ON_CREATEA create named a campaign_id or an expected base.Remove them.
REQUEST_KEY_REQUIREDA write arrived with no request_key, or a blank one.Choose a key and send it.
CAMPAIGN_HAS_NO_VERSIONAn update named a Campaign with no version to compare against.Read the Campaign's versions before planning.
BASE_VERSION_STALEThe base moved after you read it.Read the new version, rebuild the candidate, and plan again.
CONTENT_IS_HISTORICALThis exact content is already an older version, and a newer one is current. Nothing changed.Send a document that differs from that older version.
PLATFORM_NOT_CONFIGUREDThe platform is missing configuration a create needs. Nothing was created.Contact hello@humanbased.ai; a retry will not help.
DEVELOPER_SESSION_REQUIREDA Key called launchconfirm.Have a person confirm in the Developer portal.
PROPOSAL_NOT_FOUNDNo such proposal, or another Key made it.Use the Key that proposed, and the proposal_id it received.
PROPOSAL_HASH_MISMATCHThe hash sent with a confirmation is not the stored proposal's.Read the proposal again and confirm the hash it shows.
SPEND_CAP_UNAVAILABLEThe Key may launch, and its spend cap could not be read. Nothing launched.Send the same request again; if it persists, create a replacement Key.
ACTION_NOT_FOUNDNo receipt matches that action_id or request_key for this Key.Check the handle and the action it belongs to.
ACTION_LOOKUP_REQUIREDA receipt lookup named neither, or both, of action_id and request_key.Send exactly one of them.
CAMPAIGN_GRAPH_NO_WORK_ORIGINThe graph has no task or import node through which work can enter.Add one, save a new version, and launch again.
CAMPAIGN_ID_REQUIREDThe operation acts on one Campaign and none was named.Send the campaign_id.
CAMPAIGN_ROLE_INSUFFICIENTYour role on this Campaign is below what the operation requires.Ask the Campaign's owner for a role that allows it.
CAMPAIGN_VERSION_NOT_FOUNDNo such version of this Campaign.Read versions and use a listed version.
CLI_SESSION_NOT_PERMITTEDA signed-in CLI may not perform this operation.Use an organization API Key for it; signing in again does not change this.
CURSOR_INVALIDThe cursor is not one this endpoint issued.Start again without a cursor and follow the ones it returns.
KEY_PRINCIPAL_INCOMPLETEThe gateway forwarded an incomplete Key identity. This is a platform fault, not a bad Key.Retry; contact hello@humanbased.ai if it persists.
LIMIT_OUT_OF_RANGElimit is outside the range this endpoint accepts.Use a value in range, such as 1–200 for campaigns.
MODE_UNSUPPORTEDThe mode is not one this operation offers.Use a mode the operation lists.
OBSERVATION_OWNER_DISAGREESThis Campaign names a pipeline its owner does not have. This is a provisioning fault, not a bad request.Contact support with the Campaign ID.
OBSERVATION_OWNER_UNAVAILABLEThe service that owns these counts did not answer. The request was not refused.Retry the read.
OBSERVATION_REFUSED_BY_OWNERThe service that owns these counts refused the request without naming a reason.Contact support with the Campaign ID and the time.
OBSERVATION_WINDOW_INVALIDThe observation window or pipeline version was refused; errorMessage quotes the value.Correct that value and read again.
ORG_ID_REQUIREDThe caller resolved to no organization, so there is nothing to read within.Use a Key or sign-in that belongs to an organization.
ORG_MISMATCHThis CLI session is signed in to a different organization than the request named.Sign in to that organization, or drop the explicit organization.
PIPELINE_VERSION_NOT_FOUNDThat pipeline version does not belong to this Campaign's pipeline.Use a version from this Campaign.
PORTAL_SESSION_NOT_PERMITTEDA launch through this API is bound to the organization API Key or signed-in CLI that proposed it, and a Developer portal session is neither.Launch from the Campaign page in the Developer portal, or propose and execute the launch with the same Key or CLI session.
REQUEST_MALFORMEDThe request did not parse: a field is missing, of the wrong type, or out of range.Read errorMessage, fix the field, and send again.
TEMPLATE_INVALIDThe template was checked and something in it is wrong.Fix each path-addressed finding, the same ones templatevalidate returns, and send again.
TEMPLATE_KIND_IMMUTABLEA template's kind is fixed when it is created.Create a new template instead.
TEMPLATE_MALFORMEDThe body is not a template document at all.Fix the request's structure.
TEMPLATE_NOT_EDITABLEThis organization can read the template but not change it.Adopt it into your organization, then edit the copy.
TEMPLATE_NOT_FOUNDNo such template, or none this organization may use.Check the template ID.
TEMPLATE_STALEThe template changed since you read it.Read it again, apply your change to what it says now, and send it with the new hash.
ACTION_IN_FLIGHT, ACTION_UNCERTAIN, REQUEST_KEY_REUSED, REQUEST_KEY_SPENTA write under a request_key is still running, failed, ended with an unknown outcome, or the key already named a different request.Follow the table in Read the receipt and recover. For ACTION_UNCERTAIN, stop and have a person check before anything is sent again.
PROPOSAL_NOT_CONFIRMED, PROPOSAL_EXPIRED, PROPOSAL_REVOKED, PROPOSAL_STALE, PROPOSAL_OPEN_ALREADY, PROPOSAL_ALREADY_EXECUTED, SPEND_CAP_EXCEEDED, CAMPAIGN_GRAPH_MALFORMED, CAMPAIGN_GRAPH_UNAVAILABLEA launch proposal or its execution was refused.Follow the refusal table in Launch a Campaign.

A refusal raised before the request reaches this API, such as a 401 for a missing or invalid Key, can arrive without an errorCodeName. Keep a default branch for it. The same holds for 429 Too many requests from the gateway: wait Retry-After seconds, as described under Rate limits.

To launch from the Developer portal instead, see launch and budget checks.