OPENADS INTEGRATION REFERENCE
2026-10-12 / advertiser agents

Read ./instructions.txt for the execution and approval flow.
Resolve all paths relative to this document. HTTP schemas: ./openapi.json.
This page does not connect a wallet or create an agent session.
Localhost/file links work only on the same computer. If unreachable, obtain
the documents or a reachable URL before continuing.

Primary agent setup
Use the transaction-signing path below for payment inside the agent. A host
must supply its connected wallet tool; the SDK does not install or create one.
Production agent real-payment verification covers Arc Website Ads and Base
Website Ads Center Popup (300X250). Base funding, campaign/channel ACTIVE,
owner dashboard/performance reads and retry without another payment were
verified on 2026-10-09. Other Base formats and Share/Earn agent paid activation
are outside these checks. Confirm current policy and the user's selected scope.

Optional Node SDK
Requires Node.js 20+, ethers 6 and the same EOA wallet. Direct HTTP does not
need the SDK. EIP-1271 smart wallets and x402 are not included.
Read ./sdk/example.mjs for the factory and ./sdk/openads-agent.mjs for the client.
The sample helper's creative/URL/budget are examples, not advertiser input.
Reviewed Arc/Base advertising deposit addresses: ./sdk/production-networks.mjs.
They are factory defaults, not copied from an API response. The host supplies
the actual wallet signing tool; reading this guide does not create one.

Transaction-signing path (primary):
Host integration supplies campaignAgent({getSigner, journalPath, reviewedNetworks?}):
  getSigner(chainId): advertiser-owned ethers signer with a provider.
  signTransaction must sign without broadcasting; the SDK journals then sends.
  reviewedNetworks: optional explicit override reviewed by the operator.
  journalPath: one private journal shared by processes using that same wallet.
USDC must be the official six-decimal token; Vault means the advertising deposit
contract, not a Buyback contract. Do not derive trusted addresses from API output,
token symbols or screenshots. Missing configuration requires operator setup;
do not ask the advertiser to invent addresses or paste signing keys.
The journal contains session and signed-transaction recovery data. Keep it out
of Git, private, and intact while payment is pending. Do not concurrently send
unrelated transactions from this wallet. After a crash, confirm the holder
process is stopped before removing a stale .lock file; keep the journal itself.

Browser wallet path (optional handoff):
  browserCampaignAgent({getAddress, signMessage, journalPath}).
  getAddress and signMessage call the host's actual advertiser-owned EOA tool.
  The agent prepares/reviews the ad, obtains approval, then requests a signed
  launch message. If more USDC is needed, launch_campaign returns
  COMPLETE_PAYMENT_IN_BROWSER, payment and launch_url for the SAME campaign.
  Show payment.chain_id, budget_micro, balance_applied_micro,
  additional_usdc_micro and reviewed Vault/token. Open launch_url, sign in with
  the same wallet and have the user press Continue and confirm the wallet transactions.
  Check actual gas in the wallet: the SDK execution cap does not apply to
  browser transactions. SDK payment signing/broadcasting is disabled here.
  Retry the same operation for a lost response; it returns the same intent.
  Recover a browser deposit in the existing browser flow, not by sending a
  second SDK deposit. Read the campaign after payment to confirm ACTIVE.
  If existing advertising credit covers the budget, no deposit is needed.


Inputs
Exact SDK input, plan and command-message rules: ./sdk/plan.mjs.
- operation_id: 8-100 letters, numbers, hyphens or underscores. Reuse on retries;
  changed input requires a new operation, after resolving any pending payment.
- HTTPS destination, no credentials, <=2048 characters.
- Campaign name/title: 1-100 characters. Message defaults to title; supplied
  message: 1-280. Sponsor <=100; CTA <=40.
- 1-6 distinct combinations:
    Website Ads: WEB / 320X50_TOP, 320X50, 300X250, 64X64.
    Share Ads: SHARE / SHARE_CARD.
    Earn Ads: REWARD / REWARDED_VISIT.
