API · 12 operations

Holdings

What you own that is not an account, and exchange rates.

get /v1/budgets/{budget_id}/holdings

List holdings on this budget

scope budget:read · plan feature assets · listHoldings

Parameters

  • budget_id path · string · required

Returns · 200

{ holdings[] }. Declared positions: quantity, last unit price, lean-on value_cents in the holding's currency, plus home_currency, home_cents (the value in the budget's currency, null with no rate) and fx: { rate, as_of, source, stale }. Not a bank account and not a market warehouse.

Example

curl 'https://api.mintro.money/v1/budgets/default/holdings' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 402

    plan_limit: The tenant's plan does not allow this. Read entitlements on GET /v1/me to see what the plan unlocks and allows, report the limit to the human, and let them choose to upgrade at https://mintro.money/pricing; do not retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

post /v1/budgets/{budget_id}/holdings

Declare a holding; look up a public price when a symbol is given

scope budget:write · plan feature assets · createHolding

Parameters

  • budget_id path · string · required

Body · JSON

  • name string · required · ≤ 200 chars

  • kind "crypto" | "equity" | "other" · required

  • currency string · matches ^[A-Z]{3}$ · ≥ 3 chars · ≤ 3 chars

  • quote_symbol string | null · ≤ 64 chars

  • quantity string · matches ^(0|[1-9]\d*)(\.\d{1,12})?$

  • unit_price string | null · matches ^(0|[1-9]\d*)(\.\d{1,12})?$

  • note string | null · ≤ 1000 chars

  • for_retirement boolean | null

Returns · 201

The holding plus quote_found (true/false when a lookup ran, null when the caller set unit_price or the kind is other). A miss is not an error: POST .../valuations to set the price. for_retirement: true marks a kind other holding (an RRSP, TFSA, 401k or pension pot) as money to live on in retirement; left out, other counts as a thing owned, in net worth but not retirement.

Example

curl -X POST 'https://api.mintro.money/v1/budgets/default/holdings' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "name": "<name>", "kind": "crypto" }'

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 402

    plan_limit: The tenant's plan does not allow this. Read entitlements on GET /v1/me to see what the plan unlocks and allows, report the limit to the human, and let them choose to upgrade at https://mintro.money/pricing; do not retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.

get /v1/budgets/{budget_id}/holdings/{id}

One holding, and what it is expected to be worth in a month

scope budget:read · plan feature assets · getHolding

Parameters

  • budget_id path · string · required

  • id path · string · required

  • at query · string

Returns · 200

The current snapshot. With ?at=YYYY-MM, projected: { month, value_cents, annual_growth } says what it is expected to be worth then (its own rate, else the outlook's expected return for retirement money, else flat), grown with the same monthly step the projection takes, so a scenario that sells it lands on this figure.

Example

curl 'https://api.mintro.money/v1/budgets/default/holdings/<id>' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 402

    plan_limit: The tenant's plan does not allow this. Read entitlements on GET /v1/me to see what the plan unlocks and allows, report the limit to the human, and let them choose to upgrade at https://mintro.money/pricing; do not retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

patch /v1/budgets/{budget_id}/holdings/{id}

Rename, close, change symbol or quantity

scope budget:write · plan feature assets · updateHolding

Parameters

  • budget_id path · string · required

  • id path · string · required

Body · JSON

  • name string · ≤ 200 chars

  • status "open" | "closed"

  • quote_symbol string | null · ≤ 64 chars

  • quantity string · matches ^(0|[1-9]\d*)(\.\d{1,12})?$

  • kind "crypto" | "equity" | "other"

  • for_retirement boolean | null

  • annual_growth string | null · matches ^(0|[1-9]\d*)(\.\d{1,12})?$

Returns · 200

The updated holding. A quantity change at an existing unit price appends a valuation.

Example

curl -X PATCH 'https://api.mintro.money/v1/budgets/default/holdings/<id>' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "name": "<name>", "status": "open" }'

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 402

    plan_limit: The tenant's plan does not allow this. Read entitlements on GET /v1/me to see what the plan unlocks and allows, report the limit to the human, and let them choose to upgrade at https://mintro.money/pricing; do not retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.

delete /v1/budgets/{budget_id}/holdings/{id}

Delete a holding and its valuations

scope budget:write · plan feature assets · deleteHolding

Parameters

  • budget_id path · string · required

  • id path · string · required

Returns · 200

{ deleted: true }.

Example

curl -X DELETE 'https://api.mintro.money/v1/budgets/default/holdings/<id>' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 402

    plan_limit: The tenant's plan does not allow this. Read entitlements on GET /v1/me to see what the plan unlocks and allows, report the limit to the human, and let them choose to upgrade at https://mintro.money/pricing; do not retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.

post /v1/budgets/{budget_id}/holdings/{id}/refresh

Look up a public price and append a valuation if found

scope budget:write · plan feature assets · refreshHolding

Parameters

  • budget_id path · string · required

  • id path · string · required

Returns · 200

The holding plus quote_found. False leaves the snapshot unchanged; set a price yourself.

Example

curl -X POST 'https://api.mintro.money/v1/budgets/default/holdings/<id>/refresh' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 402

    plan_limit: The tenant's plan does not allow this. Read entitlements on GET /v1/me to see what the plan unlocks and allows, report the limit to the human, and let them choose to upgrade at https://mintro.money/pricing; do not retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.

get /v1/budgets/{budget_id}/holdings/{id}/valuations

The holding's history: every price and trade, with the bank row beside each trade (newest first, cap 100)

scope budget:read · plan feature assets · listHoldingValuations

