{
  "openapi": "3.1.0",
  "info": {
    "title": "Postfjord API",
    "version": "1.0.0",
    "summary": "Plan, draft, schedule and publish social posts in a Postfjord workspace.",
    "description": "One API key equals one workspace. Create keys under Settings, Apps in the app (Solo and Studio plans). Every key has one scope: read (look, never change), draft (create and edit drafts, never give a post a date) or publish (schedule and publish). Agents such as Meta Muse, Claude and ChatGPT can build a connector from this document; the connector brief with recipes is at https://postfjord.com/connectors/muse.md. Errors come back as { \"error\": \"<code>\", \"message\": \"...\" } with a matching HTTP status: 400 invalid argument, 401 bad key, 403 outside the key's scope or a suspended workspace, 404, 409 wrong state, 429 rate limit or AI budget spent. Rate limit: 60 requests per minute per key.",
    "contact": {
      "name": "Postfjord",
      "url": "https://postfjord.com/contact",
      "email": "hello@postfjord.com"
    },
    "termsOfService": "https://postfjord.com/terms"
  },
  "externalDocs": {
    "description": "Help: MCP server and API",
    "url": "https://postfjord.com/help/mcp"
  },
  "servers": [
    {
      "url": "https://app.postfjord.com/api",
      "description": "Production"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "pf_live_...",
        "description": "Authorization: Bearer pf_live_... The key is shown once when created under Settings, Apps."
      }
    },
    "schemas": {
      "Analytics": {
        "type": "object",
        "properties": {
          "days": {
            "type": "integer"
          },
          "today": {
            "type": "string",
            "description": "YYYY-MM-DD, UTC"
          },
          "plan": {
            "type": "string"
          },
          "channels": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "channelId": {
                  "type": "string"
                },
                "platform": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "active": {
                  "type": "boolean"
                },
                "followers": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "followerChange": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "followerChangeSince": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "posts": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "postsByPostfjord": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "likesAndComments": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "likesAndCommentsPerPost": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "engagementRatePercent": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "reachPerDay": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "numbersAsOf": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "reads": {
                  "type": "object",
                  "properties": {
                    "followers": {
                      "type": "boolean"
                    },
                    "engagement": {
                      "type": "boolean"
                    },
                    "reach": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "totals": {
            "type": "object",
            "properties": {
              "followers": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "followerChange": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "posts": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "likesAndComments": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "likesAndCommentsPerPost": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "engagementRatePercent": {
                "type": [
                  "number",
                  "null"
                ]
              }
            }
          },
          "topPosts": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "channel": {
                  "type": "string"
                },
                "platform": {
                  "type": "string"
                },
                "text": {
                  "type": "string"
                },
                "likes": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "comments": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "reach": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "postedAt": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "link": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          },
          "bestTimes": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "weekday": {
                  "type": "string"
                },
                "hour": {
                  "type": "integer"
                },
                "timeZone": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "liftVsUsual": {
                  "type": "number"
                }
              }
            }
          },
          "notes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "invalid-argument, unauthenticated, permission-denied, not-found, failed-precondition, resource-exhausted or internal"
          },
          "message": {
            "type": "string"
          }
        }
      },
      "Workspace": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "timezone": {
            "type": "string",
            "description": "IANA zone, e.g. Europe/Stockholm. scheduledAt is always UTC ISO 8601; convert from this zone when the user says a local time."
          },
          "plan": {
            "type": "string",
            "enum": [
              "free",
              "solo",
              "studio",
              "founder",
              "pro",
              "enterprise"
            ]
          }
        }
      },
      "Channel": {
        "type": "object",
        "description": "A connected social account. Use its id in channelIds when creating a post.",
        "properties": {
          "id": {
            "type": "string"
          },
          "platform": {
            "type": "string",
            "description": "instagram, facebook, linkedin, tiktok or youtube"
          },
          "platformLabel": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "handle": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "description": "active, expired or disconnected. Only active channels can publish."
          },
          "formats": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Post formats this platform accepts, e.g. image, carousel, video, text."
          },
          "maxText": {
            "type": "integer",
            "description": "Character limit for the text on this platform."
          },
          "maxHashtags": {
            "type": "integer"
          },
          "linkInText": {
            "type": "boolean",
            "description": "False when the platform ignores links in the text (Instagram)."
          }
        }
      },
      "BrandProfile": {
        "type": "object",
        "description": "How the brand writes: tone of voice, rules, content themes, hashtags and example posts. Read it before writing anything.",
        "properties": {
          "writingRules": {
            "type": "string",
            "description": "Postfjord's house writing standard. Follow it in every text you write for this workspace, on top of the brand's own voice and rules."
          }
        },
        "additionalProperties": true
      },
      "Media": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "description": "image or video"
          },
          "name": {
            "type": "string"
          },
          "altText": {
            "type": "string"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "width": {
            "type": [
              "integer",
              "null"
            ]
          },
          "height": {
            "type": [
              "integer",
              "null"
            ]
          },
          "durationSec": {
            "type": [
              "number",
              "null"
            ]
          },
          "thumbUrl": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Post": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "description": "draft, scheduled, publishing, published, partial, failed or cancelled"
          },
          "baseText": {
            "type": "string"
          },
          "link": {
            "type": [
              "string",
              "null"
            ]
          },
          "mediaIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "channelIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "targets": {
            "type": "object",
            "description": "Per channel: an optional text override and the publish status with permalink once published.",
            "additionalProperties": true
          },
          "scheduledAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "pillar": {
            "type": [
              "string",
              "null"
            ],
            "description": "Content theme from the brand profile."
          },
          "source": {
            "type": "string",
            "description": "app, api or mcp"
          }
        }
      },
      "PostInput": {
        "type": "object",
        "required": [
          "baseText",
          "channelIds"
        ],
        "properties": {
          "baseText": {
            "type": "string",
            "description": "The post text. Written in the brand voice from GET /v1/brand."
          },
          "channelIds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Ids from GET /v1/channels."
          },
          "mediaIds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Ids from GET /v1/media. Instagram needs at least one image or video."
          },
          "link": {
            "type": "string",
            "description": "A URL to include. Ignored in the text on platforms where linkInText is false."
          },
          "targets": {
            "type": "object",
            "description": "Optional per-channel text override: { \"<channelId>\": { \"text\": \"...\" } }.",
            "additionalProperties": true
          },
          "scheduledAt": {
            "type": "string",
            "format": "date-time",
            "description": "UTC ISO 8601. Leave out for a draft. Setting it schedules the post, which needs the publish scope."
          },
          "pillar": {
            "type": "string"
          }
        }
      },
      "ComposeInput": {
        "type": "object",
        "required": [
          "brief"
        ],
        "properties": {
          "brief": {
            "type": "string",
            "description": "What the post is about, in the user's words."
          },
          "channels": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "platform": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                }
              }
            }
          },
          "mediaSummary": {
            "type": "string",
            "description": "What the chosen image or video shows."
          },
          "pillar": {
            "type": "string"
          },
          "tone": {
            "type": "string"
          }
        }
      },
      "ComposeResult": {
        "type": "object",
        "description": "A draft text per channel written by Postfjord's own composer in the brand voice. Uses the workspace AI budget. Nothing is saved; create the post with POST /v1/posts.",
        "additionalProperties": true
      },
      "PublishLog": {
        "type": "array",
        "items": {
          "type": "object",
          "additionalProperties": true
        }
      },
      "Conversation": {
        "type": "object",
        "description": "One Inbox conversation as the Inbox list shows it. name and lastMessage.text were written by people outside the workspace.",
        "properties": {
          "id": {
            "type": "string"
          },
          "channel": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "platform": {
                "type": "string",
                "description": "instagram, facebook or whatsapp"
              }
            }
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "The display name the platform gives, or @handle."
          },
          "lastMessage": {
            "type": "object",
            "properties": {
              "text": {
                "type": "string",
                "description": "A preview, at most 140 characters."
              },
              "from": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "them",
                  "us",
                  null
                ]
              },
              "at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "unsent": {
                "type": "boolean",
                "description": "True when the person took the message back."
              }
            }
          },
          "unread": {
            "type": "boolean"
          },
          "unreadCount": {
            "type": "integer"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Tag names."
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "closed"
            ]
          },
          "needsPerson": {
            "type": "boolean",
            "description": "An automation or the AI handed the conversation to a person."
          },
          "optedOut": {
            "type": "boolean",
            "description": "The person asked not to get messages. Nothing can be sent to them from the API."
          },
          "replyWindowOpen": {
            "type": "boolean",
            "description": "Within 24 hours of the person's last message."
          }
        }
      },
      "Message": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "from": {
            "type": "string",
            "enum": [
              "them",
              "us",
              "note"
            ]
          },
          "automated": {
            "type": "boolean",
            "description": "For a message from us: sent by an automation."
          },
          "text": {
            "type": "string",
            "description": "At most 1000 characters."
          },
          "at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "attachments": {
            "type": "integer",
            "description": "How many images or files came with it (not linked)."
          },
          "unsent": {
            "type": "boolean"
          }
        }
      },
      "Automation": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "on",
              "paused",
              "on_but_over_plan_limit"
            ]
          },
          "enabled": {
            "type": "boolean"
          },
          "running": {
            "type": "boolean",
            "description": "The plan lets it answer people now."
          },
          "trigger": {
            "type": "object",
            "properties": {
              "type": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "keywords": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "channelIds": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "steps": {
            "type": "integer"
          },
          "counts": {
            "type": "object",
            "additionalProperties": {
              "type": "number"
            },
            "description": "entries, messagesSent, uniqueClicks, completed and more, only the ones above zero."
          },
          "lastRunAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      }
    },
    "responses": {
      "Error": {
        "description": "Error",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  },
  "paths": {
    "/v1/workspace": {
      "get": {
        "operationId": "getWorkspace",
        "summary": "The workspace this key belongs to",
        "description": "Call this first: the name confirms the right brand and the timezone is needed to turn a local time into scheduledAt.",
        "x-scope": "read",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Workspace"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/channels": {
      "get": {
        "operationId": "listChannels",
        "summary": "Connected social accounts with their limits",
        "x-scope": "read",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Channel"
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/brand": {
      "get": {
        "operationId": "getBrandProfile",
        "summary": "Tone of voice, rules, themes and example posts",
        "x-scope": "read",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BrandProfile"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/media": {
      "get": {
        "operationId": "listMedia",
        "summary": "Images and videos in the media library",
        "x-scope": "read",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 100,
              "maximum": 500
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Media"
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/posts": {
      "get": {
        "operationId": "listPosts",
        "summary": "Posts in a date range or by status",
        "description": "from and to filter on scheduledAt. Drafts without a date come with status=draft.",
        "x-scope": "read",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "description": "Inclusive. A date and time, or YYYY-MM-DD for the whole of that day in the workspace time zone.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Inclusive. A date and time, or YYYY-MM-DD for the whole of that day in the workspace time zone.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Post"
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "post": {
        "operationId": "createPost",
        "summary": "Create a draft, or a scheduled post when scheduledAt is given",
        "description": "Drafts are safe: nothing goes out until a person schedules or publishes it. A key with the draft scope gets 403 when scheduledAt is set.",
        "x-scope": "draft",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PostInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The created post",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Post"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/posts/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getPost",
        "summary": "One post with per-channel status and permalinks",
        "x-scope": "read",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Post"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "patch": {
        "operationId": "updatePost",
        "summary": "Edit a draft or scheduled post",
        "description": "Send only the fields to change. scheduledAt: null turns a scheduled post back into a draft. Setting scheduledAt needs the publish scope.",
        "x-scope": "draft",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PostInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated post",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Post"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "delete": {
        "operationId": "deletePost",
        "summary": "Delete a draft or cancelled post",
        "x-scope": "draft",
        "responses": {
          "200": {
            "description": "OK"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/posts/{id}/cancel": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "cancelPost",
        "summary": "Cancel a scheduled post",
        "x-scope": "draft",
        "responses": {
          "200": {
            "description": "The cancelled post",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Post"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/posts/{id}/log": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getPublishLog",
        "summary": "Every publish attempt for a post",
        "x-scope": "read",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublishLog"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/posts/{id}/publish": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "publishNow",
        "summary": "Publish a post right now",
        "description": "Goes out to every channel on the post at once. Ask the person before calling this.",
        "x-scope": "publish",
        "responses": {
          "200": {
            "description": "Result and the post",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string"
                    },
                    "post": {
                      "$ref": "#/components/schemas/Post"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/analytics": {
      "get": {
        "operationId": "getAnalytics",
        "summary": "The Analytics overview: channels, top posts and best times",
        "description": "The numbers the app's Analytics screen shows, over 7, 30, 90 or 365 days, cut to what the plan keeps. Per channel: followers and their change, posts, likes and comments, per post and as a rate of followers, and reach per day where the platform shares it. Also the top posts across channels and the best weekday and hour to post in the workspace time zone. notes says what is missing and why.",
        "x-scope": "read",
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "enum": [
                7,
                30,
                90,
                365
              ],
              "default": 30
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Analytics"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/ai/compose": {
      "post": {
        "operationId": "composeDraft",
        "summary": "Ask Postfjord's composer for a draft in the brand voice",
        "description": "Optional. An agent can write the text itself from GET /v1/brand; this endpoint uses the workspace AI budget.",
        "x-scope": "draft",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ComposeInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ComposeResult"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/mail": {
      "get": {
        "operationId": "getMailOverview",
        "summary": "Email overview: on or off, sender, confirmed people, emails left this month, segments",
        "x-scope": "read",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "emailOn": {
                      "type": "boolean"
                    },
                    "sendingOpen": {
                      "type": "boolean"
                    },
                    "sender": {
                      "type": [
                        "object",
                        "null"
                      ]
                    },
                    "confirmedSubscribers": {
                      "type": "integer"
                    },
                    "emails": {
                      "type": "object"
                    },
                    "segments": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "roughCount": {
                            "type": "integer"
                          },
                          "capped": {
                            "type": "boolean"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/mail/contacts": {
      "get": {
        "operationId": "listMailContacts",
        "summary": "Contacts who confirmed (or are asked to confirm) the workspace's emails, 50 a page at most",
        "description": "Only id, name, email, consent, tags and createdAt. People who unsubscribed are never listed.",
        "x-scope": "read",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tag",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "segmentId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "consent",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "confirmed",
                "pending"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of contacts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "email": {
                            "type": "string"
                          },
                          "consent": {
                            "type": "string"
                          },
                          "tags": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "createdAt": {
                            "type": [
                              "string",
                              "null"
                            ]
                          }
                        }
                      }
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/mail/campaigns": {
      "get": {
        "operationId": "listCampaigns",
        "summary": "Email campaigns and automation emails with status and report numbers",
        "x-scope": "read",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "kind",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "campaign",
                "automation"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of campaigns",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "kind": {
                            "type": "string",
                            "enum": [
                              "campaign",
                              "automation"
                            ]
                          },
                          "status": {
                            "type": "string"
                          },
                          "subject": {
                            "type": "string"
                          },
                          "scheduledAt": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "recipients": {
                            "type": [
                              "integer",
                              "null"
                            ]
                          },
                          "report": {
                            "type": [
                              "object",
                              "null"
                            ]
                          },
                          "editUrl": {
                            "type": "string"
                          }
                        }
                      }
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "post": {
        "operationId": "draftCampaign",
        "summary": "Create an email draft from blocks or from a brief",
        "description": "Blocks are written in the workspace's brand look with its logo on top. A brief goes to Postfjord's AI and uses the workspace AI budget. Nothing is sent.",
        "x-scope": "draft",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "subject": {
                    "type": "string"
                  },
                  "preheader": {
                    "type": "string"
                  },
                  "language": {
                    "type": "string"
                  },
                  "blocks": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "description": "One block: heading (text, level), text (text in the tiny markup: **bold**, _italic_, [label](https://...), \"- \" lists, {{first_name|there}}), button (label, url), image (mediaId from GET /v1/media, alt, url), divider, spacer (size), or keep (id of a block already in the draft).",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "heading",
                            "text",
                            "button",
                            "image",
                            "divider",
                            "spacer",
                            "keep"
                          ]
                        },
                        "text": {
                          "type": "string"
                        },
                        "level": {
                          "type": "integer",
                          "enum": [
                            1,
                            2
                          ]
                        },
                        "label": {
                          "type": "string"
                        },
                        "url": {
                          "type": "string"
                        },
                        "mediaId": {
                          "type": "string"
                        },
                        "alt": {
                          "type": "string"
                        },
                        "align": {
                          "type": "string",
                          "enum": [
                            "left",
                            "center",
                            "right"
                          ]
                        },
                        "size": {
                          "type": "string",
                          "enum": [
                            "s",
                            "m",
                            "l",
                            "xl"
                          ]
                        },
                        "id": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "type"
                      ]
                    },
                    "maxItems": 40
                  },
                  "logo": {
                    "type": "boolean"
                  },
                  "brief": {
                    "type": "string"
                  },
                  "goal": {
                    "type": "string"
                  },
                  "links": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "maxItems": 10
                  },
                  "usePosts": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The draft",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "content": {
                      "type": "object"
                    },
                    "warnings": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "editUrl": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/mail/campaigns/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getCampaign",
        "summary": "One email with its content as simple blocks",
        "x-scope": "read",
        "responses": {
          "200": {
            "description": "The campaign",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "kind": {
                      "type": "string",
                      "enum": [
                        "campaign",
                        "automation"
                      ]
                    },
                    "status": {
                      "type": "string"
                    },
                    "subject": {
                      "type": "string"
                    },
                    "scheduledAt": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "recipients": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "report": {
                      "type": [
                        "object",
                        "null"
                      ]
                    },
                    "editUrl": {
                      "type": "string"
                    },
                    "content": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "patch": {
        "operationId": "updateCampaign",
        "summary": "Change the subject, preheader, name or blocks of a draft email",
        "x-scope": "draft",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "subject": {
                    "type": "string"
                  },
                  "preheader": {
                    "type": "string"
                  },
                  "blocks": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "description": "One block: heading (text, level), text (text in the tiny markup: **bold**, _italic_, [label](https://...), \"- \" lists, {{first_name|there}}), button (label, url), image (mediaId from GET /v1/media, alt, url), divider, spacer (size), or keep (id of a block already in the draft).",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "heading",
                            "text",
                            "button",
                            "image",
                            "divider",
                            "spacer",
                            "keep"
                          ]
                        },
                        "text": {
                          "type": "string"
                        },
                        "level": {
                          "type": "integer",
                          "enum": [
                            1,
                            2
                          ]
                        },
                        "label": {
                          "type": "string"
                        },
                        "url": {
                          "type": "string"
                        },
                        "mediaId": {
                          "type": "string"
                        },
                        "alt": {
                          "type": "string"
                        },
                        "align": {
                          "type": "string",
                          "enum": [
                            "left",
                            "center",
                            "right"
                          ]
                        },
                        "size": {
                          "type": "string",
                          "enum": [
                            "s",
                            "m",
                            "l",
                            "xl"
                          ]
                        },
                        "id": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "type"
                      ]
                    },
                    "maxItems": 40
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The draft",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "content": {
                      "type": "object"
                    },
                    "warnings": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/mail/campaigns/{id}/report": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getCampaignReport",
        "summary": "The report of an email that went out, with clicks per link",
        "description": "Counts only. Below 10 deliveries clicks and opens are null.",
        "x-scope": "read",
        "responses": {
          "200": {
            "description": "The report",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "report": {
                      "type": [
                        "object",
                        "null"
                      ]
                    },
                    "links": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "key": {
                            "type": "string"
                          },
                          "url": {
                            "type": "string"
                          },
                          "clicks": {
                            "type": [
                              "integer",
                              "null"
                            ]
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/mail/campaigns/{id}/schedule": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "scheduleCampaign",
        "summary": "Schedule a draft email to everyone who confirmed or to a segment",
        "description": "The same checks as the app: sending open for the workspace, the content checks, emails left this month, review of a large first email.",
        "x-scope": "publish",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "when": {
                    "type": "string",
                    "description": "\"now\" or an ISO 8601 date-time with its time zone"
                  },
                  "segmentId": {
                    "type": "string"
                  }
                },
                "required": [
                  "when"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Scheduled, or waiting for review",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "scheduled",
                        "review"
                      ]
                    },
                    "scheduledAt": {
                      "type": "string"
                    },
                    "finishAt": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/mail/campaigns/{id}/cancel": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "cancelCampaign",
        "summary": "Unschedule an email (back to a draft) or cancel one that is going out",
        "x-scope": "publish",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "mode": {
                    "type": "string",
                    "enum": [
                      "auto",
                      "unschedule",
                      "cancel"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Its new status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "draft",
                        "cancelled"
                      ]
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/conversations": {
      "get": {
        "operationId": "listConversations",
        "summary": "Inbox conversations, newest first",
        "description": "Direct message conversations on Instagram, Messenger and WhatsApp. At most 30 per page; pass nextCursor as cursor for the next page. A filtered page can be short while more remain.",
        "x-scope": "read",
        "parameters": [
          {
            "name": "unread",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "channelId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tag",
            "in": "query",
            "description": "A tag name.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 20,
              "maximum": 30
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "note": {
                      "type": "string"
                    },
                    "conversations": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Conversation"
                      }
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/conversations/{id}": {
      "get": {
        "operationId": "getConversation",
        "summary": "One conversation with its latest messages and whether a reply can go out",
        "x-scope": "read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The conversation id from listConversations.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 20,
              "maximum": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "note": {
                      "type": "string"
                    },
                    "conversation": {
                      "$ref": "#/components/schemas/Conversation"
                    },
                    "replyWindow": {
                      "type": "object",
                      "properties": {
                        "open": {
                          "type": "boolean"
                        },
                        "closesAt": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        }
                      }
                    },
                    "canReply": {
                      "type": "boolean"
                    },
                    "cannotReply": {
                      "type": "object",
                      "properties": {
                        "reason": {
                          "type": "string",
                          "enum": [
                            "window_closed",
                            "opted_out",
                            "suspended"
                          ]
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    },
                    "messages": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Message"
                      }
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/conversations/{id}/draft-reply": {
      "post": {
        "operationId": "draftReply",
        "summary": "A reply in the brand voice from Postfjord's AI, returned as text",
        "description": "Nothing is sent. Uses the workspace AI budget. At most 10 per minute per key.",
        "x-scope": "draft",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The conversation id from listConversations.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "instruction": {
                    "type": "string",
                    "maxLength": 500
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The draft",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "sent": {
                      "type": "boolean",
                      "const": false
                    },
                    "text": {
                      "type": "string"
                    },
                    "needsPerson": {
                      "type": "boolean"
                    },
                    "replyWindowOpen": {
                      "type": "boolean"
                    },
                    "note": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/conversations/{id}/reply": {
      "post": {
        "operationId": "sendReply",
        "summary": "Send a reply to the person, as Send in the Inbox",
        "description": "Only within 24 hours of the person's last message, never to a person who opted out, at most 10 per minute per key. Refusals come back as failed-precondition with the reason.",
        "x-scope": "publish",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The conversation id from listConversations.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "text"
                ],
                "properties": {
                  "text": {
                    "type": "string",
                    "maxLength": 1000
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "sent": {
                      "type": "boolean"
                    },
                    "conversationId": {
                      "type": "string"
                    },
                    "messageId": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/automations": {
      "get": {
        "operationId": "listAutomations",
        "summary": "The DM automations with trigger, status and counts",
        "x-scope": "read",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "automations": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Automation"
                      }
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/automations/{id}": {
      "patch": {
        "operationId": "setAutomation",
        "summary": "Switch an automation on or pause it",
        "x-scope": "publish",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The automation id from listAutomations.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "enabled"
                ],
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "enabled": {
                      "type": "boolean"
                    },
                    "changed": {
                      "type": "boolean"
                    },
                    "running": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    }
  }
}
