# Amplify Scheduler Developer API

This document describes the developer-facing API contracts for the Amplify Scheduler platform, a unified social media management API.

## Developer Platform Summary

Amplify Scheduler is designed to be used by external applications as a **proper developer platform**, not just an internal integration. Each customer receives profile-scoped API keys, paid access is enforced, and external apps can create, schedule, inspect, and manage posts through versioned endpoints.

## Overview

Amplify Scheduler provides a backend-first social media management platform with:
- **Profile-scoped resources** — all data is organized under user profiles
- **Unified platform abstraction** — consistent API across all social platforms
- **Backend-owned OAuth** — connections are managed server-side
- **Normalized post model** — single post can target multiple platforms
- **Scheduling** — schedule posts for future delivery

## Base URL

```
https://amplify-schedule.com/api/v1
```

Or your custom domain if configured.

## API Discovery

Use the root discovery endpoint to inspect the current platform contract:

```
GET /api/v1
```

**Response includes:**
- API version
- auth header name
- core endpoint list
- billing requirements
- platform capabilities
- docs link

## Authentication

All API requests require an API key passed in the `X-API-Key` header:

```
X-API-Key: your_api_key_here
```

API keys are scoped to a profile. Create/manage keys in the browser UI through `/api/api-keys` after subscription is active.

## Billing Requirement (Free Tier + No Free Trial on Paid Plans)

Amplify Scheduler supports a Free tier managed entirely in Supabase, plus paid Stripe-backed plans:

- Free tier includes 2 profiles, 25 posts per month, and analytics.
- Free tier does not include API keys or webhooks.
- Users must have an active paid subscription before creating API keys.
- API requests using `X-API-Key` return `402` when subscription is inactive.
- There is no free trial on paid plans.

Stripe environment variables required on the backend:

```
STRIPE_SECRET_KEY=...
STRIPE_PRICE_ID_STARTER=price_1TLLksAHVlcpHu3BPH1NoBSw
STRIPE_PRICE_ID_GROWTH=price_1TLLmCAHVlcpHu3BV2vmPNZY
STRIPE_PRICE_ID_AGENCY=price_1TLLn6AHVlcpHu3B34tzraTY
STRIPE_PRICE_ID_UNLIMITED=price_1TLLoZAHVlcpHu3BUyq1RnnY
STRIPE_WEBHOOK_SECRET=whsec_...
APP_URL=https://classy-chaja-99384d.netlify.app
```

Browser billing endpoints:

- `GET /api/billing/subscription-status`
- `POST /api/billing/create-checkout-session`
- `POST /api/billing/create-portal-session`
- `POST /api/billing/webhook`

## Webhooks

Amplify Scheduler supports customer-configured webhooks for post lifecycle events and delivery tracking.

Available management endpoints:

- `GET /api/webhook-endpoints`
- `POST /api/webhook-endpoints`
- `PATCH /api/webhook-endpoints/:webhookId`
- `DELETE /api/webhook-endpoints/:webhookId`
- `GET /api/webhook-endpoints/:webhookId/deliveries`
- `POST /api/webhook-deliveries/:deliveryId/retry`

Typical event types include:

- `post.published`
- `post.failed`
- delivery retry/status events

## Pricing (GBP)

All plans are billed monthly in GBP. The Free tier does not require Stripe.

### Core Plans

- **Free — £0/month**
  - 2 profiles
  - 25 posts per month
  - Advanced analytics included
  - No API keys
  - No webhooks

- **Starter — £19/month**
  - 10 profiles
  - 150 scheduled posts per month
  - 10,000 API requests per month
  - Advanced analytics included

- **Growth — £49/month**
  - 50 profiles
  - Unlimited posts (fair use)
  - 100,000 API requests per month
  - Advanced analytics included

- **Agency — £99/month**
  - 100 profiles
  - Unlimited posts (fair use)
  - 500,000 API requests per month
  - Advanced analytics included

- **Unlimited — £299/month**
  - Unlimited profiles
  - Unlimited posts
  - Unlimited API requests
  - Advanced analytics included

## Free Tier Setup

Free users are seeded in Supabase by the `on_auth_user_created_billing` trigger, which inserts a `billing_subscriptions` row with `status = 'free'`. No Stripe checkout is required for the Free plan.

