Sway

API and MCP

The Sway API is a JSON REST API. The brand console, the integrator console, the operator page and the @sway/mcp package all use it, and you can call it directly.

Address
Base URLhttps://api.swayoffers.com
OpenAPI 3.1 documenthttps://api.swayoffers.com/openapi.json
Protocol parametershttps://api.swayoffers.com/v1/protocol
MCP tool definitionshttps://api.swayoffers.com/v1/mcp/tools
Encryption and chain parametershttps://api.swayoffers.com/v1/encryption
Healthhttps://api.swayoffers.com/health

The OpenAPI document is generated from the same schemas the server validates against, and it marks each operation's role with x-sway-auth. These addresses serve the API's documents and parameters; the endpoint tables below list its operations.

Conventions

  • Request and response bodies are JSON. A request body may be at most 16,384 bytes, and every object rejects fields it does not declare.
  • Money is a decimal string of USDC base units: "15000000" is 15 USDC. It is never a floating-point number.
  • Public keys and wallet addresses are base58. Signatures, ciphertext and 128-byte messages are standard padded base64. Hashes are 64 lowercase hex characters.
  • IDs are opaque strings of letters, digits, _ and -.
  • Lists take cursor and limit (1 to 100) and return { items, nextCursor, sync }, where sync.status is ready, indexing or failed.
  • Browsers can call the API only from the origins the deployment allows. Server-side callers send no Origin header.
  • Every response carries Cache-Control: no-store and X-Sway-Mode.

Response envelope

A success wraps the result in data:

{
  "data": { "id": "…", "status": "confirmed" },
  "meta": { "mode": "live", "scenario": "default", "requestId": "…" }
}

A failure carries error instead, with the HTTP status set to match:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request does not match the contract.",
    "retryable": false,
    "details": { "fields": [{ "field": "amount", "message": "This field has an invalid format." }] }
  },
  "meta": { "mode": "live", "scenario": "default", "requestId": "…" }
}

details may also hold retryAfterSeconds, requiredConfiguration or resourceId. A 429 response also sets Retry-After.

CodeMeaning
VALIDATION_ERRORThe request does not match the schema or a domain rule
UNAUTHORIZED, WRONG_WALLET, FORBIDDENMissing or invalid credentials, the wrong wallet, or a missing role, scope or approval
INVALID_SIGNATURE, CHALLENGE_EXPIRED, REPLAY_DETECTEDA signature does not verify, has expired, or was already used
IDEMPOTENCY_REQUIRED, IDEMPOTENCY_CONFLICTThe Idempotency-Key header is missing, or was used for a different request
NOT_FOUND, RATE_LIMITEDNo such resource for this caller; too many requests
BOND_LOCKEDThe merchant bond cannot be released yet: the merchant is not suspended, a receipt awaits verification, or the last lock plus the dispute window has not passed
INSUFFICIENT_FUNDS, CAP_EXCEEDEDNot enough unreserved funds; a protocol or campaign cap would be exceeded
REFUND_WINDOW_CLOSED, DISPUTE_WINDOW_CLOSEDThe conversion has unlocked; the dispute window has passed
CLAIM_LOCKED, ALREADY_CLAIMEDThe conversion is still in its lock; it was already paid
TRANSACTION_SIMULATION_FAILED, TRANSACTION_FAILEDA signed transaction could not execute, or failed on chain
COMPUTATION_FAILEDAn MPC computation did not complete
CONFIGURATION_REQUIRED, DEPENDENCY_UNAVAILABLE, PROGRAM_NOT_DEPLOYEDThe service is missing a setting or a dependency for this operation
ADDRESS_BLOCKED, ORIGIN_NOT_ALLOWED, INTERNAL_ERRORThe address is refused; the browser origin is not allowed; an unexpected server error

Authentication

Each operation belongs to one role. The role decides how a request proves who sent it.

RoleProof
publicNone
merchantAuthorization: Bearer <session token> from a wallet sign-in
integratorA signed SwayIntegrator request header from the integrator authority: the wallet in the integrator console, or a key on a machine
agentA signed SwayAgent request header
claimA one-use challenge signed by the intent's claim key, inside the body
receiptThe merchant's receipt signature over a 128-byte message, inside the body. Anyone may relay it
adminA signed SwayAdmin request header from an operator key the deployment allows
oracleA signed SwayOracle request header from the affiliate oracle key the deployment allows

One-use challenges

Sign-in, registrations, claims and disputes use challenges. Request one with POST /v1/auth/challenges:

{ "purpose": "claim", "publicKey": "<signer>", "resourceIds": ["<claimable id>"], "requestHash": "<sha256 hex>" }

The response holds id, purpose, publicKey, expiresAt and the exact message to sign:

