Overview
Bonded Earn lets you allocate a user’s balance into a specific earn strategy and deallocate it again. Unlike Auto Earn, you choose the strategy and the amount, and you see the lock-up periods. Proof-of-stake networks pay rewards for using tokens to help secure the network. Some strategies require funds to stay allocated during bonding or unbonding periods. An exit queue is the network’s waiting list before unbonding begins. Depending on the strategy, funds can pass through a bonding period before they earn and, for ETH, an exit queue, then an unbonding period, before they return to the user (see Allocation lifecycle). Allocate and deallocate are asynchronous. A request is accepted first and settles later. You get arequest_id back and poll for the outcome.
The integration shape is eleven endpoints, listed in the API reference at the end of this page.
Prerequisites
- Payward Services API credentials (see the Authentication guide). The API key needs the
earn-bonded-readpermission, plusearn-bonded-writeto allocate and deallocate (see API key permissions). - A verified user with at least one account and sufficient balance in the strategy’s asset. If the user holds a different asset, follow the Swaps guide to acquire it first.
- Examples target
https://api.services.payward.comand read credentials from thePWS_API_KEYandPWS_API_SECRETenvironment variables.
Workflow
1
Discover strategies (optional, pre-account)
Show which Bonded Earn strategies are offered in a country before the user has an account.
GET /v1/earn/bonded/strategies?country=US2
Discover strategies for an account
Show the account’s strategies, with whether it can allocate or deallocate right now.
GET /v1/accounts/{account_id}/earn/bonded/strategies3
Allocate
Submit an allocation with an
Idempotency-Key. Async.POST /v1/accounts/{account_id}/earn/bonded/strategies/{strategy_id}/allocate4
Poll the request until it settles
Watch
status move from pending to succeeded or failed.GET /v1/accounts/{account_id}/earn/bonded/requests/{request_id}5
Read allocations and rewards
Show what is bonding, earning or on its way out, and the rewards.
GET /v1/accounts/{account_id}/earn/bonded/allocationsGET /v1/accounts/{account_id}/earn/bonded/rewards/upcoming
6
Deallocate
Submit a deallocation, then poll it the same way.
POST /v1/accounts/{account_id}/earn/bonded/strategies/{strategy_id}/deallocateAllocation lifecycle
An allocation moves through up to four states.
Each strategy has three period objects,
bonding_period, exit_queue_period, and unbonding_period, with the same fields:
Asset, amount, and quote-currency conventions
Entries describe their asset withsymbol (e.g., ETH), type (fiat, crypto, stablecoin, xstock, or equity) and name (e.g., "Ethereum").
All monetary fields are plain decimal strings:
The allocation and reward endpoints accept
quote_symbol and quote_type, and echo both in the response. Today only quote_type=fiat is supported; quote_symbol defaults to USD. An unsupported quote returns 400 Bad Request with a cause on quote_symbol or quote_type.
The list endpoints page with page_token / page_size. page_size defaults to 20 and is capped at 25. page_size is ignored when you pass page_token. Pass next_page_token back as page_token to get the next page; it is absent on the last page.
Strategy lists, allocations, rewards per strategy and upcoming rewards are sorted by asset symbol (A-Z), then unbonding period (shortest first, variable last). Allocation states are sorted by the soonest scheduled change. Pending requests are sorted oldest first.
Authentication setup
Authenticated endpoints require an HMAC-SHA512 request signature in theAPI-Sign header and a monotonically increasing nonce in the API-Nonce header. The helper below derives the signature from the URL path, request body, and nonce. See the Authentication guide for the full algorithm.
params argument.
API key permissions
Create the API key in the Payward Console with the permissions your integration needs:
Grant both to an integration that allocates and then polls the request.
Idempotency keys
Idempotency-Key is required on allocate and deallocate. It must be a UUIDv4 in the canonical hyphenated form, for example 550e8400-e29b-41d4-a716-446655440000. Generate one key per logical request and keep it until the request has settled:
- Retrying the same request with the same key, strategy and
amountreturns the originalrequest_idand moves no funds again. This is how you retry safely after a timeout or a dropped connection. - Reusing a key for a different request (another strategy, another
amount, or a deallocation request instead of an allocation request) returns409 Conflictwitherror.codeidempotency_key_reused. - Keys are scoped to the account.
- A request and its key are kept for at least 24 hours after the request completes. After that, the same key starts a new request, and the old
request_idreturns404 Not Found. A pending request is never removed.
One request per strategy at a time
Only one allocation or deallocation request can be in progress on a strategy at a time. A second request on the same strategy returns409 Conflict with error.code allocation_in_progress until the first one settles. Requests on different strategies run independently.
Step 1: discover strategies by country
GET /v1/earn/bonded/strategies is user-agnostic. country is required and must be a two-letter ISO 3166-1 alpha-2 code. Strategy terms can vary by country. For a signed-in user, use the account strategy list in step 2 for their terms.
Filter with type, yield_source, and symbol:
Response example
Step 2: discover strategies for an account
GET /v1/accounts/{account_id}/earn/bonded/strategies returns the same fields, plus whether the account can act right now. It also lists strategies the account still holds an allocation in after it stopped being eligible, so the user can still deallocate.
This list is also paged. Follow next_page_token until it is absent, using the same loop as in step 1.
Use Get portfolio to match strategies to assets the user already holds.
The total waiting time after deallocation is exit_queue_period.seconds plus unbonding_period.seconds. If either period’s type is variable, the total is an estimate.
Response example
Step 3: allocate
Send aPOST with the amount in the strategy’s asset. The response is 202 Accepted with a request_id.
Response example
202 Accepted confirms the request was recorded, not that the funds moved. A request that is accepted can still fail
when it is processed: always poll its status in step 4.idempotency_key_reused, where another request already owns the key, no request is recorded and the key stays free: fix the input and retry with the same key. Branch on error.code:
An allocation request is not rejected up front when the account balance is insufficient. It is accepted and fails later, so check
error.reason in step 4. Strategy caps work the same way and fail with above_user_cap.
New codes may be added. Treat an unknown code as a generic bad_request, not_found or request_conflict error for its status.
Step 4: poll the request until it settles
GET /v1/accounts/{account_id}/earn/bonded/requests/{request_id} returns the request. status is pending until it is processed, then succeeded or failed; both are final.
Response example
A failed request is still a
200 OK: the outcome is in status and error, not in the HTTP status. A strategy cap is checked here, after the request is accepted: an allocation above user_cap fails with above_user_cap. Known error.reason values are above_user_cap, insufficient_balance, not_eligible, canceled and internal. New values may be added: treat an unknown one as a generic failure.
A failed request owns its key. Retrying the same key returns the same failed request. To try again, for example after
the user tops up, send a new request with a new key.
GET /v1/accounts/{account_id}/earn/bonded/requests. A request drops off that list once it reaches a final status.
Step 5a: list allocations
GET /v1/accounts/{account_id}/earn/bonded/allocations returns one entry per strategy, with the amount in each lifecycle state and the soonest upcoming change.
Response example
Step 5b: list allocation state changes
GET /v1/accounts/{account_id}/earn/bonded/allocation-states lists every upcoming change, ordered by when each allocation leaves its current state. Use it for a timeline: “1 ETH unbonds on June 8”. Filter with strategy_id, and with state (bonding, exit_queue or unbonding) more than once to get several states.
Response example
Step 6: read rewards
Rewards are paid once a week, at about the same time for every strategy. Each user’s account has its own weekly payout time, derived from its account ID. It is fixed for the lifetime of the account and cannot be changed. For each strategy returned by upcoming rewards,next_reward_date gives the expected payout date and time in UTC.
Three endpoints cover rewards. All take quote_symbol / quote_type; the two lists also page.
Rewards per strategy
rewarded is one lifetime figure in the strategy’s asset, net of any reward fee. Rewards paid in another asset are converted into the strategy’s asset when they are paid and included. rewarded_in_quote is calculated from the USD value of each reward at the time it was paid, so it does not move with the asset price. With quote_symbol=USD it is that USD figure. For any other quote_symbol, the USD figure is calculated into that currency at today’s USD rate, so it can change with fiat exchange rates.
Upcoming rewards
Step 7: deallocate
Deallocation requests work like allocation requests: same body, same requiredIdempotency-Key header and rules, same 202 Accepted with a request_id to poll. Partial deallocation is allowed. To leave a strategy completely, send the full earning amount. Funds that are bonding, in the exit queue or unbonding can’t be deallocated. There is no “deallocate all” shortcut.
Response example
request_id as in step 4. The funds then pass through any nonzero exit queue and unbonding periods. Follow them with allocation-states until they are available.
Error handling
All error responses use the standard envelope:Best practices
- One key per request, reused on every retry. That makes retries after a timeout safe.
- Always poll the status.
202 Acceptedmeans recorded, not done. A failed request comes back as200 OKwithstatus: failed. - Branch on codes, not messages. Use
error.codefor rejections anderror.reasonfor failed requests, and handle unknown values generically. - Use
can_allocate/can_deallocatefrom the account strategy list to decide which actions to offer. - Pass
quote_symbolconsistently across allocations and rewards so converted totals line up.