### Add-ons

- Extra profile pack: **£8/profile/month**
- White-label: **£49/workspace/month**

## API Key Management

### List API Keys

```
GET /api/v1/api-keys
```

**Response:**
```json
{
  "apiKeys": [
    {
      "id": "key_uuid",
      "name": "Production Key",
      "key": "...abc12345",
      "profile_id": "profile_uuid",
      "status": "active",
      "created_at": "2024-01-01T00:00:00.000Z",
      "last_used_at": "2024-01-15T10:30:00.000Z"
    }
  ]
}
```

### Create API Key

```
POST /api/v1/api-keys
```

**Request:**
```json
{
  "name": "My Integration Key",
  "profileId": "profile_uuid"
}
```

**Response:**
```json
{
  "success": true,
  "apiKey": {
    "id": "key_uuid",
    "name": "My Integration Key",
    "key": "ak_abc123def456...",
    "profileId": "profile_uuid",
    "status": "active",
    "createdAt": "2024-01-01T00:00:00.000Z"
  },
  "warning": "Save this key now. It will not be shown again."
}
```

> **Important:** The full API key is only returned once at creation. Store it securely.

## External Integration Flow

For third-party apps, the recommended flow is:

1. Create or select a profile
2. Connect the required social accounts
3. Create a paid subscription
4. Create an API key for the profile
5. Send `content`, `media`, and `scheduleDate` to `/api/v1/post`
6. Optionally poll `/api/v1/scheduled-posts` or subscribe to webhooks

Example post request:

```json
{
  "platform": "facebook",
  "content": "Final copy from the external app",
  "scheduleDate": "2026-04-12T09:00:00Z",
  "media": [
    {
      "public_url": "https://example.com/image.jpg",
      "mime_type": "image/jpeg",
      "file_name": "image.jpg"
    }
  ]
}
```

### Revoke API Key

```
DELETE /api/v1/api-keys/{keyId}
```

**Response:**
```json
{
  "success": true,
  "message": "API key revoked"
}
```

## Platform Capabilities

### Platform Catalog

Retrieve the complete platform catalog with capability contracts:

```
GET /api/v1/platforms
```

**Response:**
```json
{
  "platforms": [
    {
      "platform": "twitter",
      "displayName": "Twitter / X",
      "accountType": "social_profile",
      "supportStatus": "partial",
      "scopes": ["tweet.read", "tweet.write", "users.read", "offline.access"],
      "publish": {
        "supported": true,
        "text": true,
        "images": true,
        "video": false,
        "requiresMedia": false,
        "supportedMediaTypes": ["image/*"],
        "scheduling": true
      },
      "analytics": {
        "supported": false
      },
      "notes": ["Current publishing path supports text and single-image posts."]
    }
  ]
}
```

### Support Status Values

- `full` — Complete implementation with all claimed features working
- `partial` — Working implementation with some limitations
- `unsupported` — Not implemented or not feasible with current APIs

## Connections

### List Connections

Retrieve connected accounts for the API key's profile:

```
GET /api/v1/connections
```

**Response:**
```json
{
  "connections": [
    {
      "id": "twitter_abc123",
      "platform": "twitter",
      "username": "@handle",
      "externalAccountId": "123456789",
      "externalAccountName": "@handle",
      "connectionStatus": "connected",
      "capabilities": {
        "text": true,
        "images": true,
        "video": false,
        "scheduling": true,
        "analytics": false
      },
      "contract": {
        "displayName": "Twitter / X",
        "supportStatus": "partial",
        "publish": {
          "supported": true,
          "scheduling": true
        },
        "notes": ["Current publishing path supports text and single-image posts."]
      },
      "readiness": {
        "status": "ready",
        "canPublish": true,
        "canSchedule": true,
        "missingRequirements": [],
        "warnings": []
      }
    }
  ]
}
```

### Initiate OAuth Connection

Start the OAuth flow for a platform:

```
POST /api/v1/connect/init
```

**Request:**
```json
{
  "platform": "twitter",
  "returnUrl": "https://your-app.com/callback"
}
```

**Response:**
```json
{
  "url": "https://twitter.com/i/oauth2/authorize?...",
  "error": null
}
```

