Architecture & Security

Architecture & Security

A conceptual overview of how Pharlo is built and how it protects your accounts and data — for developers and technical evaluators deciding whether to integrate.

This page describes the system at a conceptual level. It intentionally omits internal hostnames, secret formats, and implementation details that would not help an integrator.

High-level

Pharlo is a content posting platform with two integration surfaces over one backend:

  • REST API — for backend and automation use (Symfony 7, PHP 8.3).
  • MCP server — for AI agents, exposed at https://pharlo.io/_mcp over Streamable HTTP.

Both surfaces resolve to the same domain model (assignments, connections, platforms, stats, webhooks) and share authorization, billing, and platform-delivery logic.

Authentication & authorization

SurfaceAuth
REST APIAPI key as a Bearer token (ds_live_...).
MCP server (/_mcp)OAuth 2.0 Bearer (authorization code + PKCE, dynamic client registration).
Web app / consoleSession auth (JWT + Clerk).
  • MCP OAuth issues short-lived access tokens (~1 hour) and rotating refresh tokens (~30 days). The ds_live_... key is entered only on the consent screen; it is not sent as a header to /_mcp.
  • Scopes restrict what a token can do (e.g. assignments:read, assignments:write, connections:read, stats:read). Clients request only the scopes they need.
  • Discovery is standards-based via /.well-known/oauth-authorization-server and /.well-known/oauth-protected-resource.

Platform connections

  • Connecting a channel runs an OAuth consent flow with the platform (Google/YouTube, Facebook).
  • Platform tokens are stored server-side and refreshed automatically; they are never returned to clients. When a token can no longer be refreshed, the connection is flagged and the user re-authorizes.

Multi-tenancy

  • Work is scoped to a workspace/organization, resolved per request (e.g. from an organization header).
  • A request only ever sees the connections, assignments, and stats of its own workspace.

Publishing pipeline

  • A publish request becomes an assignment that moves through a lifecycle (pending → uploading → uploaded → processing → published, or failed/cancelled).
  • Large local media uses resumable upload sessions instead of inline transfer; publishing happens once the session completes.
  • Delivery to platforms runs asynchronously through a message queue, with retry for failed assignments (without recreating them).

Webhooks

  • Outbound webhooks notify your endpoint of events (e.g. publish results).
  • Each delivery is signed with HMAC-SHA256 over the raw JSON body and sent in the X-Webhook-Signature-256 header as sha256=<hex>. Verify it with a constant-time comparison — see the webhook debugging playbook and the examples in the repo.

Analytics

  • Stats are collected through an analytics pipeline (ClickHouse) and exposed via the stats tools and endpoints. Live-fetch calls read from the platform APIs; cheaper calls serve cached snapshots.

Billing & metering

  • Usage is metered in credits. Read-only and discovery calls are cheap or free; writes and live fetches cost more. MCP calls carry a surcharge over equivalent REST calls.
  • When credits run out, the API returns insufficient_credits (HTTP 402); a configured spending cap returns user_spending_limit_exceeded (HTTP 402). See the error catalog (opens in a new tab).

Reliability & observability

  • Asynchronous delivery, automatic token refresh, and idempotency support (idempotencyKey) reduce duplicate publishes and transient failures.
  • The platform is monitored with standard observability tooling (metrics, logs, alerting).

Data handling

  • Platform credentials are stored server-side and never exposed to API/MCP clients.
  • Agents and integrators should likewise never log or echo API keys or OAuth tokens — see Agent Rules.

Related docs

MCP setup · Tool reference · Agent Rules · Error Handling