JuzPost Scheduling API

Put drafts on a publishing timer, one post at a time or in bulk.

GitHub OpenAPI

Operations 2

POST /posts/{postId}/schedule Schedule or immediately publish one draft #
POST /schedule Spread many drafts across your preset posting times #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/juzpost:juzpost-scheduling-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

juzpost-scheduling-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: JuzPost REST Scheduling API
  version: 1.0.0
  summary: Token-only API behind the `juzpost` command line client.
  description: 'The machine-facing surface of JuzPost: connect-once device login, draft creation,

    media upload to R2, and scheduling posts across connected social accounts.


    **Base URL.** `https://www.juzpost.com/api/cli/v1`. Note the `cli` segment:

    `/api/v1` is a different, older surface used by the dashboard and is not this API.


    **Authentication.** Every operation requires `Authorization: Bearer `. There are no

    unauthenticated operations.


    ## Get a token


    1. Open **Settings, General** in the JuzPost

    dashboard.

    2. Find the **API keys** card and choose **Generate API key**.

    3. Copy the key. It begins with `jp_live_` and is shown **once**, at creation. Store it in

    your secret manager before leaving the page.

    4. Check it works:


    ```bash

    curl https://www.juzpost.com/api/cli/v1/me \

    --header "Authorization: Bearer jp_live_YOUR_KEY"

    ```


    A `200` comes back with your account, plan and capabilities. A `401` means the key is

    wrong, revoked, or was pasted with a trailing newline.


    Revoke a key from the same card. Nothing in this API can delete the credential it is

    calling with, which is deliberate: an automated client should not be able to lock itself

    out, and revocation should be a human action.


    **One token, one account.** A token belongs to exactly one JuzPost account (a *workspace*).

    Every operation acts on that one account, and no operation anywhere accepts an account id.


    **Entitlements.** Every operation other than `GET /me` requires the

    caller''s plan to include the `api` capability, and `POST /schedule`,

    `POST /posts/{postId}/schedule` and `POST /storage/presign` additionally require `schedule`

    (or `publish`, for immediate posting). A plan that lacks the capability produces `402`,

    never `403`. Call `GET /me` first to check before attempting a write that is doomed.


    **Errors.** Every non-2xx response uses the same envelope: a single `error` object carrying

    a stable machine-readable `code`, a human `message`, and optional `details`. Never branch on

    the message text; branch on `error.code` or the HTTP status.


    **Unknown ids.** An id that is not yours is reported as `404`, never `403`, so no operation

    can be used to discover whether someone else''s data exists.'
  contact:
    name: JuzPost Support
    url: https://www.juzpost.com/support
  license:
    name: Proprietary
    url: https://www.juzpost.com/terms
servers:
- url: https://www.juzpost.com/api/cli/v1
  description: The only supported base URL.
security:
- bearerAuth: []
tags:
- name: Scheduling
  description: Put drafts on a publishing timer, one post at a time or in bulk.