Redirect the user to this URL. After authorization, the user will be redirected to your `returnUrl`.

### Disconnect Account

Remove a connected account:

```
DELETE /api/v1/connections/{connectionId}
```

**Response:**
```json
{
  "success": true
}
```

### Sync Connection

Refresh token and metadata for a connection:

```
POST /api/v1/connections/{connectionId}/sync
```

**Response:**
```json
{
  "connection": {
    "id": "twitter_abc123",
    "platform": "twitter",
    "connectionStatus": "connected",
    "readiness": {
      "status": "ready",
      "canPublish": true
    }
  }
}
```

## Media

### Create Upload URL

Get a signed upload URL for media:

```
POST /api/v1/media/upload-url
```

**Request:**
```json
{
  "profileId": "profile_uuid",
  "fileName": "photo.jpg",
  "contentType": "image/jpeg",
  "fileSize": 1234567
}
```

**Response:**
```json
{
  "upload": {
    "bucket": "post-media",
    "path": "user_uuid/profile_uuid/media_uuid-photo.jpg",
    "token": "upload_token_here",
    "signedUrl": "https://..."
  },
  "media": {
    "id": "media_uuid",
    "post_id": "",
    "file_path": "user_uuid/profile_uuid/media_uuid-photo.jpg",
    "public_url": "https://your-cdn.com/post-media/...",
    "mime_type": "image/jpeg",
    "file_size": 1234567,
    "file_name": "photo.jpg",
    "sort_order": 0,
    "created_at": "2024-01-01T00:00:00.000Z"
  }
}
```

### Upload Media

Upload the file to the signed URL:

```
PUT {signedUrl}
Content-Type: image/jpeg

[binary file data]
```

### Delete Media

Remove uploaded media:

```
DELETE /api/v1/media
```

**Request:**
```json
{
  "profileId": "profile_uuid",
  "filePath": "user_uuid/profile_uuid/media_uuid-photo.jpg"
}
```

## Posts

### Create Post (Immediate)

Publish immediately to connected platforms:

```
POST /api/v1/post
```

**Request:**
```json
{
  "platforms": ["twitter", "linkedin"],
  "content": "Hello world!",
  "media": [
    {
      "id": "media_uuid",
      "file_path": "user_uuid/profile_uuid/media_uuid-photo.jpg",
      "public_url": "https://your-cdn.com/post-media/...",
      "mime_type": "image/jpeg",
      "file_size": 1234567,
      "file_name": "photo.jpg"
    }
  ]
}
```

**Response:**
```json
{
  "success": true,
  "postId": "post_uuid",
  "targets": [
    {
      "platform": "twitter",
      "status": "published",
      "external_post_id": "123456789"
    },
    {
      "platform": "linkedin",
      "status": "published",
      "external_post_id": "urn:li:share:123"
    }
  ]
}
```

### Schedule Post

Schedule for future delivery:

```
POST /api/v1/post
```

**Request:**
```json
{
  "platforms": ["twitter"],
  "content": "Scheduled post",
  "scheduleDate": "2024-01-15T10:00:00"
}
```

**Response:**
```json
{
  "success": true,
  "postId": "post_uuid",
  "scheduled": true,
  "scheduledAt": "2024-01-15T10:00:00.000Z"
}
```

### List Posts

Retrieve posts for a profile:

```
GET /api/v1/posts?profileId=profile_uuid
```

**Response:**
```json
{
  "posts": [
    {
      "id": "post_uuid",
      "content": "Hello world!",
      "status": "published",
      "scheduled_for": null,
      "published_at": "2024-01-01T00:00:00.000Z",
      "targets": [
        {
          "id": "target_uuid",
          "platform": "twitter",
          "status": "published",
          "external_post_id": "123456789"
        }
      ],
      "media": []
    }
  ]
}
```

### List Scheduled Posts

Retrieve scheduled posts:

```
GET /api/v1/scheduled-posts?profileId=profile_uuid
```

**Response:**
```json
{
  "scheduledPosts": [
    {
      "id": "scheduled_uuid",
      "post_id": "post_uuid",
      "platform": "twitter",
      "content": "Scheduled post",
      "scheduled_at": "2024-01-15T10:00:00.000Z",
      "status": "pending"
    }
  ]
}
```

### Cancel Scheduled Post