Sway authorization v1
Audience: <audience origin>
Purpose: <purpose>
Public-Key: <signer public key>
Nonce: <43 random base64url characters>
Issued-At: <ISO 8601 time>
Expires-At: <ISO 8601 time>
Request-SHA256: <requestHash>
Resources-SHA256: <SHA-256 of the canonical JSON of the sorted resource IDs>

Sign the UTF-8 bytes of message with Ed25519 and send the proof as the body field authorization:

{ "challengeId": "<id>", "publicKey": "<signer>", "signatureBase64": "<64-byte signature, base64>" }

A proof works for one operation, one payload and one set of resources, once, until expiresAt. requestHash is the SHA-256 of the canonical JSON of the request body without its authorization field, except for sign-in.

PurposeOperationSignerresourceIds
merchant_sessionPOST /v1/auth/merchant/sessionMerchant wallet, or the Config admin wallet[]. requestHash hashes {"action":"merchant_session","publicKey":"<wallet>"}
integrator_registrationPOST /v1/integratorsIntegrator authority[]
agent_registrationPOST /v1/integrator/agent-keysIntegrator authority[<integrator id>]
claim_readPOST /v1/claims/queryClaim key[]
claim_readPOST /v1/claims/{id}/queryClaim key[<claim id>]
claim_readPOST /v1/disputes/{id}/queryClaim key[<dispute id>]
claimPOST /v1/claimsClaim key[<claimable id>]
open_disputePOST /v1/disputesClaim key[<intent id>]

Merchant session

  1. Request a merchant_session challenge for the wallet and sign its message with the wallet.
  2. Send POST /v1/auth/merchant/session with an Idempotency-Key and { "authorization": <proof> }.
  3. The response carries accessToken, a 43-character token, and expiresAt. Send it as Authorization: Bearer <accessToken>.

GET /v1/auth/merchant/session reads the session and DELETE /v1/auth/merchant/session ends it. The Config admin wallet signs in the same way, without registering a merchant, and uses its session to submit the activation transactions it signs.

Signed requests: SwayAgent, SwayIntegrator, SwayAdmin and SwayOracle

Agents, integrators, the operator and the affiliate oracle sign each HTTP request. The header is:

Authorization: <Scheme> <keyId>:<timestamp>:<nonce>:<signatureBase64>
PartValue
SchemeSwayAgent, SwayIntegrator, SwayAdmin or SwayOracle
keyIdThe agent key's id for SwayAgent; the integrator's id for SwayIntegrator; the signer's base58 public key for SwayAdmin and SwayOracle
timestampUnix seconds. It must be within the deployment's maximum request age of the server's clock
nonce16 to 128 characters from A–Z a–z 0–9 _ -, used once per key
signatureBase64Ed25519 signature of the message below, 64 bytes, standard base64

The signed message is these nine lines, joined with \n:

Sway signed request v1
Audience: <audience>
Principal: <Scheme>
Key: <keyId>
Timestamp: <timestamp>
Nonce: <nonce>
Method: <HTTP method in upper case>
Path: <path and query string exactly as sent>
Body-SHA256: <SHA-256 hex of the canonical JSON body>

Audience is the value GET /v1/encryption returns as authorizationAudience. For a request without a body, such as a GET, the body hash is the hash of {}. The agent key signs SwayAgent requests, the integrator authority signs SwayIntegrator requests, and the operator's and oracle's allowed keys sign SwayAdmin and SwayOracle requests. A browser wallet signs the message's UTF-8 bytes as a message signature. Sign a fresh request, with a new nonce, for every attempt, including retries.

Canonical JSON

Hashes cover canonical JSON. Object keys are sorted by UTF-16 code unit, with no whitespace anywhere. Strings, finite numbers, booleans and null are written as standard JSON, and arrays keep their order. {"b":1,"a":[true,null]} becomes {"a":[true,null],"b":1}.

Idempotency keys

Every mutation marked in the tables below requires an Idempotency-Key header of 1 to 128 characters. The key is scoped to the caller and the operation. Repeating a request with the same key returns the original result. Reusing the key for a different request returns 409 IDEMPOTENCY_CONFLICT, and leaving it out returns 400 IDEMPOTENCY_REQUIRED. Use a fresh key for each new action and the same key for each retry of it.

Wallet transactions

