FPDP API Contract (English)

1. Scope and status

This document defines the intended HTTP contract for the FPDP MVP. It is a design contract; the current repository does not yet implement most routes. The machine-readable source is <code>openapi.yaml</code>.

Base path: /api/v1

Authentication:

- public reads do not require authentication;

- owner/admin operations use Authorization: Bearer <token>;

- payment webhooks use provider-specific signatures and must not use user bearer tokens.

Content types: application/json, except webhook bodies which may be provider-specific JSON.

2. Common conventions

- IDs exposed by the API are UUID strings.

- Timestamps use RFC 3339 UTC values.

- Money is represented as a decimal string plus an ISO 4217 currency code to prevent floating-point errors.

- Collection responses use cursor pagination: data, meta.next_cursor, and meta.has_more.

- Mutable resources return an ETag when optimistic concurrency is supported.

- All content records identify source_type, source_provider, and canonical_url.

- Retrying a create-payment request requires the same Idempotency-Key header.

Standard success envelope:

{"data": {}, "meta": {"request_id": "req_..."}}

Standard error envelope:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The request is invalid.",
    "details": [{"field": "email", "reason": "invalid_format"}],
    "request_id": "req_..."
  }
}

3. Endpoint list

System and authentication

| Method | Path | Auth | Purpose |

|---|---|---|---|

| GET | /health | Public | Liveness and dependency summary |

| POST | /auth/register | Public | Register an owner and initial node |

| POST | /auth/login | Public | Exchange credentials for access token |

| POST | /auth/logout | Bearer | Revoke the current token |

| GET | /me | Bearer | Return authenticated user and node context |

Profiles and content

| Method | Path | Auth | Purpose |

|---|---|---|---|

| GET | /profiles/{handle} | Public | Read a public profile |

| PATCH | /me/profile | Bearer | Update the owner's profile |

| GET | /posts | Public | List posts with source/visibility filters |

| POST | /posts | Bearer | Create a local post |

| GET | /posts/{postId} | Public | Read a visible post |

| PATCH | /posts/{postId} | Bearer | Update an owned local post |

| DELETE | /posts/{postId} | Bearer | Soft-delete an owned local post |

Post writes accept up to 10 ordered media metadata items (IMAGE, VIDEO, AUDIO, or FILE). Media URLs must be absolute HTTPS URLs without embedded credentials; alternative text is limited to 500 characters. Supplying media in a PATCH replaces the complete media list atomically.

| GET | /timeline | Public/optional Bearer | Return normalized local, external, and federated content |

External sources

| Method | Path | Auth | Purpose |

|---|---|---|---|

| GET | /external-sources | Bearer | List owner connections and sync health |

| POST | /external-sources/test | Bearer | Validate and preview a source without saving it |

| POST | /external-sources | Bearer | Save and schedule a source |

| GET | /external-sources/{sourceId} | Bearer | Read source configuration and health |

| PATCH | /external-sources/{sourceId} | Bearer | Change interval, visibility, or enabled state |

| DELETE | /external-sources/{sourceId} | Bearer | Disconnect a source |

| POST | /external-sources/{sourceId}/sync | Bearer | Queue an on-demand synchronization |

Products, orders, and payments

| Method | Path | Auth | Purpose |

|---|---|---|---|

| GET | /products | Public | List available products |

| POST | /products | Bearer | Create a local product |

| GET | /products/{productId} | Public | Read product details |

| PATCH | /products/{productId} | Bearer | Update an owned product |

| POST | /orders | Public/optional Bearer | Create an order and immutable total snapshot |

| GET | /orders/{orderId} | Bearer/order token | Read an order |

| POST | /orders/{orderId}/payments | Bearer/order token | Create a payment attempt; requires Idempotency-Key |

| GET | /payments/{paymentId} | Bearer/order token | Read normalized payment state |

| POST | /payments/{paymentId}/cancel | Bearer/order token | Cancel a pending payment |

| POST | /payments/{paymentId}/refunds | Admin Bearer | Request full or partial refund |

| POST | /webhooks/payments/{gatewayCode} | Signature | Receive and normalize provider events |

Administration and federation

| Method | Path | Auth | Purpose |

|---|---|---|---|

| GET | /admin/payment-gateways | Admin Bearer | List gateway capabilities and configuration status |

| PUT | /admin/payment-gateways/{gatewayCode} | Admin Bearer | Configure and activate a gateway |

| GET | /admin/integration-jobs | Admin Bearer | Inspect synchronization queue and failures |

| POST | /admin/integration-jobs/{jobId}/retry | Admin Bearer | Retry a failed job |

| GET | /.well-known/fpdp | Public | Discover node identity and capabilities |

4. Important behavior

Source test and connection

POST /external-sources/test performs a bounded server-side fetch, blocks private/reserved network targets, applies timeouts and size limits, and returns normalized preview items. Saving a source is a separate explicit action.

Timeline provenance

Every item contains:

- source_type: LOCAL, EXTERNAL, or FEDERATED;

- source_provider: e.g. FPDP, RSS, ATOM, CUSTOM_API;

- canonical_url: authoritative original URL;

- author identity and publication timestamp.

Payment idempotency and webhooks

- A client reuses one Idempotency-Key for retries of the same logical payment request.

- The server returns the original result when the key and payload match.

- A reused key with a different payload returns 409 IDEMPOTENCY_CONFLICT.

- Webhooks are verified before processing and deduplicated using provider + event ID.

- A valid duplicate webhook returns 200 without applying the transition twice.

Suggested status codes

| Status | Meaning |

|---|---|

| 200 | Read/update success or accepted duplicate webhook |

| 201 | Resource created |

| 202 | Async synchronization/refund accepted |

| 204 | Logout or deletion completed |

| 400 | Malformed request |

| 401 | Missing/invalid authentication or webhook signature |

| 403 | Authenticated but not authorized |

| 404 | Resource unavailable or not visible |

| 409 | State or idempotency conflict |

| 422 | Semantic validation failure |

| 429 | Rate limit exceeded |

| 502 | Upstream provider failure |