Cancel a pending scheduled post:

```
DELETE /api/v1/scheduled-posts/{scheduledPostId}
```

**Response:**
```json
{
  "success": true,
  "postId": "post_uuid"
}
```

## Job Queue & Delivery Tracking

Amplify Scheduler includes a Zernio-grade job queue with automatic retry logic, delivery tracking, and rate limit handling.

### Job Logs

Retrieve delivery logs for scheduled and immediate posts:

```
GET /api/v1/job-logs?profileId=profile_uuid&limit=100&platform=twitter&status=failed
```

**Query Parameters:**
- `profileId` (required) — Profile UUID
- `limit` (optional) — Maximum results (default: 100, max: 500)
- `platform` (optional) — Filter by platform
- `status` (optional) — Filter by status: `success`, `failed`, `rate_limited`

**Response:**
```json
{
  "logs": [
    {
      "id": "job_uuid",
      "post_id": "post_uuid",
      "scheduled_post_id": "sched_uuid",
      "platform": "twitter",
      "job_type": "publish",
      "status": "success",
      "attempt_number": 1,
      "started_at": "2024-01-15T10:00:00.000Z",
      "completed_at": "2024-01-15T10:00:02.500Z",
      "duration_ms": 2500,
      "error_message": null,
      "error_category": null,
      "was_rate_limited": false,
      "will_retry": false,
      "next_retry_at": null,
      "metadata": { "durationMs": 2500, "isRetry": false },
      "created_at": "2024-01-15T10:00:00.000Z"
    }
  ]
}
```

### Delivery Metrics

Get aggregated delivery statistics by platform and date:

```
GET /api/v1/delivery-metrics?profileId=profile_uuid&days=30
```

**Query Parameters:**
- `profileId` (required) — Profile UUID
- `days` (optional) — Number of days (default: 30, max: 90)

**Response:**
```json
{
  "metrics": [
    {
      "platform": "twitter",
      "date": "2024-01-15",
      "total_jobs": 25,
      "successful_jobs": 23,
      "failed_jobs": 1,
      "retried_jobs": 2,
      "rate_limited_jobs": 1,
      "avg_duration_ms": 1840
    }
  ]
}
```

### Retry Failed Post

Manually retry a failed scheduled post:

```
POST /api/v1/scheduled-posts/{scheduledPostId}/retry
```

**Request:**
```json
{
  "profileId": "profile_uuid"
}
```

**Response:**
```json
{
  "success": true,
  "message": "Post queued for retry"
}
```

### Error Categories

The job queue automatically categorizes errors:

- `rate_limited` — Platform rate limit hit (auto-retry with exponential backoff)
- `auth_error` — Authentication/authorization failure (no retry)
- `network_error` — Network timeout/connection issues (auto-retry)
- `duplicate` — Duplicate post detected (no retry)
- `media_error` — Media upload/processing issue (auto-retry)
- `media_processing` — Instagram container not ready (auto-retry)
- `unknown` — Uncategorized error (auto-retry)

### Retry Behavior

- **Max Attempts**: 3 attempts by default
- **Backoff**: Exponential (1 min → 2 min → 4 min)
- **Rate Limits**: Automatically detected and backed off
- **Auth Errors**: Not retried (requires reconnection)

## Content Templates

Save and reuse content templates for faster posting.

### List Templates

Retrieve templates for a profile:

```
GET /api/v1/templates?profileId=profile_uuid&category=promotional&favorites=true
```

**Response:**
```json
{
  "templates": [
    {
      "id": "tmpl_uuid",
      "name": "Product Launch",
      "description": "Standard product launch announcement",
      "content": "🚀 Excited to announce our new product! Check it out at...",
      "platforms": ["twitter", "linkedin", "instagram"],
      "category": "promotional",
      "is_favorite": true,
      "usage_count": 15,
      "created_at": "2024-01-01T00:00:00.000Z",
      "updated_at": "2024-01-15T00:00:00.000Z"
    }
  ]
}
```

### Create Template

Save a new content template:

```
POST /api/v1/templates
```