paths:
  /posts/{postId}/schedule:
    post:
      operationId: schedulePost
      summary: Schedule or immediately publish one draft
      description: 'Fans one draft out to between one and ten social accounts. Supply `publishAt` to set a

        future fire time, or omit it entirely to publish right now. A future time must be at

        least five minutes and at most roughly six months out. The response reports one result

        per account; check each entry rather than only the top-level `success`, because a

        partial failure still returns `200`. Immediate publishing requires the `publish`

        capability while a future time requires `schedule`; either way a plan that lacks it

        returns `402`.'
      tags:
      - Scheduling
      parameters:
      - $ref: '#/components/parameters/PostIdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SchedulePostRequest'
      responses:
        '200':
          description: 'Timers created. Inspect `results` per account: a mix of scheduled and failed

            targets is still reported here rather than as an error status.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScheduleResult'
        '400':
          description: 'Invalid body, a fire time outside the allowed window, an override keyed by an

            account not in `socialAccountIds`, a non-text post with no media, a caption

            longer than the target platform allows, or `blueskySettings` for an account that is

            not on Bluesky, with an alt text count that differs from its files, or with labels on

            a text post. Also `wordpressSettings` for an account that is not on WordPress, a

            category name the site does not have (the error lists the site''s categories), or

            `seo` for an account whose site does not let it write Yoast SEO fields.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '404':
          description: 'The post does not exist, or at least one requested social account

            does not. Which one is deliberately not disclosed.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: 'The post left `draft` status before the timer could be claimed. It is already

            scheduled or uploading. Re-read the post before retrying.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /schedule:
    post:
      operationId: smartSchedule
      summary: Spread many drafts across your preset posting times
      description: 'The bulk scheduler. Resolves a group (or an explicit account list) to targets, then

        assigns drafts to your preset wall-clock times, `minPerDay` posts per day,

        walking forward from `startDate` and skipping any slot less than five minutes away.

        Every post goes to every resolved account. Set `dryRun` to true to see the plan without

        creating any timers. Always do this first. At most 50 posts are scheduled per call;

        the rest come back in `skipped` with a reason, so call again to continue. Targets are

        capped at 10 accounts.'
      tags:
      - Scheduling
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SmartScheduleRequest'
      responses:
        '200':
          description: 'The plan, plus what was scheduled and what was skipped. Individual entries in

            `scheduled` may carry `status: failed` with an `error`, so check them.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SmartScheduleResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '404':
          description: The named group does not exist.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: 'The request is well-formed but cannot be planned: no accounts resolved, more than

            10 accounts in the group, no posting times available, or `minPerDay` larger than the

            number of time slots. The message says which.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    SmartScheduleRequest:
      type: object
      description: 'Inputs for the bulk scheduler. You must supply either `group` or a non-empty

        `socialAccountIds`; if both are given, `socialAccountIds` wins.

        '
      required:
      - minPerDay
      properties:
        minPerDay:
          type: integer
          minimum: 1
          maximum: 20
          description: 'How many posts to place per day. Must not exceed the number of available time slots,

            or the call fails with `422`. Add more times or lower this number.

            '
        group:
          type: string
          description: 'Name of the channel group whose accounts should receive every post, matched exactly

            as returned by `GET /groups`. Unknown names return `404`.

            '
        socialAccountIds:
          type: array
          description: 'Explicit accounts to receive every post, used instead of `group`. Duplicates are

            collapsed. More than 10 distinct accounts is rejected with `422`.

            '
          items:
            type: string
            format: uuid
            description: A social account id from `GET /accounts`.
        postIds:
          type: array
          description: 'The specific drafts to schedule, in the order you want them placed. Omit this to use

            every draft you have, oldest first, up to 200. Any id that is not one of your

            drafts is reported in `skipped` rather than failing the call.

            '
          items:
            type: string
            format: uuid
            description: A draft post id.
        startDate:
          type: string
          format: date
          pattern: ^\d{4}-\d{2}-\d{2}$
          description: 'First calendar day to place posts on, as `YYYY-MM-DD` in your timezone.

            Defaults to tomorrow. Slots less than five minutes away are skipped, so a start date

            in the past simply yields fewer usable slots.

            '
        times:
          type: array
          description: 'Wall-clock posting times as `HH:MM` in your timezone, overriding the

            your defaults for this call only. Times are sorted ascending and the earliest

            `minPerDay` of them are used each day.

            '
          items:
            type: string
            pattern: ^\d{2}:\d{2}$
            description: A posting time as `HH:MM` in your timezone.
        dryRun:
          type: boolean
          description: 'When true, compute and return the plan without creating any timers. Run this first

            to confirm the dates look right, then repeat without it.

            '
    SchedulePostRequest:
      type: object
      description: 'Which accounts to send one draft to, and when. Every key used in an overrides map must

        also appear in `socialAccountIds`, or the whole request is rejected with `400`.

        '
      required:
      - socialAccountIds
      properties:
        socialAccountIds:
          type: array
          minItems: 1
          maxItems: 10
          description: 'The accounts to publish to, between one and ten. Duplicates are silently collapsed.

            If any id is not yours the entire request fails with `404` and does not

            partially schedule.

            '
          items:
            type: string
            format: uuid
            description: A social account id from `GET /accounts`.
        publishAt:
          type: string
          format: date-time
          description: 'When to publish, as an ISO 8601 UTC instant. Must be at least 5 minutes and at most

            roughly 6 months in the future. Omit this field entirely to publish immediately, but

            that path needs the `publish` capability instead of `schedule`.

            '
        title:
          type: string
          maxLength: 100
          description: 'Title to use for this run, overriding the stored one and persisted back onto the

            post. Note this limit is stricter than the 200 characters `POST /posts` accepts.

            '
        description:
          type: string
          maxLength: 10000
          description: 'Description to use for this run, overriding the stored one and persisted back onto

            the post. Also re-checked against each target platform''s own caption limit.

            '
        hashtags:
          type: array
          maxItems: 30
          description: 'Hashtags to use for this run, overriding the stored ones. Do not include the `#`.

            '
          items:
            type: string
            maxLength: 100
            description: A single hashtag, without the `#`.
        platformOverrides:
          type: object
          description: 'Per-account content overrides, keyed by social account id. Every key must be one of

            the ids in `socialAccountIds`.

            '
          additionalProperties:
            $ref: '#/components/schemas/PlatformOverride'
        publishAtOverrides:
          type: object
          description: 'Per-account fire times, keyed by social account id, each an ISO 8601 UTC instant.

            Accounts not listed use the top-level `publishAt`. Every key must be one of the ids

            in `socialAccountIds`.

            '
          additionalProperties:
            type: string
            format: date-time
            description: The ISO 8601 UTC fire time for that one account.
        tiktokSettings:
          type: object
          description: 'Per-account TikTok settings, keyed by social account id. Every key must be one of the

            ids in `socialAccountIds`.

            '
          additionalProperties:
            $ref: '#/components/schemas/TikTokSettings'
        pinterestSettings:
          type: object
          description: 'Per-account Pinterest settings, keyed by social account id. Every key must be one of

            the ids in `socialAccountIds`.

            '
          additionalProperties:
            $ref: '#/components/schemas/PinterestSettings'
        blueskySettings:
          type: object
          description: 'Per-account Bluesky alt text and content warnings, keyed by social account id. Every

            key must be one of the ids in `socialAccountIds` and must name a Bluesky account.

            '
          additionalProperties:
            $ref: '#/components/schemas/BlueskySettings'
        wordpressSettings:
          type: object
          description: 'Per-account WordPress post settings for a `blog` post, keyed by social account id.

            Every key must be one of the ids in `socialAccountIds` and must name a WordPress

            account. Nothing reaches WordPress before the post fires.

            '
          additionalProperties:
            $ref: '#/components/schemas/WordPressSettings'
    TargetResult:
      type: object
      description: The outcome of scheduling one account's copy of a post.
      required:
      - socialAccountId
      - platform
      - username
      - status
      properties:
        socialAccountId:
          type: string
          format: uuid
          description: The account this result is about.
        platform:
          type:
          - string
          - 'null'
          description: The account's platform id, e.g. `instagram`; null if the account was removed.
        username:
          type:
          - string
          - 'null'
          description: The account's handle on that platform; null if unknown or the account was removed.
        status:
          type: string
          description: 'The account''s state after the call. `scheduled` means a timer was created, `failed`

            when it could not be, or an existing state such as `published` when the target was

            already handled and was deliberately left alone.

            '
        error:
          type: string
          description: Present only when this account failed; explains why.
    ScheduledEntry:
      type: object
      description: One draft's placement in the plan, and what came of it.
      required:
      - postId
      - publishAt
      - accountIds
      properties:
        postId:
          type: string
          format: uuid
          description: The draft that was placed.
        publishAt:
          type: string
          format: date-time
          description: The ISO 8601 UTC instant this post will fire at.
        accountIds:
          type: array
          description: 'Every resolved account this post goes to. The bulk scheduler always cross-posts, so

            this is the same list for every entry.

            '
          items:
            type: string
            format: uuid
            description: A social account id receiving this post.
        status:
          type: string
          description: '`scheduled` when timers were created, `failed` when the underlying scheduler rejected

            this post. Absent on a dry run, since nothing was attempted.

            '
          enum:
          - scheduled
          - failed
        error:
          type: string
          description: Present only when `status` is `failed`; explains why that post was rejected.
    ScheduleResult:
      type: object
      description: 'Per-account results for one post. Note this shape is passed through from the underlying

        scheduler and, unlike the errors in this API, is not wrapped in an envelope.

        '
      required:
      - success
      - results
      properties:
        success:
          type: boolean
          description: 'True only when no account was left failed or pending. Partial failure still returns

            HTTP 200 with this set to false, so always inspect `results`.

            '
        results:
          type: array
          description: One entry per requested account, in the order they were processed.
          items:
            $ref: '#/components/schemas/TargetResult'
    PinterestSettings:
      type: object
      description: Per-account Pinterest options. Ignored for non-Pinterest accounts.
      required:
      - boardId
      properties:
        boardId:
          type: string
          pattern: ^\d+$
          maxLength: 32
          description: 'Numeric id of the board to pin to. Digits only. Anything else is rejected outright.

            '
    WordPressSettings:
      type: object
      description: 'How one WordPress account''s copy of a blog post is created. Every field is optional.

        Stored on that account''s copy and returned on `GET /posts/{postId}`.

        '
      properties:
        status:
          type: string
          enum:
          - publish
          - draft
          description: '`publish` (the default when omitted) or `draft`, which creates a WordPress draft for

            the site''s author to review.

            '
        categories:
          type: array
          description: 'Category names. Each must already exist on the site; an unknown name fails with

            `400` listing the site''s categories.

            '
          items:
            type: string
            description: One category name.
        tags:
          type: array
          description: Tag names. A tag the site does not have yet is created.
          items:
            type: string
            description: One tag name.
        slug:
          type: string
          maxLength: 200
          description: The URL slug. Made from the title when omitted.
        excerpt:
          type: string
          description: Short summary shown in feeds and link previews.
        featuredImageAlt:
          type: string
          description: Alt text for the featured image, which is the post's first image.
        seo:
          type: object
          description: 'Yoast SEO fields. Only for an account whose site lets it write them; otherwise the

            request fails with `400`.

            '
          properties:
            title:
              type: string
              description: SEO title shown in search results instead of the post title.
            description:
              type: string
              description: Meta description. Yoast prints none when this is omitted.
            focusKeyphrase:
              type: string
              description: Yoast focus keyphrase.
    Error:
      type: object
      description: 'The envelope returned by every non-2xx response in this API. There is never a top-level

        field other than `error`.

        '
      required:
      - error
      properties:
        error:
          type: object
          description: The single error describing why the request did not succeed.
          required:
          - code
          - message
          properties:
            code:
              $ref: '#/components/schemas/ErrorCode'
            message:
              type: string
              description: 'Human-readable explanation, safe to show to a user. Wording is not stable across

                releases, so never parse or match on it.

                '
            details:
              description: 'Optional structured context whose shape depends on the code, for example

                `{ "requiredPlan": "pro" }` on a `payment_required`, `{ "code":

                "invalid_cursor" }` on a bad cursor, or `{ "usedBytes": …, "quotaBytes": … }`

                when a storage quota is full. Treat it as opaque unless a specific operation

                documents its contents.

                '
    TikTokSettings:
      type: object
      description: Per-account TikTok options. Ignored for non-TikTok accounts.
      required:
      - privacyLevel
      properties:
        deliveryMode:
          type: string
          description: 'Optional. `direct_post` publishes straight to the account''s feed and is the only

            accepted value; any other value is rejected with a 400. Defaults to `direct_post`.

            '
          enum:
          - direct_post
          default: direct_post
        privacyLevel:
          type:
          - string
          - 'null'
          maxLength: 64
          description: 'Who can see the post, such as `PUBLIC_TO_EVERYONE` or `SELF_ONLY`. A null is

            rejected when the post is scheduled; leave the account out of `tiktokSettings`

            to publish to `PUBLIC_TO_EVERYONE`.

            '
        commercialContent:
          type: boolean
          description: Whether the video is disclosed as commercial content.
        brandOrganic:
          type: boolean
          description: Whether the disclosure marks the video as promoting your own brand.
        brandContent:
          type: boolean
          description: Whether the disclosure marks the video as a paid partnership.
    PlanSummary:
      type: object
      description: How the scheduler laid the posts out.
      required:
      - timezone
      - perDay
      - days
      - slotsUsed
      properties:
        timezone:
          type: string
          description: The timezone the wall-clock times were interpreted in.
        perDay:
          type: integer
          description: 'Posts placed per day. This is `minPerDay` clamped to the number of available slots.

            '
        days:
          type: integer
          description: How many distinct calendar days the plan spans.
        slotsUsed:
          type: array
          description: The specific `HH:MM` times used each day, in ascending order.
          items:
            type: string
            description: A posting time as `HH:MM` in your timezone.
    BlueskySettings:
      type: object
      description: 'Alt text for each file one Bluesky account publishes, and the content warning labels

        on the post. Stored on that account''s copy and returned on `GET /posts/{postId}`.

        '
      required:
      - altText
      - labels
      properties:
        altText:
          type: array
          maxItems: 4
          description: 'One entry per file, in the order that account publishes them. Use an empty string

            for a file with no description. The count must equal the number of files, or the

            request fails with `400`.

            '
          items:
            type: string
            description: The description of one file, up to 2,000 characters counted as Bluesky counts them (graphemes).
        labels:
          type: array
          maxItems: 2
          description: 'Content warnings. At most one of `sexual` (Suggestive), `nudity` or `porn` (Adult),

            plus `graphic-media`. Only on posts with images or video. Bluesky leaves posts

            labeled `sexual`, `porn` or `graphic-media` out of feeds for people who have not

            turned on adult content. Send `[]` for none.

            '
          items:
            type: string
            enum:
            - sexual
            - nudity
            - porn
            - graphic-media
            description: One Bluesky self-label value.
    PlatformOverride:
      type: object
      description: 'Per-account content overrides. Anything omitted falls back to the post''s own values, and

        an empty string is treated as "no override" rather than as blank content.

        '
      properties:
        title:
          type: string
          maxLength: 10000
          description: Title to use for this account instead of the post's title.
        description:
          type: string
          maxLength: 10000
          description: Description to use for this account instead of the post's description.
        hashtags:
          type: array
          maxItems: 30
          description: Hashtags to use for this account instead of the post's hashtags.
          items:
            type: string
            maxLength: 100
            description: A single hashtag, without the `#`.
    ErrorCode:
      type: string
      description: 'Stable machine-readable error identifier. Branch on this rather than on the message.

        Each code maps to exactly one HTTP status.

        '
      enum:
      - bad_request
      - unauthorized
      - payment_required
      - forbidden
      - not_found
      - conflict
      - unprocessable
      - pending
      - rate_limited
      - internal
    SkippedEntry:
      type: object
      description: A draft that was deliberately not scheduled, and why.
      required:
      - postId
      - reason
      properties:
        postId:
          type: string
          format: uuid
          description: The post that was skipped.
        reason:
          type: string
          description: 'Why it was skipped, typically that it is not a draft or not yours, or

            that it fell past the 50-posts-per-call limit, in which case simply call again.

            '
    SmartScheduleResponse:
      type: object
      description: The computed plan plus its outcome.
      required:
      - scheduled
      - skipped
      - plan
      properties:
        scheduled:
          type: array
          description: 'Every post that was placed, with its fire time. On a dry run these are proposals and

            nothing has been persisted.

            '
          items:
            $ref: '#/components/schemas/ScheduledEntry'
        skipped:
          type: array
          description: 'Posts that were not placed. Always check this. An empty array means everything you

            asked for was scheduled.

            '
          items:
            $ref: '#/components/schemas/SkippedEntry'
        plan:
          $ref: '#/components/schemas/PlanSummary'
        dryRun:
          type: boolean
          description: 'Present and true only when the request set `dryRun`. Its absence means timers were

            actually created.

            '
  responses:
    PaymentRequired:
      description: 'The plan behind this token does not include the capability this operation needs.

        `error.details.requiredPlan` names the tier that does. This is never a token problem, so

        do not retry without changing the plan.

        '
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    BadRequest:
      description: 'The request body or query string failed validation. `error.message` names the first

        problem found.

        '
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unauthorized:
      description: 'No bearer token was sent, or the token is unknown, revoked or malformed. Run the device

        login again to obtain a new token.

        '
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  parameters:
    PostIdPath:
      name: postId
      in: path
      required: true
      description: Id of the post, as returned by `POST /posts` or `GET /posts`.
      schema:
        type: string
        format: uuid
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: 'An API key created in the dashboard under Settings, General, API keys, sent as

        `Authorization: Bearer jp_live_...`. Required on every operation without exception.

        Missing, malformed, revoked or unknown tokens all produce `401` with code

        `unauthorized`.

        '