# Desktop → Server Sync API

How the desktop app keeps the web portal's mirrors fresh. All endpoints live
under `/api`, authenticated with the Sanctum bearer token the app already uses
for `/me/usage`, and require a verified account.

The portal (customer dashboard, Queue, Accounts pages, admin panels) renders
whatever these endpoints have reported. Until an endpoint is called, the pages
show the aggregate counts from `/me/usage` with an "appears after sync" note.

Recommended cadence: after every publish cycle, and at minimum every few
minutes while the app runs. All three syncs are idempotent — sending the same
payload twice is harmless.

---

## 1. `POST /me/usage` — aggregate counters (already implemented)

```json
{
  "accounts": {"facebook": 5, "tiktok": 2},
  "posts_today": 42,
  "posts_month": 830,
  "scheduled": 17,
  "queue": 6
}
```

Optional extension for Cloud-edition parity: `storage_used_mb` (integer) is
accepted on the user_usages row for permanent uploads.

---

## 2. `POST /me/accounts` — connected social accounts (NEW)

Full-state mirror of the app's connected accounts. Upserted by `desktop_id`;
accounts missing from the payload are deleted server-side.

```json
{
  "items": [
    {
      "desktop_id": "fb-page-88231",        // required, unique per user — use the platform page/channel id
      "platform": "facebook",               // required: tiktok|facebook|youtube|instagram|x|linkedin|pinterest
      "username": "acme.page",              // required — handle shown as @username
      "display_name": "Acme Media",         // optional
      "role": "destination",                // optional: source|destination|both (default destination)
      "status": "connected",                // optional: connected|token_expired|error|disabled
      "error": null,                        // optional, shown on error status
      "token_expires_at": "2026-09-20",     // optional — drives "reconnect soon" warnings
      "connected_at": "2026-07-01T10:00:00Z"
    }
  ]
}
```

Response:

```json
{ "ok": true, "disconnected": ["fb-page-77410"] }
```

`disconnected` lists accounts the customer disconnected **on the web**. The app
must disconnect them locally and omit them from its next payload — the server
keeps them flagged (never resurrects them from a sync) until the app drops
them, at which point they are pruned.

Limit: 500 items per request.

---

## 3. `POST /me/queue` — publishing queue (NEW)

Full-state mirror of the app's queue, same contract: upsert by `desktop_id`,
prune what's missing.

```json
{
  "items": [
    {
      "desktop_id": "job-19442",             // required, unique per user
      "title": "Morning clip roundup",       // required — post caption/label
      "platform": "instagram",               // required — target platform
      "status": "waiting",                   // required: waiting|publishing|published|failed
      "scheduled_for": "2026-07-23T10:00:00Z",
      "attempts": 1,
      "error": null,                         // set on failed
      "published_at": null,                  // set on published — drives analytics day-series
      "automation_desktop_id": "wf-7"        // links the job to its workflow (see below)
    }
  ]
}
```

Response:

```json
{ "ok": true, "retried": ["job-18990"] }
```

`retried` lists failed jobs the customer re-queued **on the web** (status was
reset to `waiting` server-side). The app should retry them before its next
sync overwrites the mirror.

Notes:
- Keep `published` items in the payload for at least 90 days if possible —
  the Analytics page builds its per-day publishing chart and platform
  distribution from `published_at`. Items dropped from the payload disappear
  from the mirror (and from analytics).
- Limit: 500 items per request.

## Automations linkage

The web portal creates/edits automations (table `automations`). To link queue
jobs and report run stats, the app should:

1. Fetch its automations from the portal (no endpoint yet — currently created
   on the web with `desktop_id = null`; the app can adopt one by syncing its
   own workflow id into `desktop_id`... endpoint TBD in a later phase).
2. Meanwhile, set `automation_desktop_id` on queue items to its workflow id —
   jobs link automatically once `automations.desktop_id` matches.

Web-side actions the app must honor on sync:
- Automation `status` toggled between `running`/`paused` on the web.
- Automation rows deleted on the web.