**Request:**
```json
{
  "profileId": "profile_uuid",
  "name": "Weekly Update",
  "description": "Weekly team update template",
  "content": "📊 This week's highlights:\n\n✅ {{achievement}}\n📈 {{metric}}\n🔜 {{next_week}}",
  "platforms": ["twitter", "linkedin"],
  "category": "internal",
  "media": [
    {
      "file_path": "...",
      "public_url": "...",
      "mime_type": "image/jpeg"
    }
  ]
}
```

**Response:**
```json
{
  "success": true,
  "template": {
    "id": "tmpl_uuid",
    "name": "Weekly Update",
    "content": "📊 This week's highlights...",
    "platforms": ["twitter", "linkedin"]
  }
}
```

### Get Template

Retrieve a single template with media:

```
GET /api/v1/templates/{templateId}
```

**Response:**
```json
{
  "template": {
    "id": "tmpl_uuid",
    "name": "Weekly Update",
    "content": "...",
    "platforms": ["twitter"],
    "media": [
      {
        "id": "tmpl_media_uuid",
        "file_path": "...",
        "public_url": "...",
        "mime_type": "image/jpeg"
      }
    ]
  }
}
```

### Update Template

Modify template content or mark as favorite:

```
PATCH /api/v1/templates/{templateId}
```

**Request:**
```json
{
  "name": "Updated Name",
  "content": "New content...",
  "is_favorite": true,
  "increment_usage": true
}
```

### Delete Template

Remove a template:

```
DELETE /api/v1/templates/{templateId}
```

**Response:**
```json
{
  "success": true
}
```

## Team Management

Invite team members and manage roles for collaborative posting.

### List Profile Members

Get all members of a profile:

```
GET /api/v1/profiles/{profileId}/members
```

**Response:**
```json
{
  "members": [
    {
      "id": "mem_uuid",
      "user_id": "user_uuid",
      "role": "owner",
      "invited_by": null,
      "invitation_status": "active",
      "permissions": {},
      "joined_at": "2024-01-01T00:00:00.000Z",
      "user": {
        "id": "user_uuid",
        "name": "John Doe",
        "email": "john@example.com",
        "avatar_url": "https://..."
      }
    }
  ]
}
```

**Roles:**
- `owner` — Full control, can manage members and delete profile
- `admin` — Can post, schedule, and manage content
- `member` — Can post and schedule

### Invite Member

Send an invitation to join the profile:

```
POST /api/v1/profiles/{profileId}/invitations
```

**Request:**
```json
{
  "email": "teammate@example.com",
  "role": "admin"
}
```

**Response:**
```json
{
  "success": true,
  "invitation": {
    "id": "inv_uuid",
    "email": "teammate@example.com",
    "role": "admin",
    "expiresAt": "2024-01-15T00:00:00.000Z",
    "inviteUrl": "https://app.com/invite/abc123..."
  }
}
```

### Accept Invitation

Accept an invitation to join a profile:

```
POST /api/v1/invitations/{token}/accept
```

**Response:**
```json
{
  "success": true,
  "profileId": "profile_uuid",
  "role": "admin"
}
```

### Update Member Role

Change a member's role (owner only):

```
PATCH /api/v1/profiles/{profileId}/members/{memberId}
```

**Request:**
```json
{
  "role": "admin"
}
```

### Remove Member

Remove a member from the profile (owner only):

```
DELETE /api/v1/profiles/{profileId}/members/{memberId}
```

### Leave Profile

Remove yourself from a profile (cannot be owner):

```
DELETE /api/v1/profiles/{profileId}/leave
```

## Webhooks

Configure outbound webhooks to receive real-time events when posts are published, scheduled, or fail.

### List Webhook Endpoints

```
GET /api/v1/webhook-endpoints?profileId=profile_uuid
```

**Response:**
```json
{
  "endpoints": [
    {
      "id": "wh_uuid",
      "url": "https://your-app.com/webhook",
      "events": ["post.published", "post.failed"],
      "is_active": true,
      "max_retries": 3,
      "last_delivery_at": "2024-01-15T10:30:00.000Z",
      "last_delivery_status": "success",
      "created_at": "2024-01-01T00:00:00.000Z"
    }
  ]
}
```

### Create Webhook Endpoint

```
POST /api/v1/webhook-endpoints
```

**Request:**
```json
{
  "profileId": "profile_uuid",
  "url": "https://your-app.com/webhook",
  "secret": "your_webhook_signing_secret",
  "events": ["post.published", "post.failed", "post.scheduled"],
  "maxRetries": 5
}
```

