Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.wiseyield.co/llms.txt

Use this file to discover all available pages before exploring further.

WiseYield’s financial endpoints (expenses, sales, invoices, payroll, P&L, cash-flow forecasts) handle multi-currency data row-by-row. This guide covers the practical mechanics — what to send when creating a row, what comes back in aggregations, and how to surface the conversion transparently to your users. For the conceptual model, see Currency model.

Creating a financial row

Every financial table carries a currency column. Pass it as an ISO 4217 code on every create:
POST /api/v1/farms/{farmId}/expenses
{
  "amount": 1500.00,
  "currency": "EUR",
  "category": "fertilizer",
  "transactionDate": "2026-05-17",
  "note": "Spring NPK application"
}
Supported currency codes include USD, EUR, EGP, GBP, SAR, AED, INR, BRL, CNY, JPY, MXN, ZAR. The full list is the union of currencies covered by ECB’s daily reference rates plus the open.er-api.com canary fallback. If you omit currency, the row defaults to USD. Pass it explicitly — defaults are convenient but unforgiving when your data is multi-currency.

Reading an aggregation

Aggregation endpoints (P&L, cash-flow, financial summary) return a structured response that’s transparent about how the total was assembled:
{
  "baseCurrency": "EUR",
  "total": 12345.67,
  "byCurrency": {
    "EUR": { "original": 8000.00, "converted": 8000.00, "rate": 1 },
    "USD": { "original": 4500.00, "converted": 4140.50, "rate": 0.92011 },
    "EGP": { "original": 9000.00, "converted": 205.17, "rate": 0.022797 }
  },
  "unconvertedCurrencies": []
}
FieldMeaning
baseCurrencyThe currency the total is expressed in (resolved per the farm)
totalSum of every byCurrency[*].converted value
byCurrencyPer-original-currency breakdown — original is the raw sum, converted is after FX, rate is what was applied
unconvertedCurrenciesCodes that didn’t match a known currency (free-form strings like "eg pounds") — their amounts contributed to the total at rate=1

How baseCurrency is resolved

The base currency for any farm-scoped aggregation follows this precedence:
  1. farms.currency — the explicit override set in farm settings.
  2. Country-derived default — inferred from farms.country.
  3. USD — final fallback.
To change a farm’s display currency, update the farm:
PATCH /api/v1/farms/{farmId}
{ "currency": "EUR" }
Subsequent aggregations will report baseCurrency: "EUR".

Surfacing the breakdown in your UI

The byCurrency map exists specifically so integrations can show users what was converted and at what rate. A good UI pattern:
function PnLTotal({ total, baseCurrency, byCurrency, unconvertedCurrencies }) {
  return (
    <div>
      <h2>Total: {formatMoney(total, baseCurrency)}</h2>

      {Object.keys(byCurrency).length > 1 && (
        <details>
          <summary>Conversion breakdown</summary>
          <table>
            {Object.entries(byCurrency).map(([code, b]) => (
              <tr key={code}>
                <td>{code}</td>
                <td>{formatMoney(b.original, code)}</td>
                <td>{formatMoney(b.converted, baseCurrency)}</td>
                <td>@ {b.rate.toFixed(4)}</td>
              </tr>
            ))}
          </table>
        </details>
      )}

      {unconvertedCurrencies.length > 0 && (
        <Warning>
          Could not convert: {unconvertedCurrencies.join(', ')}.
          Total may under-represent reality.
        </Warning>
      )}
    </div>
  );
}

When a currency can’t be converted

If a row was written with a free-form string ("eg pounds", "local", an empty value), the FX layer can’t look up a rate. The amount is still included in the total at rate=1, and the original code is added to unconvertedCurrencies. This is a graceful-degradation choice: the total is wrong by some amount, but the user is told it’s wrong rather than receiving a silently-broken number. Always surface unconvertedCurrencies in your UI. Then either:
  • Edit the offending rows to use a canonical ISO code, or
  • Filter them out client-side if you need a clean total

Rate freshness

  • Rates are refreshed daily from ECB.
  • Each request uses the same rate snapshot for the entire response — two reads at the same moment return the same total.
  • Historical row-date rates are not yet applied. A row from six months ago is converted at today’s rate. This is acceptable for live P&L; if you’re producing accounting exports for an audit period, compute totals at export time and store them alongside the source data.

Common patterns

Force a specific display currency

If your integration always reports in USD regardless of the farm’s setting, pass baseCurrency as a query parameter:
GET /api/v1/farms/{farmId}/pnl?baseCurrency=USD
(Where supported. Not every aggregation endpoint accepts an override — see the endpoint reference.)

Group by category and currency

Most aggregations also accept groupBy=category (or similar) and return the same byCurrency envelope within each group. The structure is recursive: every numeric total is paired with its breakdown.

Reconciling against a bank statement

Bank statements are typically single-currency. Filter the rows by their original currency before aggregating:
GET /api/v1/farms/{farmId}/expenses?currency=EUR
The response carries data[] with only EUR rows and a pagination object — no FX conversion happens because there’s only one currency in play.

See also

  • Currency model — billing vs operational currency, rate sources, precedence
  • Subscription tiers — billing currency is always EUR (Adaptive Currency at checkout)