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.
| Grant | The Key may | Reach |
|---|---|---|
campaign:read | List and read Campaigns, their versions, and templates. | A read-only Key reaches every Campaign in its organization. |
campaign:author | Validate 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:launch | Propose 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
| Purpose | API operation | Required grant |
|---|---|---|
| Find a template a Campaign File may reference | GET /api/v1/campaignapi/templates, template, catalog | campaign:read |
| List or inspect Campaigns and stored versions | GET /api/v1/campaignapi/campaigns, campaign, versions, version, versiondiff | campaign:read |
| Check a candidate without changing state | POST /api/v1/campaignapi/candidatevalidate, candidateplan | campaign:author |
| Create a Campaign, or apply a change to a draft | POST /api/v1/campaignapi/candidateapply | campaign:author |
| Read the receipt of an earlier apply | GET /api/v1/campaignapi/actionreceipt | campaign:author |
| Propose a launch, and read the proposal back | POST /api/v1/campaignapi/launchpropose, GET /api/v1/campaignapi/launchproposal | campaign:launch |
| Confirm a proposed launch | POST /api/v1/campaignapi/launchconfirm | A signed-in person; a Key is refused |
| Execute a confirmed launch | POST /api/v1/campaignapi/launch | campaign: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.
| Section | Holds |
|---|---|
spec_version | The string "1.0". |
campaign | The Campaign's name. The server assigns campaign_id, owner, and created_at. |
eligibility | Who may contribute: gates, kyc_tier (none, light, or full), and reputation. |
workflow | The steps, as nodes, and the order between them, as next. |
reward_policy | The base reward, its factors, and its caps. |
quotas | budget_usd, the total the Campaign may spend, and per_node limits. |
data_contract | scarcity_strata and lineage. |
audit | A 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}))
PYTo 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.jsondata.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'],
}))
PYSend 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| File | Check | Meaning |
|---|---|---|
validation.json | data.valid | true when every check passed. |
validation.json | data.checks | One entry each for schema, compile, and dependencies, with ran, passed, and violations. A violation names a path, a stable code, and a message. |
validation.json | data.candidate_hash | The identity of the document you sent. |
plan-result.json | data.applicable | true when the candidate is valid and, for a change, the base version is still a draft. |
plan-result.json | data.base | The version the plan compared against; null for a new Campaign. |
plan-result.json | data.changes, data.change_count | Each change as op (add, remove, or replace), path, and before or after. |
plan-result.json | data.findings | The 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.jsonA 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:
| Step | Effect |
|---|---|
| Allocate | A draft Campaign in the Key's organization, owned by the person who created the Key, with the budget from quotas.budget_usd. |
| Provision | The Campaign's Pipeline, holding only an entry gate. A launch writes your draft's steps into it. |
| Store | The document, as version 1 in the draft state. |
| Grant | The 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 field | Meaning |
|---|---|
replayed | true 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_id | The server's identifier for this write. |
action | campaignfile.apply for a create or an update. |
status | succeeded, in_progress, failed, or uncertain. |
request_key, request_hash | Your key, and the server's hash of the whole request. |
campaign_id, result | The Campaign written, and what the write produced. |
steps, uncertain_reason | Present while a write is unresolved or failed: how far it got, and why the outcome is unknown. |
in_progress_since | Present 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 see | What happened | What to do now |
|---|---|---|
| No response at all | The 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: succeeded | The first attempt succeeded; nothing was written again. | Use this receipt. |
409 ACTION_IN_FLIGHT, or a receipt with status: in_progress | A 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: failed | The earlier attempt failed and wrote nothing. | Fix the cause and send again with a new key. |
409 REQUEST_KEY_REUSED | This 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: uncertain | A 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:
| Precondition | Where 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.jsonA 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.jsonThe 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 see | What happened | What to do now |
|---|---|---|
A receipt with status: succeeded | The 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 all | The 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_CONFIRMED | No person has confirmed yet. | Ask the person to confirm, then send the same body again. |
PROPOSAL_EXPIRED or PROPOSAL_REVOKED | The proposal is closed. | Propose again and have the new proposal confirmed. |
PROPOSAL_STALE | The 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_EXCEEDED | The 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_MALFORMED | The 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_UNAVAILABLE | The 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_EXECUTED | This proposal already ran, or is running. | Read the proposal and the Campaign's status. Launch nothing again. |
409 ACTION_UNCERTAIN | The 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.
errorCodeName | What happened | What to do now |
|---|---|---|
KEY_CAPABILITY_MISSING | The Key lacks the grant this operation needs. | Create a Key with that grant. |
KEY_HAS_NO_CAMPAIGN_ALLOWLIST | The 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_PERSON | The Key records nobody who created it, so a Campaign it made would have no owner. | Create a replacement Key. |
CAMPAIGN_NOT_FOUND | The Campaign does not exist, or this Key cannot see it. | Check the ID against campaigns and managed_campaign_ids. |
CANDIDATE_MALFORMED | The candidate is not a JSON object shaped like a Campaign File. | Fix the document's structure. |
CANDIDATE_TOO_LARGE | The 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_INVALID | The 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_REQUIRED | An update did not pin its base. | Send both values from the version you read. |
EXPECTED_BASE_ON_CREATE | A create named a campaign_id or an expected base. | Remove them. |
REQUEST_KEY_REQUIRED | A write arrived with no request_key, or a blank one. | Choose a key and send it. |
CAMPAIGN_HAS_NO_VERSION | An update named a Campaign with no version to compare against. | Read the Campaign's versions before planning. |
BASE_VERSION_STALE | The base moved after you read it. | Read the new version, rebuild the candidate, and plan again. |
CONTENT_IS_HISTORICAL | This 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_CONFIGURED | The platform is missing configuration a create needs. Nothing was created. | Contact hello@humanbased.ai; a retry will not help. |
DEVELOPER_SESSION_REQUIRED | A Key called launchconfirm. | Have a person confirm in the Developer portal. |
PROPOSAL_NOT_FOUND | No such proposal, or another Key made it. | Use the Key that proposed, and the proposal_id it received. |
PROPOSAL_HASH_MISMATCH | The hash sent with a confirmation is not the stored proposal's. | Read the proposal again and confirm the hash it shows. |
SPEND_CAP_UNAVAILABLE | The 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_FOUND | No receipt matches that action_id or request_key for this Key. | Check the handle and the action it belongs to. |
ACTION_LOOKUP_REQUIRED | A receipt lookup named neither, or both, of action_id and request_key. | Send exactly one of them. |
CAMPAIGN_GRAPH_NO_WORK_ORIGIN | The graph has no task or import node through which work can enter. | Add one, save a new version, and launch again. |
CAMPAIGN_ID_REQUIRED | The operation acts on one Campaign and none was named. | Send the campaign_id. |
CAMPAIGN_ROLE_INSUFFICIENT | Your role on this Campaign is below what the operation requires. | Ask the Campaign's owner for a role that allows it. |
CAMPAIGN_VERSION_NOT_FOUND | No such version of this Campaign. | Read versions and use a listed version. |
CLI_SESSION_NOT_PERMITTED | A signed-in CLI may not perform this operation. | Use an organization API Key for it; signing in again does not change this. |
CURSOR_INVALID | The cursor is not one this endpoint issued. | Start again without a cursor and follow the ones it returns. |
KEY_PRINCIPAL_INCOMPLETE | The 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_RANGE | limit is outside the range this endpoint accepts. | Use a value in range, such as 1–200 for campaigns. |
MODE_UNSUPPORTED | The mode is not one this operation offers. | Use a mode the operation lists. |
OBSERVATION_OWNER_DISAGREES | This 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_UNAVAILABLE | The service that owns these counts did not answer. The request was not refused. | Retry the read. |
OBSERVATION_REFUSED_BY_OWNER | The service that owns these counts refused the request without naming a reason. | Contact support with the Campaign ID and the time. |
OBSERVATION_WINDOW_INVALID | The observation window or pipeline version was refused; errorMessage quotes the value. | Correct that value and read again. |
ORG_ID_REQUIRED | The caller resolved to no organization, so there is nothing to read within. | Use a Key or sign-in that belongs to an organization. |
ORG_MISMATCH | This 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_FOUND | That pipeline version does not belong to this Campaign's pipeline. | Use a version from this Campaign. |
PORTAL_SESSION_NOT_PERMITTED | A 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_MALFORMED | The 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_INVALID | The template was checked and something in it is wrong. | Fix each path-addressed finding, the same ones templatevalidate returns, and send again. |
TEMPLATE_KIND_IMMUTABLE | A template's kind is fixed when it is created. | Create a new template instead. |
TEMPLATE_MALFORMED | The body is not a template document at all. | Fix the request's structure. |
TEMPLATE_NOT_EDITABLE | This organization can read the template but not change it. | Adopt it into your organization, then edit the copy. |
TEMPLATE_NOT_FOUND | No such template, or none this organization may use. | Check the template ID. |
TEMPLATE_STALE | The 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_SPENT | A 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_UNAVAILABLE | A 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.