Internal personal transfers

Move an asset between two accounts of the same user on your platform, keeping its original lots and cost basis.

Part of Handle transfers. When a user moves an asset from one of their accounts on your platform to another, the receiving account should keep the original lots: the same acquisition dates and the same cost basis. You link the two sides of the transfer, and Taxbit carries the lots across.

By the end of this page you have moved an asset between two accounts of one user, with its lots and cost basis intact in the receiving account.

Before you begin

  • A bearer token. See Authenticate your tenant.
  • Two accounts that belong to the same user, and inventory in the sending one. See Set up inventory and gains.
  • Your own record of which withdrawal and which deposit are two sides of the same transfer. You make that match; the request links them.

Send the withdrawal

Send the outgoing side from the sending account as a withdraw with subtype internal-personal.

curl -X POST $BASE_URL/v1/transactions/external-id \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "wd_52210",
    "account_id": "acct_5f2e7d10",
    "type": "withdraw",
    "subtype": "internal-personal",
    "datetime": "2026-09-10T14:02:00Z",
    "sent": [{
      "asset_amount": { "amount": "0.5", "asset": { "code": "ETH", "type": "Crypto" } },
      "rates": [{ "amount": "3150.00", "asset": { "code": "USD", "type": "Fiat" } }]
    }]
  }'

Send the deposit, linked to the withdrawal

Send the incoming side to the receiving account as a deposit with subtype internal-personal, and put the withdrawal's id in counterparty_transaction_id.

curl -X POST $BASE_URL/v1/transactions/external-id \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "dep_52211",
    "account_id": "acct_9d41b3e7",
    "type": "deposit",
    "subtype": "internal-personal",
    "counterparty_transaction_id": "wd_52210",
    "datetime": "2026-09-10T14:02:00Z",
    "received": [{
      "asset_amount": { "amount": "0.5", "asset": { "code": "ETH", "type": "Crypto" } },
      "rates": [{ "amount": "3150.00", "asset": { "code": "USD", "type": "Fiat" } }]
    }]
  }'

You should see 201 with "status": "success" for both.

counterparty_transaction_id is required on an internal-personal deposit; without it the request returns 400 with counterparty_transaction_id is required when subtype is internal-personal on transactions. The link also takes precedence over user-provided lots: if the deposit has transfer lots as well, the withdrawal's lots are the ones that carry over.

Check the receiving account

Read the receiving account's ETH inventory, as in Retrieve inventory. The lots that left the sending account appear here with their original acquisition_datetime and cost, not as missing cost basis.

If the request fails

  • 400. A field failed validation, for example an internal-personal deposit without counterparty_transaction_id: counterparty_transaction_id is required when subtype is internal-personal on transactions.
  • 401. The bearer token is missing, invalid, or expired. Mint a new one and retry.
  • 429. Too many requests. Slow down and retry.

Parameters

Only the fields that make a transfer internal. The full schema is in the Transactions reference.

FieldTypeDescription
typestringwithdraw on the sending side, deposit on the receiving side.
subtypestringinternal-personal on both sides.
counterparty_transaction_idstringOn the deposit: your id for the matching withdrawal.

Where to go next

Back to Handle transfers, or Gifts for a transfer between two different users.