**Events:**
- `post.published` — Fired when a post is successfully published
- `post.failed` — Fired when a post fails to publish
- `post.scheduled` — Fired when a post is scheduled
- `*` — Subscribe to all events

**Response:**
```json
{
  "success": true,
  "endpoint": {
    "id": "wh_uuid",
    "url": "https://your-app.com/webhook",
    "events": ["post.published", "post.failed", "post.scheduled"],
    "is_active": true,
    "max_retries": 5
  }
}
```

### Update Webhook Endpoint

```
PATCH /api/v1/webhook-endpoints/{webhookId}
```

**Request:**
```json
{
  "is_active": false,
  "events": ["post.published"],
  "max_retries": 3
}
```

### Delete Webhook Endpoint

```
DELETE /api/v1/webhook-endpoints/{webhookId}
```

### List Delivery History

```
GET /api/v1/webhook-endpoints/{webhookId}/deliveries?limit=50
```

**Response:**
```json
{
  "deliveries": [
    {
      "id": "whd_uuid",
      "event_type": "post.published",
      "status": "delivered",
      "attempt_number": 1,
      "response_status": 200,
      "delivered_at": "2024-01-15T10:30:00.000Z",
      "created_at": "2024-01-15T10:30:00.000Z"
    }
  ]
}
```

### Retry Failed Delivery

```
POST /api/v1/webhook-deliveries/{deliveryId}/retry
```

**Response:**
```json
{
  "success": true,
  "message": "Delivery queued for retry"
}
```

### Webhook Payload Format

Webhook payloads are signed with HMAC-SHA256:

```json
{
  "event_type": "post.published",
  "timestamp": "2024-01-15T10:30:00.000Z",
  "profile_id": "profile_uuid",
  "data": {
    "post_id": "post_uuid",
    "platform": "twitter",
    "content": "Hello World!",
    "published_at": "2024-01-15T10:30:00.000Z",
    "external_post_id": "1234567890"
  }
}
```

### Signature Verification

Verify the webhook signature in your receiver:

```javascript
const crypto = require('crypto');

function verifyWebhook(payload, signature, secret) {
  const hmac = crypto.createHmac('sha256', secret);
  hmac.update(JSON.stringify(payload));
  const expected = hmac.digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// Express example
app.post('/webhook', (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  if (!verifyWebhook(req.body, signature, WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }
  // Process webhook...
  res.status(200).send('OK');
});
```

### Ready-to-run example

The repository includes copy-paste receiver examples at `webhook-receiver.example.js` and `webhook-receiver.example.ts`.

Run it with:

```bash
WEBHOOK_SECRET=your_webhook_secret node webhook-receiver.example.js
```

Then point an AmplifySchedule webhook endpoint at the public URL for `/webhook`.

The example:

- verifies `X-Webhook-Signature`
- accepts `post.published`, `post.failed`, and `post.scheduled`
- returns `200 OK` on success
- logs the event payload to the console

### Test locally

1. Start the receiver on your machine.
2. Expose it with a tunnel tool such as ngrok.
3. Create a webhook endpoint in AmplifySchedule pointing to the public tunnel URL.
4. Publish or schedule a post.
5. Confirm the event arrives and the receiver returns `200 OK`.

If you use the TypeScript example, run it with your preferred TS runtime or compile step and keep the same `WEBHOOK_SECRET` and `/webhook` path.

## Analytics

### Overview

Get posting analytics for a profile:

```
GET /api/v1/analytics/overview?profileId=profile_uuid
```

**Response:**
```json
{
  "analytics": {
    "totalPosts": 42,
    "draftPosts": 5,
    "scheduledPosts": 3,
    "publishingPosts": 1,
    "publishedPosts": 30,
    "failedPosts": 3,
    "totalTargets": 50,
    "successfulTargets": 45,
    "failedTargets": 5,
    "platformBreakdown": [
      {
        "platform": "twitter",
        "targets": 20,
        "published": 18,
        "failed": 2
      }
    ]
  }
}
```

### Real-time Dashboard

Retrieve unified dashboard totals, platform breakdown, daily trends, and top posts:

```
GET /api/analytics/realtime?profileId=profile_uuid&days=30
```

### Post Timeline

Get time-series post performance by day and platform:

```
GET /api/analytics/post-timeline?profileId=profile_uuid&days=30&platform=instagram
```

**Query Parameters:**
- `profileId` (required)
- `days` (optional, default `30`, max `180`)
- `platform` (optional, e.g. `instagram`)

### Content Decay

Measure engagement-rate decay from peak to latest snapshots:

```
GET /api/analytics/content-decay?profileId=profile_uuid&days=30&platform=linkedin
```

Returns per-platform decay summaries and top decayed posts.

### Posting Frequency

Get publishing cadence by day, hour, and platform:

```
GET /api/analytics/posting-frequency?profileId=profile_uuid&days=30&platform=facebook
```

### Best Time (Analytics-derived)

Get top posting time slots derived from historical performance:

```
GET /api/analytics/best-time?profileId=profile_uuid&days=30&platform=tiktok
```

### Analytics Capability Matrix

Get platform-level analytics support and connection status:

```
GET /api/analytics/capabilities?profileId=profile_uuid
```

### Analytics Sync Health

Get freshness and health diagnostics for analytics sync:

```
GET /api/analytics/sync-health?profileId=profile_uuid
```

**Response includes:**
- `summary` counts (`healthy`, `stale`, `needsAttention`, `noData`, `unsupported`, `disconnected`)
- `byAccount` status with `lastMetricAt`, `freshnessHours`, `reason`

## Advanced Scheduling

### Get Best Posting Times

Get AI-recommended optimal posting times based on historical engagement data:

```
GET /api/v1/scheduling/best-times?profileId=profile_uuid&platform=twitter
```

**Response (with historical data):**
```json
{
  "bestTimes": [
    {
      "dayOfWeek": 2,
      "hourOfDay": 9,
      "avgEngagementScore": 85.5,
      "postsCount": 12
    },
    {
      "dayOfWeek": 3,
      "hourOfDay": 14,
      "avgEngagementScore": 72.3,
      "postsCount": 8
    }
  ],
  "isDefault": false
}
```

**Response (no data yet - defaults):**
```json
{
  "bestTimes": [
    { "day_of_week": 2, "hour_of_day": 9, "reason": "Weekday morning engagement" },
    { "day_of_week": 3, "hour_of_day": 12, "reason": "Midweek lunch break" }
  ],
  "isDefault": true,
  "message": "No historical data yet. Using recommended defaults."
}
```

**Day of week mapping:**
- `0` = Sunday
- `1` = Monday
- `2` = Tuesday
- `3` = Wednesday
- `4` = Thursday
- `5` = Friday
- `6` = Saturday

### List Recurring Schedules

Get all recurring post schedules:

```
GET /api/v1/recurring-schedules?profileId=profile_uuid
```

**Response:**
```json
{
  "schedules": [
    {
      "id": "rs_uuid",
      "name": "Daily Motivation",
      "content": "Your daily dose of motivation! 💪",
      "platforms": ["twitter", "linkedin"],
      "recurrence_pattern": "daily",
      "time_of_day": "09:00:00",
      "timezone": "America/New_York",
      "is_active": true,
      "next_scheduled_at": "2024-01-16T09:00:00.000Z",
      "last_posted_at": "2024-01-15T09:00:00.000Z"
    }
  ]
}
```

### Create Recurring Schedule

Set up automated recurring posts:

```
POST /api/v1/recurring-schedules
```

**Request (Daily):**
```json
{
  "profileId": "profile_uuid",
  "name": "Morning Update",
  "content": "Good morning! Here's today's update.",
  "platforms": ["twitter", "linkedin"],
  "recurrencePattern": "daily",
  "timeOfDay": "08:00:00",
  "timezone": "America/New_York"
}
```

**Request (Weekly):**
```json
{
  "profileId": "profile_uuid",
  "name": "Monday Motivation",
  "content": "Happy Monday! Let's crush this week! 💪",
  "platforms": ["instagram", "facebook"],
  "recurrencePattern": "weekly",
  "recurrenceConfig": { "day_of_week": 1 },
  "timeOfDay": "09:00:00",
  "timezone": "UTC"
}
```

