Portfolios & Wallets
The Qeychain API gives you two complementary ways to organize on-chain addresses:
- Linked wallets attach addresses directly to your Qeychain account. The number of wallets you can link is governed by your plan.
- Portfolios are named groupings of addresses that produce aggregated views — consolidated holdings and consolidated tax figures — across every address in the group.
Use linked wallets to remember the addresses that belong to you, and portfolios when you need a single rolled-up answer across several of them (for example, one household or one fund reported together).
All endpoints on this page live under the supported /api/v1 surface. The base URL for every example is:
https://api.qeychain.xyzAuthentication
Section titled “Authentication”These endpoints operate on your account, so they require a user access token (a JWT), not just a data API key. Send it as a Bearer token on every request:
Authorization: Bearer <ACCESS_TOKEN>Response envelope
Section titled “Response envelope”Every v1 response is wrapped in a standard envelope. Successful responses carry a data payload plus a meta block for request tracing:
{ "success": true, "data": { }, "meta": { "request_id": "3f2b7c1a-9d4e-4a51-8c2b-1e0f6a2d9b44", "timestamp": "2026-07-29T14:32:10.123456Z", "version": "1.0.0" }}Errors use the same envelope with success: false and an error object:
{ "success": false, "error": { "code": "PORTFOLIO_NOT_FOUND", "message": "Portfolio 8b1c… not found" }, "meta": { "request_id": "3f2b7c1a-9d4e-4a51-8c2b-1e0f6a2d9b44", "timestamp": "2026-07-29T14:32:10.123456Z", "version": "1.0.0" }}List endpoints add a pagination block alongside data.
Linked wallets
Section titled “Linked wallets”Linked wallets are addresses tied to your account. Supported address formats are EVM (0x…), Bitcoin, and Solana — fungible tokens and native coins are tracked, valued in USD.
Plan limits
Section titled “Plan limits”Each plan permits a maximum number of linked wallets:
| Plan | Max linked wallets |
|---|---|
| Free | 1 |
| Starter | 5 |
| Professional | 25 |
| DeFi Power User | Unlimited |
Link a wallet
Section titled “Link a wallet”POST /api/v1/user/walletsLinks an on-chain address to your account.
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
address |
string | Yes | On-chain wallet address, 26–255 characters (EVM 0x…, Bitcoin, or Solana). |
label |
string | No | Friendly name for the wallet (max 100 characters). |
Example request
curl -X POST https://api.qeychain.xyz/api/v1/user/wallets \ -H "Authorization: Bearer $QEYCHAIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "address": "0x742d35cc6634c0532925a3b844bc454e4438f44e", "label": "Treasury cold wallet" }'Example response (200 OK)
{ "success": true, "data": { "id": "a1c2e3f4-5678-49ab-9cde-0123456789ab", "address": "0x742d35cc6634c0532925a3b844bc454e4438f44e", "label": "Treasury cold wallet", "created_at": "2026-07-29T14:32:10.101010Z" }, "meta": { "request_id": "3f2b7c1a-9d4e-4a51-8c2b-1e0f6a2d9b44", "timestamp": "2026-07-29T14:32:10.123456Z", "version": "1.0.0" }}If the same address is already linked to your account, the request returns 409 Conflict with error code INVALID_REQUEST.
List linked wallets
Section titled “List linked wallets”GET /api/v1/user/walletsReturns every wallet linked to your account (oldest first), plus your plan’s wallet allowance.
Example request
curl https://api.qeychain.xyz/api/v1/user/wallets \ -H "Authorization: Bearer $QEYCHAIN_TOKEN"Example response (200 OK)
{ "success": true, "data": { "wallets": [ { "id": "a1c2e3f4-5678-49ab-9cde-0123456789ab", "address": "0x742d35cc6634c0532925a3b844bc454e4438f44e", "label": "Treasury cold wallet", "created_at": "2026-07-29T14:32:10.101010Z" } ], "count": 1, "wallet_limit": 5 }, "meta": { "request_id": "7c9a1b2d-3e4f-4051-8617-2a3b4c5d6e7f", "timestamp": "2026-07-29T14:33:02.554433Z", "version": "1.0.0" }}count is how many wallets you have linked; wallet_limit is how many your plan allows (null = unlimited).
Unlink a wallet
Section titled “Unlink a wallet”DELETE /api/v1/user/wallets/{wallet_id}Removes a linked wallet. You can only unlink wallets that belong to your own account.
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
wallet_id |
string (UUID) | Yes | The wallet id returned when it was linked or listed. |
Example request
curl -X DELETE \ https://api.qeychain.xyz/api/v1/user/wallets/a1c2e3f4-5678-49ab-9cde-0123456789ab \ -H "Authorization: Bearer $QEYCHAIN_TOKEN"Example response (200 OK)
{ "success": true, "data": { "deleted": true }, "meta": { "request_id": "9d8c7b6a-5432-41fe-8dcb-6a5b4c3d2e1f", "timestamp": "2026-07-29T14:35:44.778899Z", "version": "1.0.0" }}Error responses
| Status | Error code | When |
|---|---|---|
400 |
INVALID_PARAMETER |
wallet_id is not a valid UUID. |
403 |
FORBIDDEN |
The wallet belongs to another account. |
404 |
RESOURCE_NOT_FOUND |
No wallet exists with that ID. |
Portfolios
Section titled “Portfolios”A portfolio is a named group of addresses. Beyond CRUD, portfolios expose aggregation endpoints that consolidate holdings and tax figures across every address they contain.
A portfolio address record has these fields:
| Field | Type | Description |
|---|---|---|
address |
string | The wallet address. |
label |
string | null | Optional label for the address. |
chain |
string | null | Optional default-chain hint. |
added_at |
string (ISO 8601) | When the address was added to the portfolio. |
Create a portfolio
Section titled “Create a portfolio”POST /api/v1/portfoliosRequest body
| Parameter | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Portfolio name (1–255 characters). |
addresses |
string[] | No | Initial wallet addresses (max 1000). Each is validated as an EVM, Bitcoin, or Solana address. |
description |
string | No | Free-form description. |
metadata |
object | No | Arbitrary JSON metadata you want to store with the portfolio. |
Example request
curl -X POST https://api.qeychain.xyz/api/v1/portfolios \ -H "Authorization: Bearer $QEYCHAIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Fund I — Digital Assets", "description": "All addresses reported for Fund I", "addresses": [ "0x742d35cc6634c0532925a3b844bc454e4438f44e", "0x53d284357ec70ce289d6d64134dfac8e511c8a3d" ] }'Example response (200 OK)
{ "success": true, "data": { "id": "8b1c2d3e-4f50-4617-92a8-b0c1d2e3f405", "name": "Fund I — Digital Assets", "description": "All addresses reported for Fund I", "address_count": 2, "addresses": [ { "address": "0x742d35cc6634c0532925a3b844bc454e4438f44e", "label": null, "chain": null, "added_at": "2026-07-29T14:40:00.000000Z" }, { "address": "0x53d284357ec70ce289d6d64134dfac8e511c8a3d", "label": null, "chain": null, "added_at": "2026-07-29T14:40:00.000000Z" } ], "metadata": null, "created_at": "2026-07-29T14:40:00.000000Z", "updated_at": "2026-07-29T14:40:00.000000Z" }, "meta": { "request_id": "1a2b3c4d-5e6f-4708-9a1b-2c3d4e5f6071", "timestamp": "2026-07-29T14:40:00.221100Z", "version": "1.0.0" }}List portfolios
Section titled “List portfolios”GET /api/v1/portfoliosReturns your portfolios, most recently created first, with pagination.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
page |
integer | No | Page number, 1-indexed. Default 1. |
per_page |
integer | No | Items per page, 1–100. Default 50. |
Example request
curl "https://api.qeychain.xyz/api/v1/portfolios?page=1&per_page=50" \ -H "Authorization: Bearer $QEYCHAIN_TOKEN"Example response (200 OK)
{ "success": true, "data": [ { "id": "8b1c2d3e-4f50-4617-92a8-b0c1d2e3f405", "name": "Fund I — Digital Assets", "description": "All addresses reported for Fund I", "address_count": 2, "addresses": [], "metadata": null, "created_at": "2026-07-29T14:40:00.000000Z", "updated_at": "2026-07-29T14:40:00.000000Z" } ], "meta": { "request_id": "2b3c4d5e-6f70-4819-8a2b-3c4d5e6f7081", "timestamp": "2026-07-29T14:41:12.010203Z", "version": "1.0.0" }, "pagination": { "page": 1, "per_page": 50, "total": 1, "total_pages": 1, "has_more": false }}Get a portfolio
Section titled “Get a portfolio”GET /api/v1/portfolios/{portfolio_id}Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
portfolio_id |
string (UUID) | Yes | Portfolio identifier. |
Example request
curl https://api.qeychain.xyz/api/v1/portfolios/8b1c2d3e-4f50-4617-92a8-b0c1d2e3f405 \ -H "Authorization: Bearer $QEYCHAIN_TOKEN"Returns the same portfolio object shape shown under Create a portfolio, with addresses fully populated. A missing portfolio returns 404 with error code PORTFOLIO_NOT_FOUND.
Update a portfolio
Section titled “Update a portfolio”PUT /api/v1/portfolios/{portfolio_id}Updates the portfolio’s top-level fields. Only the fields you send are changed. To change the address set, use the address endpoints below.
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
portfolio_id |
string (UUID) | Yes | Portfolio identifier. |
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
name |
string | No | New name (1–255 characters). |
description |
string | No | New description. |
metadata |
object | No | Replaces the stored metadata object. |
Example request
curl -X PUT https://api.qeychain.xyz/api/v1/portfolios/8b1c2d3e-4f50-4617-92a8-b0c1d2e3f405 \ -H "Authorization: Bearer $QEYCHAIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Fund I — Digital Assets (2026)" }'Returns the updated portfolio object. A missing portfolio returns 404 PORTFOLIO_NOT_FOUND.
Delete a portfolio
Section titled “Delete a portfolio”DELETE /api/v1/portfolios/{portfolio_id}Deletes the portfolio and its address memberships. This does not unlink any account wallets — it only removes the grouping.
Example response (200 OK)
{ "success": true, "data": { "deleted": true, "portfolio_id": "8b1c2d3e-4f50-4617-92a8-b0c1d2e3f405" }, "meta": { "request_id": "3c4d5e6f-7081-4920-8b3c-4d5e6f708192", "timestamp": "2026-07-29T14:45:00.334455Z", "version": "1.0.0" }}A missing portfolio returns 404 PORTFOLIO_NOT_FOUND.
Add an address
Section titled “Add an address”POST /api/v1/portfolios/{portfolio_id}/addressesPath parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
portfolio_id |
string (UUID) | Yes | Portfolio identifier. |
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
address |
string | Yes | Wallet address to add (EVM, Bitcoin, or Solana). |
label |
string | No | Optional label (max 255 characters). |
chain |
string | No | Optional default-chain hint (max 50 characters). |
Example request
curl -X POST \ https://api.qeychain.xyz/api/v1/portfolios/8b1c2d3e-4f50-4617-92a8-b0c1d2e3f405/addresses \ -H "Authorization: Bearer $QEYCHAIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "address": "0xab5801a7d398351b8be11c439e05c5b3259aec9b", "label": "Operating wallet" }'Example response (200 OK)
{ "success": true, "data": { "address": "0xab5801a7d398351b8be11c439e05c5b3259aec9b", "label": "Operating wallet", "chain": null, "added_at": "2026-07-29T14:47:31.000000Z" }, "meta": { "request_id": "4d5e6f70-8192-4a31-8c4d-5e6f70819243", "timestamp": "2026-07-29T14:47:31.556677Z", "version": "1.0.0" }}Error responses
| Status | Error code | When |
|---|---|---|
400 |
PORTFOLIO_ADDRESS_EXISTS |
The address is already in this portfolio. |
404 |
PORTFOLIO_NOT_FOUND |
No portfolio exists with that ID. |
Remove an address
Section titled “Remove an address”DELETE /api/v1/portfolios/{portfolio_id}/addresses/{address}Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
portfolio_id |
string (UUID) | Yes | Portfolio identifier. |
address |
string | Yes | The address to remove from the portfolio. |
Example request
curl -X DELETE \ "https://api.qeychain.xyz/api/v1/portfolios/8b1c2d3e-4f50-4617-92a8-b0c1d2e3f405/addresses/0xab5801a7d398351b8be11c439e05c5b3259aec9b" \ -H "Authorization: Bearer $QEYCHAIN_TOKEN"Example response (200 OK)
{ "success": true, "data": { "removed": true, "address": "0xab5801a7d398351b8be11c439e05c5b3259aec9b" }, "meta": { "request_id": "5e6f7081-9243-4b41-8d5e-6f70819243a5", "timestamp": "2026-07-29T14:49:05.998877Z", "version": "1.0.0" }}If the address is not part of the portfolio, the request returns 404 RESOURCE_NOT_FOUND.
Aggregated views
Section titled “Aggregated views”These endpoints roll up on-chain data across every address in a portfolio. Qeychain fetches and processes each address’s transaction history when you call them.
Aggregated holdings
Section titled “Aggregated holdings”GET /api/v1/portfolios/{portfolio_id}/holdingsReturns holdings consolidated by symbol + chain across all addresses. Fungible tokens and native coins are included; values are in USD.
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
portfolio_id |
string (UUID) | Yes | Portfolio identifier. |
Example request
curl https://api.qeychain.xyz/api/v1/portfolios/8b1c2d3e-4f50-4617-92a8-b0c1d2e3f405/holdings \ -H "Authorization: Bearer $QEYCHAIN_TOKEN"Example response (200 OK)
{ "success": true, "data": { "portfolio_id": "8b1c2d3e-4f50-4617-92a8-b0c1d2e3f405", "portfolio_name": "Fund I — Digital Assets", "address_count": 2, "addresses": [ "0x742d35cc6634c0532925a3b844bc454e4438f44e", "0x53d284357ec70ce289d6d64134dfac8e511c8a3d" ], "holdings": [ { "symbol": "ETH", "chain": "ethereum", "quantity": 12.5, "cost_basis": 24000.0, "current_value": 40625.0, "unrealized_gl": 16625.0, "unrealized_gl_pct": 69.27, "price": 3250.0, "wallet_count": 2 }, { "symbol": "USDC", "chain": "ethereum", "quantity": 50000.0, "cost_basis": 50000.0, "current_value": 50000.0, "unrealized_gl": 0.0, "unrealized_gl_pct": 0.0, "price": 1.0, "wallet_count": 1 } ], "total_value": 90625.0, "total_cost_basis": 74000.0, "total_unrealized_gl": 16625.0, "errors": null }, "meta": { "request_id": "6f708192-43a5-4c51-8e6f-70819243a5b6", "timestamp": "2026-07-29T14:52:18.112233Z", "version": "1.0.0" }}Each holding reports wallet_count, the number of portfolio addresses that hold it. Holdings are sorted by current_value descending. If a position has no available price, its current_value is 0 and price is null. A missing portfolio returns 404 PORTFOLIO_NOT_FOUND.
Aggregated tax summary
Section titled “Aggregated tax summary”GET /api/v1/portfolios/{portfolio_id}/tax-reports/summaryReturns realized short- and long-term gains and losses consolidated across every address in the portfolio for a tax year.
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
portfolio_id |
string (UUID) | Yes | Portfolio identifier. |
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
tax_year |
integer | No | Tax year (2009–2100). Defaults to the current year. |
Example request
curl "https://api.qeychain.xyz/api/v1/portfolios/8b1c2d3e-4f50-4617-92a8-b0c1d2e3f405/tax-reports/summary?tax_year=2025" \ -H "Authorization: Bearer $QEYCHAIN_TOKEN"Example response (200 OK)
{ "success": true, "data": { "portfolio_id": "8b1c2d3e-4f50-4617-92a8-b0c1d2e3f405", "portfolio_name": "Fund I — Digital Assets", "tax_year": 2025, "address_count": 2, "addresses": [ "0x742d35cc6634c0532925a3b844bc454e4438f44e", "0x53d284357ec70ce289d6d64134dfac8e511c8a3d" ], "short_term_gains": 4200.00, "short_term_losses": 1100.00, "net_short_term": 3100.00, "long_term_gains": 18500.00, "long_term_losses": 900.00, "net_long_term": 17600.00, "total_realized_gl": 20700.00, "total_disposals": 47, "errors": null }, "meta": { "request_id": "70819243-a5b6-4d61-8f70-819243a5b6c7", "timestamp": "2026-07-29T14:55:41.443322Z", "version": "1.0.0" }}A missing portfolio returns 404 PORTFOLIO_NOT_FOUND.
Consolidated Form 8949
Section titled “Consolidated Form 8949”GET /api/v1/portfolios/{portfolio_id}/tax-reports/8949Returns every disposal in the tax year, formatted for IRS Form 8949 and consolidated across all portfolio addresses. Entries are split into short-term and long-term lists.
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
portfolio_id |
string (UUID) | Yes | Portfolio identifier. |
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
tax_year |
integer | Yes | Tax year (2009–2100). |
Example request
curl "https://api.qeychain.xyz/api/v1/portfolios/8b1c2d3e-4f50-4617-92a8-b0c1d2e3f405/tax-reports/8949?tax_year=2025" \ -H "Authorization: Bearer $QEYCHAIN_TOKEN"Example response (200 OK, abridged)
{ "success": true, "data": { "portfolio_id": "8b1c2d3e-4f50-4617-92a8-b0c1d2e3f405", "portfolio_name": "Fund I — Digital Assets", "tax_year": 2025, "address_count": 2, "short_term_entries": [ { "description": "ETH (ethereum)", "date_acquired": "03/14/2025", "date_sold": "09/02/2025", "proceeds": 3250.00, "cost_basis": 2400.00, "adjustment": 0, "gain_or_loss": 850.00, "quantity": 1.0, "lot_id": "lot_7f3a", "source_wallet": "0x742d35cc6634c0532925a3b844bc454e4438f44e" } ], "short_term_count": 1, "short_term_total_proceeds": 3250.00, "short_term_total_cost": 2400.00, "short_term_total_gl": 850.00, "long_term_entries": [], "long_term_count": 0, "long_term_total_proceeds": 0, "long_term_total_cost": 0, "long_term_total_gl": 0, "errors": null }, "meta": { "request_id": "819243a5-b6c7-4e71-8081-9243a5b6c7d8", "timestamp": "2026-07-29T14:58:03.221100Z", "version": "1.0.0" }}Each entry carries a source_wallet so you can trace consolidated disposals back to the originating address. A missing portfolio returns 404 PORTFOLIO_NOT_FOUND.
Error codes
Section titled “Error codes”The endpoints on this page can return the following error codes in the standard error envelope:
| Code | Typical status | Meaning |
|---|---|---|
UNAUTHORIZED |
401 |
Missing or invalid access token. |
FORBIDDEN |
403 |
You do not own the target resource. |
INVALID_REQUEST |
409 |
Wallet address already linked to your account. |
INVALID_PARAMETER |
400 |
A path parameter (such as wallet_id) is malformed. |
RESOURCE_NOT_FOUND |
404 |
Wallet, or portfolio address, not found. |
PORTFOLIO_NOT_FOUND |
404 |
No portfolio exists with that ID. |
PORTFOLIO_ADDRESS_EXISTS |
400 |
The address is already in the portfolio. |
INTERNAL_ERROR |
500 |
Unexpected server error while processing the request. |