- SDK budgets/CPM: decimal strings, <=6 decimals. Share/Earn each >=5 USDC;
  Website budget >=selected CPM. Current policy supplies prices/availability.
  Earn's maximum allocation also comes from policy. Do not split budgets silently.
- API plan amounts: integer micro-USDC strings (5 USDC = "5000000").
- Arc: chain_id 5042; Base: 8453. Require availability in current policy.
- Actual PNG/JPEG/WebP <=3 MiB; GIF <=1 MiB. Upload complete data URLs.
  Static images: single frame, <=4096 pixels/side and <=4 million pixels.
  GIF: <=1280 pixels/side, <=1 million pixels/frame, <=120 frames and <=48 million
  total frame pixels. Server content checks and normalization must also succeed.

Direct HTTP preparation (in order)
1. GET /api/v2/auth/challenge?wallet=<wallet>. Check openads.world audience,
   wallet and expiry, then request signing of that exact message.
2. POST /api/v2/auth/session {challenge, signature}. Preserve Set-Cookie's
   openads_v2_session. x-openads-v2-session accepts the SAME raw cookie value;
   JSON access_token is a separate site-access token, not V2 API authentication.
3. GET /api/v2/campaigns/policy. Use current capabilities/available_chain_ids.
4. POST /api/v2/campaigns {name, draft_plan, idempotency_key}.
   Use openads.campaign-draft/2.1 from plan.mjs; persist returned ID/version.
5. POST /api/v2/assets {campaign_id, format, data_url}; retain validated asset_url.
6. POST /api/v2/campaigns/{id}/commands/save-draft with current target_version,
   stable idempotency_key, name and finalized draft_plan containing the assets.
7. POST /api/v2/campaigns/{id}/commands/submit-review with the latest version
   and stable idempotency_key; GET the campaign to check all selected surfaces:
   status/effective_status READY, creative_review APPROVED, no expired assets.
Preserve each returned version. Unknown responses require replay/recovery with
the same identifiers before creating another draft, upload or command.

Direct HTTP launch/payment (only after approval)
Recheck campaign/creative/version and current policy before initial review-launch.
POST /api/v2/campaigns/{id}/commands/review-launch with target_version,
idempotency_key, chain_id, allocations, persisted random bytes32 funding_ref
and signature. Build the EXACT message with buildCommandMessage in plan.mjs:
stable sorted-key JSON SHA256 binds payload, wallet, campaign, version, times
and idempotency nonce. Sign only the current approved payload.
Require ALL_READY and valid funding before additional payment; mode NONE means
no deposit. Match campaign, chain, pinned USDC/Vault, intent_id, funding_ref,
required total, remaining_shortfall_micro, status and expiry to the operation.
Use existing funds first; remaining_shortfall_micro is the additional deposit.
Use this selected-chain funding response for the payment breakdown. The account
dashboard's aggregate available balance can include other networks; do not
subtract it from a single-chain campaign budget to calculate a deposit.
If zero, do not deposit. For nonzero shortfall:
  Estimate gas and enforce approved costs.
  If allowance is insufficient, approve ONLY the shortfall to the pinned Vault
  and wait for its successful receipt.
  Call Vault.deposit(funding_ref, shortfall). Persist signed bytes/hash/nonce
  BEFORE broadcast; retries use these exact bytes. No unrelated transfer or
  infinite approval. See ./sdk/ethers-wallet.mjs and ./sdk/file-state.mjs.
  POST /api/v2/funding/submissions {intent_id, tx_hash} to register that deposit.
A 202 submission response records the transaction; server verification and
activation may still be pending. Follow next_action and read the same campaign.
The existing bounded funding reconciliation also runs after an account read
and in the scheduled recovery job. Never deposit again because activation is pending.
SDK max_transaction_gas_wei caps gasLimit x fee per transaction, not combined
transactions or Base L1 data fees. Approval and deposit may be two transactions.
Gas amounts use 18-decimal native units on both chains, unlike six-decimal
USDC payment amounts. For example, a 0.01 native-token execution-gas cap is
"10000000000000000" wei: 0.01 USDC on Arc, or 0.01 ETH on Base.
Activation checks and recovery rules are in instructions.txt.

