GhostfeedGhostfeed MCPv5.3.0

Concepts

Authentication, the response envelope, workspaces, money, realtime, generations, and the REST API.

Authentication

Ghostfeed is both the authorization server and the protected resource. Any OAuth-capable MCP client discovers everything from a 401 response; you never copy keys around.

  1. An unauthenticated request returns 401 with a WWW-Authenticate header pointing at the discovery documents (/.well-known/oauth-authorization-server).
  2. The client registers itself (Dynamic Client Registration), then opens the browser: Google sign-in, then Ghostfeed's consent screen.
  3. On consent you choose the workspace grant (all or specific) and one default workspace for read-only calls.
  4. The client exchanges the authorization code (PKCE S256) for tokens.
TokenPrefixLifetimePurpose
Accessgf_at_1 hourSent as Authorization: Bearer gf_at_... on every call
Refreshgf_rt_60 daysExchanged for new access tokens; single use (rotation)

Tokens are opaque. The server stores only HMAC fingerprints, so a leaked database contains nothing usable. Refresh tokens rotate on every use. Reusing a consumed refresh token revokes the whole grant (theft detection).

Auth failures use the envelope below with code: "unauthorized" and a remediation string: an expired access token says to refresh, a revoked credential says to re-authenticate.

The response envelope

Every /api/v2 response and every MCP tool result is one of two shapes:

Success
{ "success": true, "requestId": "...", "data": {} }
Error
{
  "success": false,
  "requestId": "...",
  "error": {
    "code": "insufficient_credits",
    "message": "Not enough credits.",
    "status": 402,
    "retryable": false,
    "remediation": "Top up credits and retry. Check the balance with get_credits."
  }
}

code is a small closed set, projected straight from the server's own definitions:

codeHTTPretryablemeaning
validation_failed400noInput failed validation; fieldErrors lists the problems per field.
unauthorized401noMissing, expired, or revoked credential.
forbidden403noValid credential, but no access to this workspace or resource.
not_found404noNo such resource in this workspace.
insufficient_credits402noCredit balance is too low for this operation.
approval_required402yesReserved for operations that will require explicit human approval (none today).
idempotency_conflict409noThis idempotency key was already used with a different request.
rate_limited429yesToo many requests; back off and retry.
provider_error502yesAn upstream provider failed; charges for failed work are refunded.
internal_error500yesUnexpected server error; details are logged server-side, never leaked.

Unexpected server errors are logged internally and returned as internal_error; internal details never leak into a response. remediation tells an agent what to do next. requestId is the handle for support.

Workspaces

A workspace is the content boundary. The credential stores two separate facts: the grant (the workspaces it may reach) and one immutable default used when a read omits its target. A read can explicitly override the default with the per-call workspace parameter or X-Workspace-Id, but only inside the grant. Every workspace-scoped write must name its target workspace on the call itself; writes never inherit the default. Dashboard navigation is not part of this decision.

Both doors enforce this the same way, so an agent learns one habit. MCP takes workspace as a required tool parameter. REST takes it in the JSON body, or as a ?workspace= query param on a bodyless call like a DELETE. The connection-wide X-Workspace-Id header is a read convenience only — it is not accepted for writes, because a stale connection header is exactly how a multi-workspace agent would write into the wrong workspace. Name the target on the call.

Agents discover stable ids and slugs with list_workspaces. Names are not unique; when two workspaces share one, the error lists the matching ids so the agent can retry unambiguously.

A credential granted specific workspaces can never reach outside them, and live access is re-checked on every request: leaving a team closes access immediately, valid token or not. Two agents in different workspaces never see each other's content.

Money

Paid operations charge credits when the work starts and refund automatically on failure, including partial refunds when a provider delivers fewer images than requested. Every paid result carries creditsSpent and creditsRemaining. Free operations (approve, rename, list) never touch the balance.

Retries are safe: pass an idempotencyKey and a repeated call reuses the original charge instead of billing twice.

Realtime

Mutations emit workspace-scoped events the dashboard listens to over SSE. An avatar created by an agent appears in an open dashboard in realtime, no refresh. Events are invalidation hints only ({workspaceId, topic}); no content rides the stream.

Generations (async work)

Long-running work (video, upcoming verticals) returns a gen_... id immediately. Poll get_generation until the state is terminal: queued → running → succeeded | failed, plus canceled and needs_action. Avatars generate synchronously and return their drafts directly, no polling.

This normalized vocabulary is the one contract every vertical answers in, even when the underlying record uses its own words. A slideshow variant, for instance, stores pending | processing | ready | failed; the generation ledger maps that to queued | running | succeeded | failed so a single polling loop works across product types. The mapping is enforced in code and pinned by a mirror test, so get_slideshow's per-variant status (ready) and get_generation's normalized state (succeeded) describe the same deck by design, never by coincidence.

REST

Every tool has an HTTP twin under /api/v2, same bearer token, same envelope:

Terminal
curl https://api.ghostfeed.ai/api/v2/me \
  -H "Authorization: Bearer gf_at_..."

Each entry in the MCP docs shows the twin's exact HTTP method and path. Both adapters call the same operation, so the two doors are never allowed to drift: they share one input schema, one response envelope, and one set of error codes. A canonical manifest and a bidirectional CI test reject an MCP-only tool, a REST-only endpoint, or a method/path mismatch, and the same mirror tests that pin credit costs and layout constants pin the envelope shape. Pick the door your runtime prefers; the contract is identical on both.

For REST, ids shown as :id, :slideshowId, or :slideIndex belong in the URL. Remaining read inputs use query parameters; POST, PUT, and PATCH inputs use JSON. POST /pinterest/search is a read-style search whose array input is JSON. A workspace-scoped write names workspace in the JSON body, or as a ?workspace= query param on a bodyless DELETE (the X-Workspace-Id header is for reads only, see Workspaces above); reads may use workspace in the query or the header.