Skip to main content

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 a request_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-read permission, plus earn-bonded-write to 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.com and read credentials from the PWS_API_KEY and PWS_API_SECRET environment 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=US
2

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/strategies
3

Allocate

Submit an allocation with an Idempotency-Key. Async.POST /v1/accounts/{account_id}/earn/bonded/strategies/{strategy_id}/allocate
4

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/allocations
  • GET /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}/deallocate

Allocation 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 with symbol (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 the API-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.
Bonded Earn endpoints take query parameters or a JSON body. Include the query string in the path you sign: the helper supports this through its 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 amount returns the original request_id and 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) returns 409 Conflict with error.code idempotency_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_id returns 404 Not Found. A pending request is never removed.
With idempotent=True, the Python pws_headers helper generates a new key on every call. The JavaScript helper does the same with idempotent: true. For allocation and deallocation requests, leave that option unset. Create the key yourself and pass the same value on every retry, as the examples below do.

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 returns 409 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

APY is an estimate, not a guarantee. Label it “estimated”. When other_assets is not empty, apy already includes their share: show it as the total, for example “3.10% (includes 0.50% EIGEN)”.

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 a POST 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.
A request can be rejected up front, before it is recorded. Except for 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.
To list every request of the account that is still pending, use 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 required Idempotency-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

A rejected deallocation request returns the same codes as an allocation request: see the table in step 3. Poll the 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

  1. One key per request, reused on every retry. That makes retries after a timeout safe.
  2. Always poll the status. 202 Accepted means recorded, not done. A failed request comes back as 200 OK with status: failed.
  3. Branch on codes, not messages. Use error.code for rejections and error.reason for failed requests, and handle unknown values generically.
  4. Use can_allocate / can_deallocate from the account strategy list to decide which actions to offer.
  5. Pass quote_symbol consistently across allocations and rewards so converted totals line up.

API reference