Parameters

  • budget_id path · string · required

  • id path · string · required

  • order query · string

Returns · 200

{ valuations[] }, each { id, kind, quantity, unit_price, value_cents, traded_quantity, paid_cents, source, as_of, note, transaction_id, transaction: { date, name, account_id, amount_cents } | null, reversed_at, reverses_id }. kind is price | quantity | buy | sell | reversal. quantity is what was held after the row; traded_quantity what a buy added (positive) or a sell removed (negative); paid_cents what the bank row paid or brought in. A reversal undoes the row in reverses_id. order=asc walks oldest first. GET /v1/transactions?holding_id= lists the rows.

Example

curl 'https://api.mintro.money/v1/budgets/default/holdings/<id>/valuations' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 402

    plan_limit: The tenant's plan does not allow this. Read entitlements on GET /v1/me to see what the plan unlocks and allows, report the limit to the human, and let them choose to upgrade at https://mintro.money/pricing; do not retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

post /v1/budgets/{budget_id}/holdings/{id}/valuations

Set the unit price by hand and append a valuation

scope budget:write · plan feature assets · setHoldingPrice

Parameters

  • budget_id path · string · required

  • id path · string · required

Body · JSON

  • unit_price string · required · matches ^(0|[1-9]\d*)(\.\d{1,12})?$

  • quantity string · matches ^(0|[1-9]\d*)(\.\d{1,12})?$

  • note string | null · ≤ 1000 chars

Returns · 201

The holding with the new lean-on value. Always allowed, even when a feed exists.

Example

curl -X POST 'https://api.mintro.money/v1/budgets/default/holdings/<id>/valuations' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "unit_price": "<unit_price>" }'

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 402

    plan_limit: The tenant's plan does not allow this. Read entitlements on GET /v1/me to see what the plan unlocks and allows, report the limit to the human, and let them choose to upgrade at https://mintro.money/pricing; do not retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.

post /v1/budgets/{budget_id}/holdings/{id}/buy

Buy more: money leaves an account and the row is linked as the buy

scope budget:write · plan feature assets · buyHolding

Parameters

  • budget_id path · string · required

  • id path · string · required

Body · JSON

  • envelope_id string · required · ≤ 64 chars

  • account_id string · required · ≤ 64 chars

  • amount_cents integer · required · ≥ 1

  • quantity string · matches ^(0|[1-9]\d*)(\.\d{1,12})?$

  • date string · required · YYYY-MM-DD

  • note string | null · ≤ 1000 chars

Returns · 201

{ holding, outflow }. One transaction on the paying account, filed into envelope_id (a buy comes out of an envelope, as YNAB categorises a transfer into a tracking account) and linked to the holding. quantity defaults to the amount ÷ the holding's unit price; an existing price stays (the fill may disagree because of fees), a first buy without a price takes the fill. Not a transfer: a holding is not an account. When the bank already delivered the row, link that row with POST /v1/transactions/{id}/holding-trade instead of writing a second one.

Example

curl -X POST 'https://api.mintro.money/v1/budgets/default/holdings/<id>/buy' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "envelope_id": "<envelope_id>", "account_id": "<account_id>", "amount_cents": 45000, "date": "2026-09-15" }'

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 402

    plan_limit: The tenant's plan does not allow this. Read entitlements on GET /v1/me to see what the plan unlocks and allows, report the limit to the human, and let them choose to upgrade at https://mintro.money/pricing; do not retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.

get /v1/budgets/{budget_id}/fx

The exchange rates this budget's picture converts with

scope budget:read · listFx

Parameters

  • budget_id path · string · required

Returns · 200

{ home, as_of, stale, currencies: [{ currency, holdings, loans, quote: { rate, as_of, source, stale } | null }] }. One row per currency a holding or loan is kept in other than the budget's own. quote is the latest stored rate on or before today (a rate the person set wins over the feed on the same day), or null when none is stored; stale is true when any is missing or older than seven days. A read never fetches: call POST …/fx/refresh, or set one. Every home_cents on a holding or loan, and every total on the picture, was converted with these.

Example

curl 'https://api.mintro.money/v1/budgets/default/fx' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

put /v1/budgets/{budget_id}/fx

Set an exchange rate by hand

scope budget:write · setFxRate

Parameters

  • budget_id path · string · required

Body · JSON

  • currency string · required · matches ^[A-Z]{3}$ · ≥ 3 chars · ≤ 3 chars

  • rate string · required · matches ^(0|[1-9]\d*)(\.\d{1,12})?$

  • as_of string · YYYY-MM-DD

Returns · 200

{ currency, home, quote }. Home units per one unit of currency (a USD holding on a CAD budget: 1.35). Dated as_of (default today) and kept beside the feed's rate for that day; yours wins. Use it when the feed has no rate for the currency, or when the lender's statement used a different one.

Example

curl -X PUT 'https://api.mintro.money/v1/budgets/default/fx' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "currency": "CAD", "rate": "<rate>" }'

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.

post /v1/budgets/{budget_id}/fx/refresh

Fetch today's rates (and month-ends back five years) for every currency in use

scope budget:write · refreshFx

Parameters

  • budget_id path · string · required

Returns · 200

{ home, refreshed: [{ currency, found, days, latest_as_of }] }. Frankfurter (ECB reference rates, keyless). On demand only: nothing in Mintro polls a feed on a clock. A currency the feed does not know reports found: false; set its rate with PUT …/fx. Month-ends are stored so the net-worth history converts each month at that month's rate.

Example

curl -X POST 'https://api.mintro.money/v1/budgets/default/fx/refresh' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.