Merchant mutations that change chain state, and the operator's merchant activation, suspension, bond release and shopper-side dispute resolution, return a prepared transaction: { id, kind, resourceId, requiredSigner, status, unsignedTransactionBase64, signature, expiresAt, failure, simulationOnly }. Legacy and v0 transactions are both accepted.

  1. Sign unsignedTransactionBase64 with the wallet named in requiredSigner. The prepared instructions, their order, the blockhash, the fee payer and the signer set stay unchanged. The wallet may add compute-budget instructions before them and a Lighthouse guard after them, and nothing else; no account may become newly writable.
  2. Send it one of two ways, with the wallet session of the signer:
    • Through Sway: POST /v1/transactions/{id}/submit with { "signedTransactionBase64": "…" }. Sway simulates the transaction first and answers TRANSACTION_SIMULATION_FAILED if it would not execute; prepare a fresh one after correcting balances or state.
    • From the wallet: when the wallet broadcasts it itself, as with solana:signAndSendTransaction, report it with POST /v1/transactions/{id}/report and { "signature": "<base58 transaction signature>" }. Sway fetches the transaction, applies the same checks to its instructions and signers, and tracks it without sending it again. Retry with the same signature while the RPC has not yet indexed it.
  3. Poll GET /v1/transactions/{id} until status is confirmed, failed or expired.

Endpoints

The "Key" column marks operations that require Idempotency-Key.

Public

MethodPathPurposeKey
GET/v1/encryptionMXE public key, authorizationAudience, chain clock, purchaseWindowSeconds, settlementGraceSeconds, allowedShards
POST/v1/early-accessRequest early access as a brand or agent developer
POST/v1/auth/challengesRequest a one-use challenge
POST/v1/auth/merchant/sessionExchange a signed merchant_session challenge for a sessionYes
POST/v1/integratorsRegister an integrator: name, authority public key, payout address, authority proofYes
GET/v1/cashback/ratesPublic cashback rates, filterable by shard
GET/v1/protocol/buyback{swayMint, treasuryBalance, totalUsdcSpent, totalSwayBurned}: the SWAY mint, the treasury's finalized USDC balance, and the USDC spent and SWAY burned by finalized buybacks

Merchant (brand session)

MethodPathPurposeKey
GET/v1/auth/merchant/sessionRead the current session and wallet
DELETE/v1/auth/merchant/sessionEnd the sessionYes
GET/v1/merchantMerchant profile, receipt signers, bond and status
POST/v1/merchantPrepare register_merchant: name, receipt signer, return windowYes
POST/v1/merchant/receipt-signersPrepare rotate_receipt_signer with a new Ed25519 keyYes
POST/v1/merchant/bondPrepare a USDC bond depositYes
GET/v1/campaignsList campaigns
POST/v1/campaignsPrepare create_campaign: name, cap per conversion, lock days, daily capYes
GET/v1/campaigns/{id}Campaign with vault balance, pending liability and withdrawable balance
POST/v1/campaigns/{id}/fundPrepare a USDC deposit into the vaultYes
POST/v1/campaigns/{id}/withdrawPrepare a withdrawal of unreserved USDCYes
GET/v1/campaigns/{id}/spendDaily rebate and fee debits, inclusive UTC from and to
GET/v1/offersList offers, filterable by campaignId
POST/v1/offersSubmit a browser-encrypted offer; returns the offer and a submit_offer transactionYes
GET/v1/offers/{id}One offer's slot and ingest state
GET/v1/conversionsList conversions, filterable by campaignId and status
GET/v1/conversions/{id}One conversion: amounts, fees, unlock time, refund availability
POST/v1/transactions/{id}/submitSimulate and send a wallet-signed prepared transaction: { signedTransactionBase64 }Yes
POST/v1/transactions/{id}/reportReport a prepared transaction the wallet sent itself: { signature }, base58Yes
GET/v1/transactions/{id}Track a prepared transaction

The three transaction routes also take the Config admin wallet's session, for the activation transactions it signs.

Receipt signer

MethodPathPurposeKey
POST/v1/receiptsRelay a 128-byte receipt signed by the merchant's receipt signer, with the proposed rebateYes
POST/v1/conversions/{id}/refundRelay a signed refund notice while the conversion is lockedYes

A receipt body carries merchantId, signerPublicKey, payloadBase64 (the 128 signed receipt bytes), signatureBase64 and proposedRebateUsdc, the merchant's proposal of the rebate in base units. Arcium's verification checks that the proposal equals the sealed winning offer before the program records or escrows anything. The response is the conversion, at verification_pending; poll GET /v1/conversions/{id} until it is locked. A rejected proposal moves nothing: send the same signed receipt again with the corrected proposal and a new Idempotency-Key.

Integrator (SwayIntegrator)

MethodPathPurposeKey
GET/v1/integratorThe integrator and its status: pending, active or revoked. Readable while pending
GET/v1/integrator/agent-keysList agent keys
POST/v1/integrator/agent-keysRegister an agent public key with a label and scopes from intent:write, intent:read, purchase:report and cashback:link, plus an agent_registration proof. Requires an active integratorYes
DELETE/v1/integrator/agent-keys/{id}Revoke an agent keyYes

Agent (SwayAgent)

