# Re-fetch finance data from Etsy

> Eto API reference for `POST /api/v1/finance/{shop_id}/sync`. Base URL `https://eto.tools/api/v1/`. Authenticate with the `X-Eto-API-Key` header.

Part of the [Eto API reference](https://eto.tools/dev/docs/). The full reference in one file is at [llms-full.txt](https://eto.tools/dev/docs/llms-full.txt); the machine-readable schema is at [openapi.json](https://eto.tools/dev/docs/openapi.json).

## 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.
