# Finance — Eto API

> Eto’s own revenue, fee, refund and profit figures for a connected store.

Base URL `https://eto.tools/api/v1/`. Every request carries the `X-Eto-API-Key` header. Part of the [Eto API reference](https://eto.tools/dev/docs/).

## Endpoints in this category

- [GET /api/v1/finance/{shop_id}](https://eto.tools/dev/docs/finance/finance.md): Get all finance metrics
- [POST /api/v1/finance/{shop_id}/sync](https://eto.tools/dev/docs/finance/finance-sync.md): Re-fetch finance data from Etsy
- [GET /api/v1/finance/software-expenses](https://eto.tools/dev/docs/finance/finance-software-expenses.md): Get logged software expenses (subscriptions)

## GET /api/v1/finance/{shop_id}

Get all finance metrics

- Operation ID: `finance`
- Slug: `finance`
- Category: Finance
- Authentication: API key only. Reads or writes data Eto holds for your own account.
- HTML: https://eto.tools/dev/docs/#ep-finance
- Markdown: https://eto.tools/dev/docs/finance/finance.md

Returns every financial metric for a store over a date range. Zero Etsy API calls — reads directly from the Eto database. Instant.

━━━ WHAT YOU GET BACK (always returned) ━━━

shop_id — the store
shop_name — store display name
currency — e.g. "GBP", "USD"
start / end — the range you requested
balance_cents — current store balance (latest ledger entry, not range-scoped)
paid_out_cents — total deposited to bank in this range
sales_count — number of sales
gross_sales_cents — raw sale revenue before any deductions
sales_cents — display sales value (gross - refunds - cancelled + remitted tax)
fees_cents — total Etsy fees charged (negative number)
ads_cents — total ad spend (negative number)
refund_cents — total refunded to buyers
cancelled_cents — total cancelled order amounts
recoup_cents — Etsy recovering money from your balance
remitted_tax_cents — sales tax / VAT remitted by Etsy (negative)
gross_profit_cents — profit before COGS (gross + fees + ads + tax)
cogs_cents — cost of goods sold (from your item costs in Eto)
net_profit_cents — gross profit minus COGS

All amounts in cents. Divide by 100 for display. Negative = money out.

━━━ HOW TO PICK THE DATE WINDOW ━━━

EASIEST — pass `period`: today, yesterday, last_7_days, last_30_days, this_month, last_month.
  The server resolves it in the shop timezone (override with `timezone`). No timestamps, no math.
Specific days — pass `start_date` / `end_date` as "YYYY-MM-DD" (inclusive).
Advanced — pass raw `start` / `end` unix seconds (range is [start, end), max 366 days).
The response echoes back a `range` object (exact start/end unix, start_local/end_local ISO,
timezone, label) AND the current server time — so you never compute or guess dates, and never
need a separate "what time is it" call. If the user means THEIR calendar day, pass their timezone.
Any store — use any shop_id connected to your Eto account (GET /api/v1/stores to list them).

━━━ BREAKDOWNS (optional) ━━━

Add &breakdown= to get detailed sub-breakdowns. Combine with commas.

breakdown=fees — returns breakdown.fees with:
  listing, listing_credits, transaction, transaction_credits,
  processing, processing_credits, shipping, shipping_credits,
  vat_on_fees, vat_on_fees_credits, regulatory, regulatory_credits,
  other, total

breakdown=ads — returns breakdown.ads with:
  etsy_ads, offsite_ads, offsite_ads_credits, subscription, total

breakdown=sales — returns breakdown.sales with:
  gross_sales_cents, refund_cents, cancelled_cents,
  remitted_tax: { sales_tax, sales_tax_credits, vat_ep, vat_ep_credits, total }

breakdown=gross — same as sales (alias)

breakdown=profit — returns both breakdown.gross_profit and breakdown.net_profit:
  gross_profit: { gross_sales_cents, fees_cents, ads_cents, remitted_tax_cents, total }
  net_profit: { gross_profit_cents, cogs_cents, total }

Combine: &breakdown=fees,ads,profit returns all three at once.
Omit entirely to get just the top-level numbers.

━━━ WHAT HAS BREAKDOWNS vs WHAT DOESN'T ━━━

HAS breakdown: fees, ads, sales/gross, profit
NO breakdown: balance_cents, paid_out_cents, sales_count, refund_cents,
  cancelled_cents, recoup_cents, cogs_cents, remitted_tax_cents
  (some of these appear inside the sales or profit breakdown instead)

━━━ HOURLY BREAKDOWN (optional, single-day only) ━━━

Add &hourly=true to get a per-hour breakdown alongside the day summary.
Only works when your range is 24 hours or less (single day).
If your range is longer than 24h, hourly returns null with a note.

What you get: an "hourly" array with one object per hour, from hour 0
(midnight) up to the hour containing your end time.

Each hour object contains:
  hour — 0-23 (the hour index)
  hour_start — unix timestamp of this hour's start
  hour_end — unix timestamp of this hour's end
  entry_count — number of ledger entries in this hour
  sales_count — number of sales
  gross_sales_cents — raw revenue for this hour
  sales_cents — display sales (gross - refunds - cancelled + tax)
  fees_cents — Etsy fees charged this hour
  ads_cents — ad spend this hour
  remitted_tax_cents — tax remitted this hour
  refund_cents — refunds this hour
  cancelled_cents — cancellations this hour
  recoup_cents — Etsy recoupments this hour
  gross_profit_cents — profit before COGS
  cogs_cents — cost of goods sold this hour
  net_profit_cents — profit after COGS
  paid_out_cents — payouts this hour

Combine with breakdowns: &hourly=true&breakdown=fees,ads works fine.
The hourly array gives per-hour summary metrics.
The breakdown object gives sub-category detail for the FULL day.

Turn it off: just omit &hourly (default is off). No penalty for not using it.
Multi-day range: hourly is ignored (returns null + hourly_note explaining why).

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `shop_id` | path | `integer` | yes | Your Etsy shop ID (from GET /api/v1/stores) |
| `period` | query | `string` | no | EASIEST — a named window the server resolves for you, no timestamps: today, yesterday, last_7_days, last_30_days, this_month, last_month. |
| `start_date` | query | `string` | no | Calendar day "YYYY-MM-DD" (inclusive). Use with end_date for a custom range. |
| `end_date` | query | `string` | no | Calendar day "YYYY-MM-DD" (inclusive). Defaults to start_date (single day). |
| `timezone` | query | `string` | no | IANA timezone for resolving period/dates (e.g. "Europe/London", "America/New_York"). Defaults to the shop's local timezone. Pass the USER's timezone (e.g. Europe/Helsinki) if they mean their own calendar day. |
| `start` | query | `integer` | no | Advanced: raw range start as unix seconds (inclusive). Prefer period/start_date. |
| `end` | query | `integer` | no | Advanced: raw range end as unix seconds (exclusive, max 366 days). Prefer period/start_date. |
| `breakdown` | query | `string` | no | Comma-separated breakdowns to include: fees, ads, sales, gross, profit. Omit for top-level numbers only. |
| `hourly` | query | `string` | no | Set to "true" to get per-hour metrics. Only works for single-day queries (range ≤ 24h). Returns array of 24 hour objects. Default: off. |

### Request

```bash
# ── All metrics for a period (no breakdown) ──
curl -H "X-Eto-API-Key: eto_your_key" \
  "https://eto.tools/api/v1/finance/12345678?start=1716508800&end=1717113600"

# ── All metrics for a single day ──
curl -H "X-Eto-API-Key: eto_your_key" \
  "https://eto.tools/api/v1/finance/12345678?start=1716508800&end=1716595200"

# ── All metrics + fee breakdown ──
curl -H "X-Eto-API-Key: eto_your_key" \
  "https://eto.tools/api/v1/finance/12345678?start=1716508800&end=1717113600&breakdown=fees"

# ── All metrics + ads breakdown ──
curl -H "X-Eto-API-Key: eto_your_key" \
  "https://eto.tools/api/v1/finance/12345678?start=1716508800&end=1717113600&breakdown=ads"

# ── All metrics + profit breakdown (shows gross + net) ──
curl -H "X-Eto-API-Key: eto_your_key" \
  "https://eto.tools/api/v1/finance/12345678?start=1716508800&end=1717113600&breakdown=profit"

# ── All metrics + sales/tax breakdown ──
curl -H "X-Eto-API-Key: eto_your_key" \
  "https://eto.tools/api/v1/finance/12345678?start=1716508800&end=1717113600&breakdown=sales"

# ── All metrics + EVERY breakdown combined ──
curl -H "X-Eto-API-Key: eto_your_key" \
  "https://eto.tools/api/v1/finance/12345678?start=1716508800&end=1717113600&breakdown=fees,ads,sales,profit"

# ── Single day + fee and ads breakdown ──
curl -H "X-Eto-API-Key: eto_your_key" \
  "https://eto.tools/api/v1/finance/12345678?start=1716508800&end=1716595200&breakdown=fees,ads"

# ── Single day + hourly breakdown (per-hour metrics) ──
curl -H "X-Eto-API-Key: eto_your_key" \
  "https://eto.tools/api/v1/finance/12345678?start=1716508800&end=1716595200&hourly=true"

# ── Single day + hourly + fee breakdown (combine both) ──
curl -H "X-Eto-API-Key: eto_your_key" \
  "https://eto.tools/api/v1/finance/12345678?start=1716508800&end=1716595200&hourly=true&breakdown=fees"
```

```python
import requests

url = "https://eto.tools/api/v1/finance/12345678?start=1716508800&end=1717113600"
headers = {"X-Eto-API-Key": "eto_your_key"}

response = requests.get(url, headers=headers, timeout=120)
response.raise_for_status()
print(response.json())
```

```javascript
const url = "https://eto.tools/api/v1/finance/12345678?start=1716508800&end=1717113600";

const response = await fetch(url, {
  method: "GET",
  headers: {
    "X-Eto-API-Key": "eto_your_key",
  },
});
if (!response.ok) throw new Error(`Eto API ${response.status}`);
const data = await response.json();
```

### Response

```
// ── Response WITHOUT breakdown ──
{
  "shop_id": "12345678",
  "shop_name": "My Etsy Shop",
  "currency": "GBP",
  "start": 1716508800,
  "end": 1717113600,
  "balance_cents": 125430,
  "paid_out_cents": 90000,
  "sales_count": 15,
  "gross_sales_cents": 125000,
  "sales_cents": 120000,
  "fees_cents": -15000,
  "ads_cents": -5000,
  "refund_cents": 8000,
  "cancelled_cents": 2000,
  "recoup_cents": 0,
  "remitted_tax_cents": -3000,
  "gross_profit_cents": 102000,
  "cogs_cents": 30000,
  "net_profit_cents": 72000
}

// ── Response WITH breakdown=fees,ads,sales,profit ──
{
  "shop_id": "12345678",
  "shop_name": "My Etsy Shop",
  "currency": "GBP",
  "start": 1716508800,
  "end": 1717113600,
  "balance_cents": 125430,
  "paid_out_cents": 90000,
  "sales_count": 15,
  "gross_sales_cents": 125000,
  "sales_cents": 120000,
  "fees_cents": -15000,
  "ads_cents": -5000,
  "refund_cents": 8000,
  "cancelled_cents": 2000,
  "recoup_cents": 0,
  "remitted_tax_cents": -3000,
  "gross_profit_cents": 102000,
  "cogs_cents": 30000,
  "net_profit_cents": 72000,
  "breakdown": {
    "fees": {
      "listing": -400,
      "listing_credits": 40,
      "transaction": -8125,
      "transaction_credits": 325,
      "processing": -4800,
      "processing_credits": 192,
      "shipping": -650,
      "shipping_credits": 0,
      "vat_on_fees": -1200,
      "vat_on_fees_credits": 0,
      "regulatory": -500,
      "regulatory_credits": 0,
      "other": 0,
      "total": -15118
    },
    "ads": {
      "etsy_ads": -3000,
      "offsite_ads": -1500,
      "offsite_ads_credits": 0,
      "subscription": -999,
      "total": -5499
    },
    "sales": {
      "gross_sales_cents": 125000,
      "refund_cents": 8000,
      "cancelled_cents": 2000,
      "remitted_tax": {
        "sales_tax": -2000,
        "sales_tax_credits": 200,
        "vat_ep": -1200,
        "vat_ep_credits": 0,
        "total": -3000
      }
    },
    "gross_profit": {
      "gross_sales_cents": 125000,
      "fees_cents": -15000,
      "ads_cents": -5000,
      "remitted_tax_cents": -3000,
      "total": 102000
    },
    "net_profit": {
      "gross_profit_cents": 102000,
      "cogs_cents": 30000,
      "total": 72000
    }
  }
}

// ── Response WITH hourly=true (single day) ──
{
  "shop_id": "12345678",
  "shop_name": "My Etsy Shop",
  "currency": "GBP",
  "start": 1716508800,
  "end": 1716595200,
  "balance_cents": 125430,
  "paid_out_cents": 12000,
  "sales_count": 5,
  "gross_sales_cents": 45000,
  "sales_cents": 43500,
  "fees_cents": -5400,
  "ads_cents": -1200,
  "refund_cents": 1500,
  "cancelled_cents": 0,
  "recoup_cents": 0,
  "remitted_tax_cents": 0,
  "gross_profit_cents": 38400,
  "cogs_cents": 10000,
  "net_profit_cents": 28400,
  "hourly": [
    {
      "hour": 0,
      "hour_start": 1716508800,
      "hour_end": 1716512400,
      "entry_count": 0,
      "sales_count": 0,
      "gross_sales_cents": 0,
      "sales_cents": 0,
      "fees_cents": 0,
      "ads_cents": 0,
      "remitted_tax_cents": 0,
      "refund_cents": 0,
      "cancelled_cents": 0,
      "recoup_cents": 0,
      "gross_profit_cents": 0,
      "cogs_cents": 0,
      "net_profit_cents": 0,
      "paid_out_cents": 0
    },
    { "hour": 1, "..." : "..." },
    { "hour": 2, "..." : "..." },
    "... (hours 3-8 omitted for brevity) ...",
    {
      "hour": 9,
      "hour_start": 1716541200,
      "hour_end": 1716544800,
      "entry_count": 4,
      "sales_count": 2,
      "gross_sales_cents": 18000,
      "sales_cents": 18000,
      "fees_cents": -2160,
      "ads_cents": -600,
      "remitted_tax_cents": 0,
      "refund_cents": 0,
      "cancelled_cents": 0,
      "recoup_cents": 0,
      "gross_profit_cents": 15240,
      "cogs_cents": 4000,
      "net_profit_cents": 11240,
      "paid_out_cents": 0
    },
    "... (hours 10-23) ..."
  ]
}
```

### Errors

Failures answer with the shared error body described in the Errors section: `{"error": {"code", "status", "message", "hint", "docs"}}`. Branch on `code`, not on the message.


## POST /api/v1/finance/{shop_id}/sync

Re-fetch finance data from Etsy

- Operation ID: `finance_sync`
- Slug: `finance-sync`
- Category: Finance
- Authentication: API key only. Reads or writes data Eto holds for your own account.
- HTML: https://eto.tools/dev/docs/#ep-finance-sync
- Markdown: https://eto.tools/dev/docs/finance/finance-sync.md

Re-fetches ledger data from the Etsy API and saves it to the Eto database. After syncing, GET /finance/{shop_id} returns the updated numbers.

━━━ MODES ━━━

EASIEST — send { "period": "today" } (or yesterday/last_7_days/last_30_days/this_month/last_month), or { "date": "2026-06-01" }, or { "start_date": "...", "end_date": "..." }. The server resolves the window (shop timezone, override with "timezone") and re-fetches it — no unix math. Single day → day mode, multi-day → range mode. The response echoes a `range` object with the exact window + server time.

1. Incremental — send empty body or {}.
   Fetches only new entries since last sync. Fast, safe to call often.
   Use for: keeping data fresh on a schedule (e.g. every 15 min).
   Returns: new_entries, total_fetched, affected_days.

2. Single day — send { "day_start": unix_ts }.
   Re-fetches ALL entries for that day. Replaces stale data.
   Optionally include day_end (defaults to day_start + 86399).
   Use for: a day that looks wrong or incomplete.
   Returns: day_start, day_end, new_entries, total_fetched.

3. Date range — send { "start": unix_ts, "end": unix_ts }.
   Deletes existing entries in the range, re-fetches from Etsy, recomputes summaries.
   Use for: full reconciliation of a period. Heaviest mode.
   Returns: start, end, deleted_stale, new_entries.

━━━ WHAT YOU GET BACK ━━━

success — true if sync completed
mode — "incremental", "day", or "range"
new_entries — number of new ledger entries saved
total_fetched — total entries fetched from Etsy (incremental + day modes)
affected_days — list of day timestamps that had changes (incremental mode only)
deleted_stale — entries deleted before re-fetch (range mode only)
day_start / day_end — the day that was synced (day mode only)
start / end — the range that was synced (range mode only)

━━━ NOTES ━━━

This is the ONLY finance endpoint that makes Etsy API calls.
Counts toward your Eto rate limit AND Etsy API cost billing.
If the store's OAuth token expired, returns STORE_TOKEN_EXPIRED.
If Etsy's API is down, returns UPSTREAM_ERROR with detail.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `shop_id` | path | `integer` | yes | Your Etsy shop ID |
| `period` | body | `string` | no | EASIEST — resolve a window server-side and re-fetch it: today, yesterday, last_7_days, last_30_days, this_month, last_month. No timestamps. |
| `date` | body | `string` | no | Re-fetch one calendar day "YYYY-MM-DD" (resolved in the shop/timezone). |
| `start_date` | body | `string` | no | Range mode — first day "YYYY-MM-DD" (inclusive). |
| `end_date` | body | `string` | no | Range mode — last day "YYYY-MM-DD" (inclusive). |
| `timezone` | body | `string` | no | IANA timezone for resolving period/date (default: shop timezone). |
| `day_start` | body | `integer` | no | Advanced single-day mode — unix timestamp of the day start. |
| `day_end` | body | `integer` | no | Advanced single-day mode — unix timestamp of the day end (default: day_start + 86399). |
| `start` | body | `integer` | no | Advanced range mode — start of range to re-fetch (unix seconds). |
| `end` | body | `integer` | no | Advanced range mode — end of range to re-fetch (unix seconds). |

### Request

```bash
# ── Incremental sync (get latest entries) ──
curl -X POST -H "X-Eto-API-Key: eto_your_key" \
  "https://eto.tools/api/v1/finance/12345678/sync"

# ── Re-fetch a single day ──
curl -X POST -H "X-Eto-API-Key: eto_your_key" \
  -H "Content-Type: application/json" \
  -d '{"day_start": 1716508800}' \
  "https://eto.tools/api/v1/finance/12345678/sync"

# ── Re-fetch a single day with explicit end ──
curl -X POST -H "X-Eto-API-Key: eto_your_key" \
  -H "Content-Type: application/json" \
  -d '{"day_start": 1716508800, "day_end": 1716595199}' \
  "https://eto.tools/api/v1/finance/12345678/sync"

# ── Re-fetch an entire date range ──
curl -X POST -H "X-Eto-API-Key: eto_your_key" \
  -H "Content-Type: application/json" \
  -d '{"start": 1716508800, "end": 1717113600}' \
  "https://eto.tools/api/v1/finance/12345678/sync"
```

```python
import requests

url = "https://eto.tools/api/v1/finance/12345678/sync"
headers = {"X-Eto-API-Key": "eto_your_key"}

response = requests.post(url, headers=headers, timeout=120)
response.raise_for_status()
print(response.json())
```

```javascript
const url = "https://eto.tools/api/v1/finance/12345678/sync";

const response = await fetch(url, {
  method: "POST",
  headers: {
    "X-Eto-API-Key": "eto_your_key",
  },
});
if (!response.ok) throw new Error(`Eto API ${response.status}`);
const data = await response.json();
```

### Response

```
// ── Incremental ──
{
  "success": true,
  "mode": "incremental",
  "new_entries": 12,
  "total_fetched": 15,
  "affected_days": [1716508800, 1716595200]
}

// ── Single day ──
{
  "success": true,
  "mode": "day",
  "day_start": 1716508800,
  "day_end": 1716595199,
  "new_entries": 22,
  "total_fetched": 22
}

// ── Date range ──
{
  "success": true,
  "mode": "range",
  "start": 1716508800,
  "end": 1717113600,
  "deleted_stale": 145,
  "new_entries": 152
}
```

### Errors

Failures answer with the shared error body described in the Errors section: `{"error": {"code", "status", "message", "hint", "docs"}}`. Branch on `code`, not on the message.


## GET /api/v1/finance/software-expenses

Get logged software expenses (subscriptions)

- Operation ID: `finance_software_expenses`
- Slug: `finance-software-expenses`
- Category: Finance
- Authentication: API key only. Reads or writes data Eto holds for your own account.
- HTML: https://eto.tools/dev/docs/#ep-finance-software-expenses
- Markdown: https://eto.tools/dev/docs/finance/finance-software-expenses.md

Returns the software expenses you logged on the Finance dashboard — the subscriptions under Finance → Settings → Subscriptions (Canva, Printify, Eto, …) — and exactly what they cost inside a date window. The same numbers as the dashboard's "Software Expense" card and its breakdown. Zero Etsy API calls — reads from the Eto database. Instant.

These are ACCOUNT-level, not per shop, so they are NOT included in GET /finance/{shop_id}. To get profit after software, subtract total_cents from the shop's net_profit_cents.

━━━ WHAT YOU GET BACK (always returned) ━━━

start / end — the resolved window (unix seconds)
range — exact window: timezone, local-ISO bounds, label, server time
currency — the currency total_cents is in: your common currency from Finance settings, else the single currency all subscriptions share, else null (mixed)
common_currency — the common currency configured in Finance → Settings, or null
total_cents — everything charged inside the window, converted to `currency`
totals_by_currency — { "USD": 2598, "GBP": 999 } raw sums per native currency
charge_count — number of individual charges inside the window
subscription_count — number of subscriptions logged (all of them, not just active in window)
subscriptions[] — one entry per logged subscription:
  id, name, amount_cents, currency, frequency (weekly | biweekly | monthly | onetime), start_date (YYYY-MM-DD), conversion_rate (native → common), amount_converted_cents, charges_in_range, total_in_range_cents (converted), next_charge_date (YYYY-MM-DD or null), issue (only present when the entry is broken and never bills — e.g. missing amount/date)
scope — reminder that these are account-level costs, not per shop
warnings[] — caveats, e.g. mixed currencies with no common currency set
hint — only when nothing is logged yet, with where to add subscriptions

All amounts are COSTS (money out) returned as POSITIVE cents. Divide by 100 for display.

━━━ HOW CHARGES ARE COUNTED ━━━

Each subscription bills from its start_date on its schedule: weekly every 7 days, biweekly every 14, monthly on the same day each month (clamped to shorter months — the 31st bills on the 30th/28th), onetime exactly once on start_date. A charge is counted when its calendar date falls inside the window. So "this month" for a $12.99 monthly subscription started on the 15th is 1299 cents; "last_7_days" may be 0 if the billing day is not in that week. Identical to the dashboard.

━━━ HOW TO PICK THE DATE WINDOW ━━━

EASIEST — pass period=this_month (the default), today, yesterday, last_7_days, last_30_days or last_month. Or start_date=YYYY-MM-DD & end_date=YYYY-MM-DD (inclusive calendar days). Add timezone=Europe/London to resolve the window in a specific zone — the default is your primary connected shop's timezone, else UTC. Raw unix start/end are accepted too (max 366 days). The response echoes the resolved window in `range`, so you never have to guess which days were counted.

━━━ OPTIONAL ━━━

include=charges — also return charges[]: every individual charge inside the window (date, subscription_id, name, frequency, amount_cents, currency, converted_cents), sorted by date. charges_truncated is true if the list was cut at 2,000 entries (totals are always complete).

━━━ NOTES ━━━

Read-only. Subscriptions are added and edited on the Finance dashboard (eto.tools/dashboard/finances → Settings → Subscriptions).
Only YOUR account's subscriptions are ever returned — there is no id to look up.
An empty subscriptions[] with total_cents 0 is a valid answer: nothing is logged yet (the response carries a hint saying so).

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `period` | query | `string` | no | EASIEST — today, yesterday, last_7_days, last_30_days, this_month, last_month. Resolved server-side. Default: this_month. Default: `this_month`. |
| `start_date` | query | `string` | no | First calendar day "YYYY-MM-DD" (inclusive). |
| `end_date` | query | `string` | no | Last calendar day "YYYY-MM-DD" (inclusive). Defaults to start_date. |
| `timezone` | query | `string` | no | IANA timezone used to resolve period/dates into calendar days (default: your primary connected shop's timezone, else UTC). |
| `include` | query | `string` | no | Set to "charges" to also list every individual charge in the window. |
| `start` | query | `integer` | no | Advanced — unix timestamp, range start (inclusive). Prefer period/start_date. |
| `end` | query | `integer` | no | Advanced — unix timestamp, range end (exclusive). Prefer period/end_date. |

### Request

```bash
# ── This month (default) ──
curl -H "X-Eto-API-Key: eto_your_key" \
  "https://eto.tools/api/v1/finance/software-expenses"

# ── Last month ──
curl -H "X-Eto-API-Key: eto_your_key" \
  "https://eto.tools/api/v1/finance/software-expenses?period=last_month"

# ── A specific window, in your own timezone ──
curl -H "X-Eto-API-Key: eto_your_key" \
  "https://eto.tools/api/v1/finance/software-expenses?start_date=2026-07-01&end_date=2026-07-31&timezone=Europe/London"

# ── This month + every individual charge ──
curl -H "X-Eto-API-Key: eto_your_key" \
  "https://eto.tools/api/v1/finance/software-expenses?period=this_month&include=charges"
```

```python
import requests

url = "https://eto.tools/api/v1/finance/software-expenses"
headers = {"X-Eto-API-Key": "eto_your_key"}

response = requests.get(url, headers=headers, timeout=120)
response.raise_for_status()
print(response.json())
```

```javascript
const url = "https://eto.tools/api/v1/finance/software-expenses";

const response = await fetch(url, {
  method: "GET",
  headers: {
    "X-Eto-API-Key": "eto_your_key",
  },
});
if (!response.ok) throw new Error(`Eto API ${response.status}`);
const data = await response.json();
```

### Response

```json
{
  "start": 1785542400,
  "end": 1788220800,
  "range": {
    "start": 1785542400,
    "end": 1788220800,
    "start_local": "2026-08-01T00:00:00+01:00",
    "end_local": "2026-09-01T00:00:00+01:00",
    "timezone": "Europe/London",
    "label": "this month (month-to-date)",
    "server_now_unix": 1787788800,
    "server_now_utc": "2026-08-23T00:00:00+00:00"
  },
  "currency": "GBP",
  "common_currency": "GBP",
  "total_cents": 2325,
  "totals_by_currency": { "USD": 1299, "GBP": 1299 },
  "charge_count": 2,
  "subscription_count": 3,
  "subscriptions": [
    {
      "id": "sub_1753000000000_ab12",
      "name": "Eto",
      "amount_cents": 1299,
      "currency": "GBP",
      "frequency": "monthly",
      "start_date": "2026-03-05",
      "conversion_rate": 1.0,
      "amount_converted_cents": 1299,
      "charges_in_range": 1,
      "total_in_range_cents": 1299,
      "next_charge_date": "2026-09-05"
    },
    {
      "id": "sub_1751000000000_cd34",
      "name": "Canva Pro",
      "amount_cents": 1299,
      "currency": "USD",
      "frequency": "monthly",
      "start_date": "2026-01-15",
      "conversion_rate": 0.79,
      "amount_converted_cents": 1026,
      "charges_in_range": 1,
      "total_in_range_cents": 1026,
      "next_charge_date": "2026-09-15"
    },
    {
      "id": "sub_1749000000000_ef56",
      "name": "Printify Premium",
      "amount_cents": 2499,
      "currency": "USD",
      "frequency": "monthly",
      "start_date": "2026-08-28",
      "conversion_rate": 0.79,
      "amount_converted_cents": 1974,
      "charges_in_range": 0,
      "total_in_range_cents": 0,
      "next_charge_date": "2026-08-28"
    }
  ],
  "scope": {
    "level": "account",
    "note": "Software expenses are logged per Eto account, not per shop, so they are NOT part of GET /api/v1/finance/{shop_id}. All amounts are costs (money out) returned as positive cents; subtract total_cents from a shop's net_profit_cents to get profit after software.",
    "manage_at": "https://eto.tools/dashboard/finances (Settings → Subscriptions)"
  },
  "warnings": []
}

// ── With include=charges (extra keys) ──
{
  "...": "...",
  "charges": [
    { "date": "2026-08-05", "subscription_id": "sub_1753000000000_ab12", "name": "Eto", "frequency": "monthly", "amount_cents": 1299, "currency": "GBP", "converted_cents": 1299 },
    { "date": "2026-08-15", "subscription_id": "sub_1751000000000_cd34", "name": "Canva Pro", "frequency": "monthly", "amount_cents": 1299, "currency": "USD", "converted_cents": 1026 }
  ],
  "charges_truncated": false
}

// ── Nothing logged yet ──
{
  "...": "...",
  "currency": null,
  "total_cents": 0,
  "subscription_count": 0,
  "subscriptions": [],
  "hint": "No software expenses are logged yet. Add them at eto.tools/dashboard/finances → Settings → Subscriptions and they will appear here."
}
```

### Errors

Failures answer with the shared error body described in the Errors section: `{"error": {"code", "status", "message", "hint", "docs"}}`. Branch on `code`, not on the message.
