# Integrations

Read stored OnlyFans, MYM and Fanvue data through one project-scoped API. All eight routes below are GET-only. Access depends on current feature eligibility and the explicit integrations.read scope. Existing keys gain no scopes automatically. This reference describes the implemented contract; it does not establish deployment or live account access.

## Start here

1. Create or edit a project API key in the [dashboard](https://dashboard.linkscale.to/mcp) and explicitly grant integrations.read.
2. List connections and follow pagination.next_cursor.
3. Inspect available_data, then read tracking-links, metrics, account, statements or fan-health using the returned connection ID.
4. Retain source freshness, currency, actual range and coverage with every number.

See [MCP](https://docs.linkscale.to/#tag/MCP) for the equivalent assistant workflow. The separate partner API at /api/partner/v1 has its own daily-statistics contract; do not use partner keys or partner account IDs here.

## Freshness and permissions

freshness.last_synced_at is the last successful source pull. observed_at only says when this response was served. A recent failed attempt does not refresh data. next_sync_at is scheduled, not guaranteed completion. Data may end at the stored watermark rather than today. available_data describes provider support, not imported data or permission to see money. Missing, disconnected or foreign connections are refused without revealing their existence. Revenue restrictions remain authoritative; never reconstruct hidden revenue from counters. Credentials, proxy details, account email, fan identities and raw worker errors are excluded.

## Common money reading

Every tracking row adds **`revenue_reading`**, the common money field across
OnlyFans, MYM and Fanvue. Existing fields remain compatible:

- `amount`, `currency`: the stored reading in its original currency; no conversion
  or cross-currency sum. Zero is a measurement; unknown/pending amounts are null.
- `basis`: `ledger` (attributed payments), `counter` (platform readings), or null
  when no authorized money reading is available.
- `scope`: `window`, `lifetime` for a platform counter, or `stored_history` for
  the ledger under the lifetime selector. OnlyFans' stored history must never
  be presented as the platform's lifetime revenue.
- `range`: the requested window's dates, null for lifetime/stored-history scope.
- `status`: `available`, `pending`, `unavailable`; `reason` preserves the mapping
  reason or says `awaiting_readings` / `not_available`.
- `floor`: null without an amount, true for a recovering map or a counter whose
  first reading is after the requested start. Other source limitations remain in
  the surrounding response; false is not a claim of independently audited income.

The response also preserves `revenue.since` (connection/tracking dates), row
`net_since_connection` / `net_since_tracking`, attribution checks, Fanvue
`sources` and `fan_join`, and the dated `subscriber_split`. These are imported
facts; no provider request or new attribution calculation happens on read.

Assistant tracking-link pages are ordered by `record_id` and may shrink to fit
the response budget. Follow **`page.next_offset`**, never `offset + limit`.
`page.total` counts the source reading, and totals/caveats stay intact on every
page. This is not a snapshot across sync runs: restart pagination if
`connection.freshness.last_synced_at` changes. Statements keep a fixed page size;
a response too large to preserve intact is refused with 413. Reduce `limit`
and restart statement pagination at page 0, or use REST. Fan health likewise
asks for fewer weeks rather than silently discarding weeks.

## Two pagination layers

REST connection lists use pagination.next_cursor -> cursor; MCP uses next_after -> after. Tracking-link source coverage is independent of assistant paging: sources without history retain a 300-record cap disclosed by coverage.has_more. MCP page.next_offset only pages through the returned source reading and can advance by less than the requested limit. Finishing those pages does not retrieve capped-out records; REST uses the same source cap. Never add repeated totals. Restart if last_synced_at changes during paging. Statements keep a fixed page size and advance page.index + 1; totals cover the entire filter.

## OnlyFans attribution boundary

OnlyFans joins stored subscriber mappings and transactions; it does not publish native per-link revenue counters. Mapping starts at a fixed connection-relative boundary; older stored mappings can survive. A settled map is not proof of complete historical attribution. Preserve revenue.since and reconciliation. MYM/Fanvue use imported counter differences; the first reading is a baseline, and a first reading after the reporting start makes revenue a floor. Compare only matching currencies, scopes, date ranges and bases.

### Current cadence and remaining work

| Policy | Base interval | Additional jitter | Minimum private interval |
| --- | --- | --- | --- |
| OnlyFans | 10h | Up to 1.5h | 6h |
| MYM | 12h | None | 12h (operational attempt floor) |
| Fanvue | 2h; 1h on Agency | None | 1h |
| Partner `stats_daily` (OnlyFans/MYM) | 24h | Up to 2h | Provider floor; profile restrictions remain |

Fanvue counters have a separate 6h clock, increased if the resolved refresh
interval is slower. Cadence is scheduling policy, not a completion SLA.
`next_sync_at` is scheduled; failures, cooldowns, leases and processing can delay
completion. Expired or invalid private grants resolve to ordinary policy.
Default service has not been moved to 24h globally.

Private access never adds datasets or revenue permission. The admin writer,
audit/concurrency workflow and grant/revoke UI remain unfinished; no REST/MCP
tool provisions private offers. Fanvue serializers do not consistently receive
the current project plan, although scheduling does: do not infer the paid tier
from the reported interval or promise hourly service from it.

### Access and size troubleshooting

- Missing tool or 403: check explicit `integrations.read`, current feature
  eligibility, project membership, account rights and disconnected state.
  `integrations.manage` alone does not grant reads. A refusal is not evidence
  that the provider lacks revenue support.
- Authorized connection but 404 resource: inspect `available_data` before
  retrying. A provider may not support that record type.
- 400: correct IDs, windows, date ranges or unsupported/duplicate arguments.
- MCP 413: reduce statement `limit` and restart at page 0, reduce fan-health
  `weeks`, or use REST for an oversized tracking reading. A single oversized
  tracking row cannot be repaired by requesting fewer rows.
- Metrics `transport_limited`: daily/breakdown rows were omitted to fit; totals
  still cover the returned range. Narrow `days`, `top` or `breakdowns`, or use
  REST. Do not recompute totals from partial arrays.
- 429: respect the retry delay. An uncertain management response calls for a
  status read before retrying; reading stale data never authorizes a sync.