MethodPathPurposeScopeKey
POST/v1/intentsPost an encrypted intent with its claim and x25519 public keysintent:writeYes
GET/v1/intents/{id}Poll an intent; a matched one carries the sealed top threeintent:read
GET/v1/intents/{id}/bookThe public campaign and brand map captured for the intentintent:read
POST/v1/intents/{id}/retryThe one retry of an expired computationintent:writeYes
POST/v1/intents/{id}/voucherIssue the checkout voucher for a chosen slotintent:writeYes
POST/v1/purchasesReport order and evidence hashespurchase:reportYes
POST/v1/cashback/tracking-linksCreate an affiliate link with the intent ID as sub-ID. Answers CONFIGURATION_REQUIRED until the deployment connects an affiliate networkcashback:linkYes

Claim key

MethodPathPurposeKey
POST/v1/claims/queryAmounts, unlock times and states for a claim key
POST/v1/claimsClaim one unlocked conversion to a chosen destinationYes
POST/v1/claims/{id}/queryRead a claim's relay and transaction status
POST/v1/disputesOpen a dispute for an intent slotYes
POST/v1/disputes/{id}/queryRead a dispute

A claim body carries claimPublicKey, one ID in claimableIds, destination and the challenge proof. It also carries claimMessageBase64, a 128-byte SWAYCLM1 message that binds the program, the conversion account, the destination wallet, an expiry and a nonce, and claimSignatureBase64, the claim key's signature over it. The program checks that signature before it pays.

Operator (SwayAdmin)

The Sway operator signs each of these with a SwayAdmin request from a key the deployment allows. The operator page at https://swayoffers.com/admin calls them with the Config admin wallet.

MethodPathPurposeKey
GET/v1/admin/merchants/pendingMerchants waiting for activation, oldest first
GET/v1/admin/merchants?status=active|suspendedActive or suspended merchants; omit status for both
GET/v1/admin/integrators/pendingIntegrators waiting for approval, oldest first
GET/v1/admin/disputesOpen and under-review disputes, each with its brand, opening time, evidence hash and compensation address
POST/v1/admin/merchants/{id}/activatePrepare activate_merchant for the Config admin wallet to sign. Body {}Yes
POST/v1/admin/merchants/{id}/suspendPrepare suspend_merchant for the Config admin wallet to sign. Body {}Yes
POST/v1/admin/merchants/{id}/bond/releasePrepare release_bond for the Config admin wallet to sign. Body {}Yes
POST/v1/admin/integrators/{id}/approveApprove an integrator. Body {}; returns the integrator as activeYes
POST/v1/admin/disputes/{id}/resolveResolve a dispute. Body {outcome, bondDebit, resolutionHash}; returns the dispute and, for a user outcome, a prepared slash_bond transactionYes

Activation, suspension and bond release are prepared only for the program's Config admin and only once the merchant's registration is final on chain. Each returns a prepared transaction: the admin wallet signs it and sends it with POST /v1/transactions/{id}/submit or /report under its own wallet session. The program activates the merchant when its bond holds at least minimum_bond. A bond release takes no amount or destination: Sway computes the whole bond balance and pays it to the merchant wallet's own USDC account, and answers BOND_LOCKED while the program would refuse the release. Approval changes the integrator's status from pending to active at once, and its authority can then register agent keys.

A resolve body carries outcome, one of user, merchant or reject; bondDebit, in USDC base units, positive and at most 50 USDC for user and "0" otherwise; and resolutionHash, 64 lowercase hex characters. The operator page sets resolutionHash to the SHA-256 of the operator's resolution note. A user outcome leaves the dispute under_review and returns transaction, a prepared slash_bond that pays the compensation address from the bond. The admin wallet signs and sends it like the transactions above, and the dispute becomes resolved_user once that debit is final on chain. merchant and reject are recorded by the signed request alone.

Affiliate oracle (SwayOracle)

These routes answer CONFIGURATION_REQUIRED until the deployment connects an affiliate network.

MethodPathPurposeKey
POST/v1/affiliate/postbacksIngest a signed affiliate commission eventYes
GET/v1/affiliate/eventsList commission events
POST/v1/oracle/batchesPost a batch of cashback conversionsYes
GET/v1/oracle/batches/{id}Track a batch

MCP tools

GET https://api.swayoffers.com/v1/mcp/tools returns the five tools of @sway/mcp, each as { name, description, inputSchema, outputSchema, operationId }, with followUpOperationId where the tool polls. These are the schemas the package validates against. The package performs the encryption, key storage and signing, then calls the REST operations listed here.

ToolREST operationFollow-up
find_offerspostIntent, POST /v1/intentsgetIntent
get_vouchergetVoucher, POST /v1/intents/{id}/voucher
report_purchasereportPurchase, POST /v1/purchases
claimablelistClaimables, POST /v1/claims/query
claimclaim, POST /v1/claimsgetClaim

Installation, inputs and outputs are in For agent developers.