Shared website, API and SDK error handling
Complete guide: ./errors.txt. The same website error-ux.js policy is bundled as
./sdk/error-ux.cjs and used by the API's existing error envelope. Direct HTTP
callers receive next_action, retryable and retry_after in openads.error/2.0.
HTTP Retry-After takes precedence. Respect denials; never bypass safety blocks.
Catch SDK exceptions and report error.toJSON(): error, message, next_action,
retryable, plus operation_id/campaign_id/stage/tx_hash when known. Balance errors
include exact required, available and shortfall amounts in details. _micro uses
six-decimal USDC; _native_wei uses 18-decimal native units. Do not expose the
private journal, signed transaction bytes or raw provider error objects.
The ethers adapter checks USDC before signing an approval and again before
deposit. It checks native funds against execution fees, the current Base L1
fee estimate and, on Arc deposit, the native USDC being deposited.
- INSUFFICIENT_USDC_BALANCE: show the selected chain and exact shortfall. Ask
  for funds, then resume the same operation; no transaction was signed for
  the failed step. Do not create another campaign or funding intent.
- INSUFFICIENT_GAS_BALANCE: request native gas funds on the selected chain.
- WALLET_SIGNATURE_REJECTED: obtain consent before requesting another signature.
- PAYMENT_SIMULATION_FAILED / PAYMENT_NONCE_CONFLICT / PAYMENT_FAILED:
  review the saved operation and transaction; do not automatically replace/pay.
- TRANSACTION_SUBMISSION_UNKNOWN: inspect the saved hash. Only its exact saved
  bytes may be rebroadcast. A pending result includes this as payment_error.
- HTTP 429/temporary 5xx: respect retry_after and resume the same operation with
  bounded retries. Disabled/configuration errors can be 503 and require operator
  setup instead. Definitive API rejections preserve any existing payment.
- Unexpected local/wallet errors stop with a safe error; inspect setup and
  saved records before continuing. Do not assume every failure is retryable.
retryable is false for missing funds and rejected signatures: action is needed
first. This does not prevent resuming the same operation after that action.
These checks belong to the transaction-signing adapter; direct HTTP or custom
wallet adapters must enforce the same checks. They do not guarantee unchanged
balances or fees between checking and broadcasting.

After launch: one authenticated read
SDK: get_campaign_performance({campaign_id}). Direct HTTP:
GET /api/v2/campaigns/{id} using the existing owner's session.
SDK performance fields: budget_micro, spent_micro, remaining_budget_micro,
as_of, chain_id and channels[]. Each channel includes surface, format, status,
budget_micro, spent_micro, remaining_budget_micro and the applicable result:
web_ad_views / website_clicks / qualified_share_clicks / completed_earn_visits.
Unknown facts remain null. Monetary fields are integer micro-USDC strings;
convert with exact decimal arithmetic. No chain call or payment is needed.
Use all_selected_surfaces_active along with status ACTIVE to confirm full launch.
This read does not invoke funding recovery; get_campaign still recovers a saved
deposit registration when needed. Query on request, not in a background loop.
My Campaigns selects the campaign from ?campaign=<id> after owner login, with
manual Refresh results, channel tabs and the existing management controls.
Unused funds remain advertising credit after settlement; deposits are non-refundable.

Preparation resource limits
- Advertiser requests also share a lightweight burst guard: 300 requests/IP
  per 60 seconds within one warm server instance, before authentication/DB work.
  This is best-effort protection, not a global quota; existing account/network
  limits still apply. On 429, wait for Retry-After and resume the same operation.
- Metadata: 32 KiB. Shared preparation limits: account 60/min, network 120/min;
  draft creation additionally account 30/hour. Retries consume these limits too.
- Policy reads: account 120/min, network 300/min; private/no-store.
- Managed images in unfunded DRAFTs with no funding-intent history become
  eligible for cleanup after seven idle days by default. Last upload and campaign
  update must both be old; cleanup runs in the existing bounded job, not exactly
  at the seven-day mark. Any funding-intent history protects these images,
  including expired intents, because a signed payment may arrive late.
- Follow expiry/recovery errors; never delete financial history or auto-repay.
