logicspike/docs

Content Engine

Phase 4: API Specification — Content Engine

Last Updated: 2026-03-15 Base Path: /content/* (proxied via Gateway) Auth: RS256 JWT (via Gateway x-user-id, x-tenant-id, x-user-permissions headers) ServiceCode: "content" — must be registered in packages/core-types/src/service.ts before implementation


1. Social Connections

GET /content/connections

List all social connections for the current tenant.

Permissions: content:connections.read

Response 200:

{
  "data": [
    {
      "id": "uuid",
      "platform": "twitter",
      "platformUsername": "@clientbrand",
      "platformAccountId": "123456789",
      "status": "active",
      "tokenExpiresAt": "2026-04-13T00:00:00Z",
      "connectedBy": "user-uuid",
      "createdAt": "2026-03-01T00:00:00Z"
    }
  ]
}

POST /content/connections/:platform/authorize

Initiate OAuth flow for a social platform. Returns the OAuth authorization URL.

Permissions: content:connections.write

Request Body:

{
  "callbackUrl": "https://dashboard.logicspike.com/content/callback"
}

Response 200:

{
  "authorizationUrl": "https://twitter.com/i/oauth2/authorize?client_id=...&state=...",
  "state": "random-csrf-token"
}

POST /content/connections/:platform/callback

Complete the OAuth flow after user authorization.

Permissions: content:connections.write

Request Body:

{
  "code": "oauth-authorization-code",
  "state": "random-csrf-token"
}

Response 201:

{
  "connection": {
    "id": "uuid",
    "platform": "twitter",
    "platformUsername": "@clientbrand",
    "status": "active"
  }
}

DELETE /content/connections/:connectionId

Disconnect a social account. Revokes the OAuth token on the platform side.

Permissions: content:connections.write

Response 204: No content.


2. Campaigns

GET /content/campaigns

List campaigns for the current tenant.

Permissions: content:campaigns.read

Query Params: ?status=active&page=1&limit=20

Response 200:

{
  "data": [
    {
      "id": "uuid",
      "name": "Q1 Product Launch",
      "color": "#FF6B35",
      "startDate": "2026-03-01",
      "endDate": "2026-03-31",
      "status": "active",
      "slotCount": 12
    }
  ],
  "meta": { "page": 1, "limit": 20, "total": 3, "totalPages": 1 }
}

POST /content/campaigns

Create a new campaign.

Permissions: content:campaigns.write

Request Body:

{
  "name": "Q1 Product Launch",
  "description": "All content related to the March product launch",
  "color": "#FF6B35",
  "startDate": "2026-03-01",
  "endDate": "2026-03-31"
}

Response 201: Created campaign object.


GET /content/campaigns/:campaignId

Get details of a specific campaign including slot count.

Permissions: content:campaigns.read

Response 200: Single campaign object.


PATCH /content/campaigns/:campaignId

Update a campaign.

Permissions: content:campaigns.write

Request Body (partial update):

{
  "name": "Q1 Product Launch — Extended",
  "endDate": "2026-04-15"
}

Response 200: Updated campaign object.


DELETE /content/campaigns/:campaignId

Archive a campaign (soft delete).

Permissions: content:campaigns.write

Response 204: No content.


3. Labels

GET /content/labels

List all labels for the tenant.

Permissions: content:labels.read


POST /content/labels

Create a label.

Permissions: content:labels.write

Request Body:

{
  "name": "ProductUpdate",
  "color": "#4ECDC4"
}

DELETE /content/labels/:labelId

Delete a label (removes from all slots).

Permissions: content:labels.write


PATCH /content/labels/:labelId

Update a label.

Permissions: content:labels.write

Request Body:

{ "name": "ProductLaunch", "color": "#FF6B35" }

4. Content Slots (Core)

GET /content/slots

Query content slots — the primary calendar data endpoint.

Permissions: content:slots.read

Query Params:

Param Type Description
from ISO DateTime Start of date range (required)
to ISO DateTime End of date range (required)
status String Filter by status (optional)
campaignId UUID Filter by campaign (optional)
connectionId UUID Filter by social account (optional)
labelId UUID Filter by label (optional)

Response 200:

{
  "data": [
    {
      "id": "uuid",
      "caption": "🚀 Our latest product update...",
      "mediaIds": ["media-uuid-1"],
      "scheduledAt": "2026-03-14T09:00:00Z",
      "timezone": "Asia/Kolkata",
      "status": "scheduled",
      "campaign": { "id": "uuid", "name": "Q1 Launch", "color": "#FF6B35" },
      "labels": [{ "id": "uuid", "name": "ProductUpdate", "color": "#4ECDC4" }],
      "targets": [
        {
          "id": "uuid",
          "connectionId": "uuid",
          "platform": "twitter",
          "platformUsername": "@clientbrand",
          "publishStatus": "pending",
          "platformCaption": null
        },
        {
          "id": "uuid",
          "connectionId": "uuid",
          "platform": "linkedin",
          "platformUsername": "Client Brand",
          "publishStatus": "pending",
          "platformCaption": "Extended version for LinkedIn..."
        }
      ],
      "createdBy": "user-uuid",
      "createdAt": "2026-03-13T10:30:00Z"
    }
  ]
}

POST /content/slots

Create a new content slot.

Permissions: content:slots.write

Request Body:

{
  "caption": "🚀 Our latest product update is here!",
  "contentType": "image",
  "mediaIds": ["media-uuid-1"],
  "scheduledAt": "2026-03-14T09:00:00Z",
  "timezone": "Asia/Kolkata",
  "campaignId": "campaign-uuid",
  "labelIds": ["label-uuid-1"],
  "targets": [
    { "connectionId": "twitter-connection-uuid" },
    { "connectionId": "linkedin-connection-uuid", "platformCaption": "Extended for LinkedIn..." }
  ]
}

Validations:

  • scheduledAt must be in the future
  • At least 1 target required
  • All referenced connectionIds must be active
  • contentType must be one of: text, image, video, carousel, story, link
  • Caption validated per platform rules:
    • Twitter/X: max 280 characters
    • LinkedIn: max 3,000 characters
    • Instagram: max 2,200 characters, media required
    • Facebook: max 63,206 characters

Response 201: Created slot with full targets array.


PATCH /content/slots/:slotId

Update a content slot (only allowed in draft or pending_review status).

Permissions: content:slots.write

Request Body (partial update):

{
  "caption": "Updated caption text",
  "mediaIds": ["new-media-uuid"],
  "targets": [
    { "connectionId": "twitter-connection-uuid" },
    { "connectionId": "instagram-connection-uuid" }
  ]
}

Response 200: Updated slot with full targets array.


DELETE /content/slots/:slotId

Delete a content slot (only allowed in draft or scheduled status).

Permissions: content:slots.write


POST /content/slots/:slotId/submit-review

Submit a slot for review — transitions draft → pending_review.

Permissions: content:slots.write


POST /content/slots/:slotId/approve

Approve a slot — transitions pending_review → approved → scheduled.

Permissions: content:slots.approve

Request Body (optional):

{ "note": "Looks great, approved!" }

POST /content/slots/:slotId/reject

Reject a slot — transitions pending_review → draft.

Permissions: content:slots.approve

Request Body:

{ "note": "Caption is too long for Twitter. Please shorten." }

POST /content/slots/:slotId/reschedule

Change the scheduled time of a slot (drag-and-drop support).

Permissions: content:slots.write

Request Body:

{
  "scheduledAt": "2026-03-15T14:00:00Z"
}

GET /content/slots/:slotId

Get full details of a specific content slot including targets and publish status.

Permissions: content:slots.read

Response 200: Full slot object with targets, labels, campaign, and publish logs.


POST /content/slots/bulk

Create multiple content slots at once (used by dashboard and MCP agents).

Permissions: content:slots.write

Request Body:

{
  "slots": [
    {
      "caption": "Post 1",
      "contentType": "text",
      "scheduledAt": "2026-03-14T09:00:00Z",
      "timezone": "Asia/Kolkata",
      "targets": [{ "connectionId": "twitter-uuid" }]
    },
    {
      "caption": "Post 2",
      "contentType": "image",
      "mediaIds": ["media-uuid"],
      "scheduledAt": "2026-03-15T09:00:00Z",
      "timezone": "Asia/Kolkata",
      "targets": [{ "connectionId": "linkedin-uuid" }]
    }
  ],
  "campaignId": "campaign-uuid"
}

Max: 50 slots per request.

Response 201: Array of created slot objects.


5. Recurring Schedules

GET /content/recurring

List recurring schedules.

Permissions: content:recurring.read


POST /content/recurring

Create a recurring content pattern.

Permissions: content:recurring.write

Request Body:

{
  "name": "Motivation Monday",
  "cronExpression": "0 10 * * 1",
  "timezone": "Asia/Kolkata",
  "templateCaption": "💡 Motivation Monday: Start your week strong!",
  "templateMediaIds": [],
  "targetConnectionIds": ["instagram-uuid", "linkedin-uuid"],
  "autoSchedule": true
}

PATCH /content/recurring/:scheduleId

Update a recurring schedule.

Permissions: content:recurring.write


POST /content/recurring/:scheduleId/pause

Pause slot generation (does not cancel existing slots).

Permissions: content:recurring.write


POST /content/recurring/:scheduleId/resume

Resume a paused schedule.

Permissions: content:recurring.write


DELETE /content/recurring/:scheduleId

Delete a recurring schedule.

Permissions: content:recurring.write


6. Publish Logs

GET /content/slots/:slotId/logs

View the publish audit trail for a specific slot.

Permissions: content:slots.read

Response 200:

{
  "data": [
    {
      "id": "uuid",
      "slotTargetId": "target-uuid",
      "action": "publish_attempt",
      "platform": "twitter",
      "createdAt": "2026-03-14T09:00:01Z"
    },
    {
      "id": "uuid",
      "slotTargetId": "target-uuid",
      "action": "publish_success",
      "platform": "twitter",
      "externalPostUrl": "https://twitter.com/clientbrand/status/123...",
      "createdAt": "2026-03-14T09:00:03Z"
    }
  ]
}

7. Error Codes

Code HTTP Description
SLOT_NOT_FOUND 404 Content slot doesn't exist
INVALID_STATUS_TRANSITION 409 e.g., trying to approve a draft slot
CONNECTION_EXPIRED 422 Target social connection token is expired
CONNECTION_NOT_FOUND 404 Referenced connection doesn't exist
SCHEDULE_PAST_TIME 422 scheduledAt is in the past
PLATFORM_VALIDATION_FAILED 422 Caption exceeds platform limits
CAMPAIGN_NOT_FOUND 404 Referenced campaign doesn't exist
INSUFFICIENT_PERMISSIONS 403 Missing required PBAC permission
PUBLISH_LIMIT_REACHED 429 Tenant has hit their plan's slot limit
PARTIALLY_FAILED 207 Some SlotTargets succeeded but others failed
BULK_LIMIT_EXCEEDED 422 Too many items in a bulk operation (max 50)
INVALID_CONTENT_TYPE 422 contentType is not valid for the target platform
MEDIA_REQUIRED 422 Platform requires media attachment (e.g., Instagram)

8. Permissions Map

Permission String Grants
content:connections.read View connected social accounts
content:connections.write Connect/disconnect social accounts
content:campaigns.read View campaigns
content:campaigns.write Create/edit/archive campaigns
content:labels.read View labels
content:labels.write Create/delete labels
content:slots.read View content slots and calendar
content:slots.write Create/edit/delete/schedule content slots
content:slots.approve Approve/reject content in review
content:recurring.read View recurring schedules
content:recurring.write Create/edit/delete recurring schedules
Content Engine