**Request (Monthly):**
```json
{
  "profileId": "profile_uuid",
  "name": "Monthly Newsletter",
  "content": "This month's highlights...",
  "platforms": ["linkedin"],
  "recurrencePattern": "monthly",
  "recurrenceConfig": { "day_of_month": 1 },
  "timeOfDay": "10:00:00",
  "timezone": "Europe/London"
}
```

**Recurrence Patterns:**
- `daily` — Every day at specified time
- `weekly` — Weekly on specified day
- `monthly` — Monthly on specified date
- `custom` — Custom pattern (future use)

**Recurrence Config:**
- `day_of_week` — 0-6 (Sunday-Saturday) for weekly
- `day_of_month` — 1-31 for monthly

**Response:**
```json
{
  "success": true,
  "schedule": {
    "id": "rs_uuid",
    "name": "Morning Update",
    "recurrencePattern": "daily",
    "timeOfDay": "08:00:00",
    "nextScheduledAt": "2024-01-16T08:00:00.000Z"
  }
}
```

### Update Recurring Schedule

```
PATCH /api/v1/recurring-schedules/{scheduleId}
```

**Request:**
```json
{
  "name": "Updated Name",
  "isActive": false,
  "timeOfDay": "10:00:00",
  "recurrencePattern": "weekly",
  "recurrenceConfig": { "day_of_week": 3 }
}
```

### Delete Recurring Schedule

```
DELETE /api/v1/recurring-schedules/{scheduleId}
```

## Platform-Specific Constraints

### Twitter / X
- Text: Yes (280 characters)
- Images: Yes (single image)
- Video: No
- Scheduling: Yes

### LinkedIn
- Text: Yes
- Images: Yes (single image, uses asset upload)
- Video: No
- Scheduling: Yes

### Instagram
- Text: Yes (caption)
- Images: Yes (single image)
- Video: Yes (single video, reels format)
- Scheduling: Yes
- Requires: Business account

### Facebook
- Text: Yes
- Images: Yes (single image)
- Video: Yes (single video)
- Scheduling: Yes
- Requires: Page with manage_posts permission

### Threads
- Text: Yes
- Images: Yes (single image)
- Video: Yes (single video)
- Scheduling: Yes

### TikTok
- Text: Yes (title only)
- Images: No
- Video: Yes (required)
- Scheduling: Yes

### YouTube
- Text: Yes (title/description)
- Images: No
- Video: Yes (required)
- Scheduling: Yes

### Pinterest
- Text: Yes (description)
- Images: Yes (required, single image)
- Video: No
- Scheduling: Yes
- Requires: Default board configured

### Reddit
- Text: Yes (self posts)
- Images: No
- Video: No
- Scheduling: Yes

### Snapchat
- Status: Unsupported (Marketing API only)

### Bluesky
- Status: Unsupported (AT Protocol implementation incomplete)

## Error Handling

All errors follow this format:

```json
{
  "success": false,
  "error": "Human readable error message"
}
```

Common HTTP status codes:
- `200` — Success
- `400` — Bad request (missing/invalid parameters)
- `401` — Unauthorized (invalid API key)
- `403` — Forbidden (profile access denied)
- `404` — Resource not found
- `500` — Server error

## Multi-Platform Posting Rules

When posting to multiple platforms simultaneously:

1. **Media compatibility** — All selected platforms must support the media type
2. **Single asset** — Only one media item per post (image or video)
3. **No mixed types** — Cannot combine images and videos in one post
4. **Scheduling compatibility** — All platforms must support scheduling
5. **Media requirements** — Platforms requiring media must receive it

The API validates these rules and returns errors for invalid combinations.

## Webhooks (Optional)

Configure webhooks to receive real-time events:

```
POST /api/v1/webhooks/configure
```

**Request:**
```json
{
  "url": "https://your-app.com/webhook",
  "events": ["post.published", "post.failed"],
  "secret": "webhook_signing_secret"
}
```

Webhook payloads are signed with `X-Webhook-Signature` header using HMAC-SHA256.

## Rate Limits

- Posts: 100 per hour per profile
- Media uploads: 50 per hour per profile
- API requests: 1000 per hour per API key

Rate limit headers:
```
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1704067200
```

## Support

For integration support, please use the Contact Us form on the public site.
