Skip to content
LunarPost

Developers

Lunar Post API and MCP docs

Schedule videos and images to your customers' social accounts from your own code, or let Claude and Cursor do it. One API key works for the REST API and the MCP server.

Base URLhttps://post.lnr.ae/api/post/v1

On this page

Overview

Lunar Post has a REST API for your servers and an MCP server for AI tools like Claude and Cursor. Both act in the workspace the API key belongs to, and share its plan, profiles and connected accounts.

REST API
https://post.lnr.ae/api/post/v1
MCP server
https://post.lnr.ae/api/post/mcp

What you can do

Quickstart

  1. Get a key

    Open the dashboard, go to API keys and create one. Copy it straight away: it's shown once.

  2. Check your plan

    Ask for this month's usage. If the key works, you get your plan and the posts you have left.

    curl https://post.lnr.ae/api/post/v1/usage \
      -H "Authorization: Bearer YOUR_API_KEY"
  3. Add a customer and post

    Create a profile, send its connect link, upload media and schedule it. Every endpoint is in the API reference below, with an example to copy.

Authentication

Send the key in the Authorization header of every request, as a bearer token.

Authorization: Bearer YOUR_API_KEY

Each key is read only, read and write, or (made by the workspace owner) read, write and approve. Every endpoint and tool says which scope it needs.

Treat a key like a password. Keep it on your server or in your AI tool's config, never in a web page or a public repository. Revoke it from the dashboard the moment you think it's leaked.

Connect Claude or Cursor

The MCP server lets an AI tool use Lunar Post for you: it lists profiles, uploads media and schedules posts when you ask. Replace YOUR_API_KEY with your key.

Claude Code

Run this once in your terminal. Claude Code can then use Lunar Post in every project.

claude mcp add --transport http lunar-post \
  https://post.lnr.ae/api/post/mcp \
  --header "Authorization: Bearer YOUR_API_KEY"

Claude Desktop

Open Settings, Developer, Edit config in Claude Desktop, add this to claude_desktop_config.json and restart Claude. Claude Desktop reaches the server through mcp-remote, so it needs Node.js installed.

{
  "mcpServers": {
    "lunar-post": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://post.lnr.ae/api/post/mcp",
        "--header",
        "Authorization:${LUNAR_POST_AUTH}"
      ],
      "env": {
        "LUNAR_POST_AUTH": "Bearer YOUR_API_KEY"
      }
    }
  }
}

Cursor

Add this to ~/.cursor/mcp.json for every project, or to .cursor/mcp.json in one project.

{
  "mcpServers": {
    "lunar-post": {
      "url": "https://post.lnr.ae/api/post/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_API_KEY"
      }
    }
  }
}

Other MCP clients

Any client that supports MCP over Streamable HTTP can connect. Give it the server URL and send the key as a bearer token.

URL:    https://post.lnr.ae/api/post/mcp
Header: Authorization: Bearer YOUR_API_KEY

MCP tools

The MCP server has 18 tools. Your AI tool picks the right one from what you ask, so you rarely name them yourself.

  • get_usageRead

    Get usage. Get the workspace's Lunar Post plan, this month's posts used against its limit (one post per account posted to, UTC calendar month) and live profiles against the plan's profiles.

  • list_profilesRead

    List profiles. List the workspace's profiles, newest first.

  • create_profileAsks firstWrite

    Create profile. Create a profile for one of the developer's customers.

  • get_profileRead

    Get profile. Get one profile with its accounts: each account's id, network, handle and status (connected, needs_reauth or disconnected; only connected accounts can post).

  • create_connect_linkWrite

    Create connect link. Make a short-lived link (1 hour by default) to a Lunar Post page where the profile's owner connects their social accounts by signing in on each network's own page.

  • list_accountsRead

    List accounts. List a profile's connected accounts: the ids schedule_post takes in accountIds, with network, handle, status and timezone.

  • create_upload_urlWrite

    Create upload URL. Start uploading a video (MP4 or MOV, up to 1 GB) or image (JPEG, PNG or WebP, up to 20 MB) you have as a file.

  • complete_uploadWrite

    Complete upload. Finish an upload after the PUT: checks the stored file's size and type, saves it as media and starts its content check.

  • import_media_from_urlWrite

    Import media from a link. Fetch a video or image from a public https link, check its real type and size, save it as media and start its content check.

  • list_mediaRead

    List media. List the workspace's Lunar Post media, newest first, with each item's id, kind, profile and contentCheck (pending, passed or held; only passed media can be posted).

  • schedule_postAsks firstWrite

    Schedule post. Schedule one media item to one or more of a profile's accounts, with a caption per network.

  • publish_nowAsks firstWrite

    Publish now. Post one media item to one or more of a profile's accounts right away, with a caption per network.

  • list_postsRead

    List posts. List Lunar Post posts, latest scheduled first, with status, time, live link (externalUrl) and failureReason.

  • get_postRead

    Get post. Get one post: its status (scheduled, publishing, published, failed…), when it goes or went out, the live link once published and the reason if it failed.

  • update_postAsks firstWrite

    Update post. Move a post that hasn't gone out to a new time and/or change its caption and hashtags.

  • cancel_postAsks firstWrite

    Cancel post. Cancel a post that hasn't gone out.

  • retry_postAsks firstWrite

    Retry post. Send a failed post again (it goes out in about 3 minutes).

  • approve_postAsks firstApprove

    Approve post. Approve a post waiting for the workspace owner's review, which schedules it.

Asks firstShows a preview and waits for your OK before it changes anything.

Plans and limits

Limits are hard: past them, the request is refused with plan_limit and nothing extra is charged. One post is one account posted to, counted by UTC calendar month.

PlanPosts a monthProfiles
Trial503
Starter50010
Growth2,50050
Scale10,000250

Each key can make 60 requests a minute. Past that you get rate_limited (HTTP 429) with a Retry-After header saying how many seconds to wait.

Responses and errors

Every response is JSON with two fields, data and error. On success, data holds the result and error is null. On failure it's the other way round, and error has a code to act on and a message you can show a person.

Success

{
  "data": {
    "posts": {
      "used": 12,
      "limit": 50
    },
    "profiles": {
      "used": 2,
      "limit": 3
    }
  },
  "error": null
}

Failure

{
  "data": null,
  "error": {
    "code": "plan_limit",
    "message": "This workspace has used its 50 posts for this month on the Trial plan. Posting opens again on the 1st (UTC)."
  }
}
CodeHTTP statusWhat it means
invalid_input400A field is missing or out of range. The message says which.
plan_limit403The month's posts, the plan's profiles or its media storage are used up, or the trial has ended.
rate_limited429Too many requests from this key. Wait for the Retry-After seconds.
unauthorized401The key is missing, malformed, revoked or expired.
forbidden403The key can't do this: a read-only key tried to write, or it lacks the approve scope.
not_found404Nothing with that id in this workspace.
invalid_json400The body isn't valid JSON.
internal500Something failed on our side. Try again; if it keeps happening, tell us.

OpenAPI

The whole REST API is described in one OpenAPI 3.1 file. Import it into Postman or Insomnia, give it to Cursor or another AI tool as context, or generate a client in your language.

curl -o lunar-post-openapi.json https://post.lnr.ae/api/post/v1/openapi.json

Download the OpenAPI file

API reference

Profiles and accounts

A profile is one of your customers. Each has its own connected accounts, and posts never cross between profiles.

GET/api/post/v1/profiles

The workspace's live profiles, newest first. Find one by your own id with `externalRef`; add `includeArchived=true` to see archived ones too.

MCP tool: list_profiles

Parameters

  • externalRefstringQueryOptional

    Only the profile with this externalRef. (at most 200 characters)

  • includeArchivedbooleanQueryOptional

    Include archived profiles. Default false.

  • limitintegerQueryOptional

    How many to return. Default 100. (1–250)

Example request

curl https://post.lnr.ae/api/post/v1/profiles \
  -H "Authorization: Bearer YOUR_API_KEY"

Example response

HTTP 200
{
  "data": {
    "profiles": [
      {
        "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
        "name": "Acme Coffee",
        "externalRef": "cus_8841",
        "createdAt": "2026-11-14T09:12:00.000Z",
        "archivedAt": null
      }
    ]
  },
  "error": null
}
POST/api/post/v1/profiles

Adds a profile for one of your customers. Uses one of the plan's profiles, and is refused with `plan_limit` when they're all in use (archive one to free it).

MCP tool: create_profile

Parameters

  • preview"1"QueryOptional

    `1` runs every check and returns `{ preview }` (what would happen) without changing anything.

Body

  • namestringRequired

    The customer's name, shown on the connect page. (1–80 characters)

  • externalRefstring | nullOptional

    Your own id for this customer. Unique among live profiles; null clears it. (at most 200 characters)

Example request

curl -X POST https://post.lnr.ae/api/post/v1/profiles \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Coffee",
    "externalRef": "cus_8841"
  }'

Example response

HTTP 201
{
  "data": {
    "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "name": "Acme Coffee",
    "externalRef": "cus_8841",
    "createdAt": "2026-11-14T09:12:00.000Z",
    "archivedAt": null
  },
  "error": null
}
GET/api/post/v1/profiles/{id}

One profile with its accounts. Only accounts with status `connected` can post; `needs_reauth` means the customer has to connect it again.

MCP tool: get_profile

Parameters

  • idstring (uuid)PathRequired

    The profile's id, from List profiles or Create a profile.

Example request

curl https://post.lnr.ae/api/post/v1/profiles/ID \
  -H "Authorization: Bearer YOUR_API_KEY"

Example response

HTTP 200
{
  "data": {
    "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "name": "Acme Coffee",
    "externalRef": "cus_8841",
    "createdAt": "2026-11-14T09:12:00.000Z",
    "archivedAt": null,
    "accounts": [
      {
        "id": "c4a1e2f3-5b6d-4e7f-8a9b-0c1d2e3f4a5b",
        "platform": "instagram",
        "handle": "acmecoffee",
        "avatarUrl": "https://cdn.example.com/avatars/acme.jpg",
        "status": "connected",
        "timezone": "Asia/Dubai"
      },
      {
        "id": "d7e8f9a0-1b2c-4d3e-8f4a-5b6c7d8e9f0a",
        "platform": "tiktok",
        "handle": "acme.coffee",
        "avatarUrl": null,
        "status": "connected",
        "timezone": "Asia/Dubai"
      }
    ]
  },
  "error": null
}
PATCH/api/post/v1/profiles/{id}

Renames a live profile, or changes or clears its externalRef. Send at least one of the two.

Parameters

  • idstring (uuid)PathRequired

    The profile's id, from List profiles or Create a profile.

Body

  • namestringOptional

    The customer's name, shown on the connect page. (1–80 characters)

  • externalRefstring | nullOptional

    Your own id for this customer. Unique among live profiles; null clears it. (at most 200 characters)

Example request

curl -X PATCH https://post.lnr.ae/api/post/v1/profiles/ID \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Coffee Roasters"
  }'

Example response

HTTP 200
{
  "data": {
    "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "name": "Acme Coffee Roasters",
    "externalRef": "cus_8841",
    "createdAt": "2026-11-14T09:12:00.000Z",
    "archivedAt": null
  },
  "error": null
}
POST/api/post/v1/profiles/{id}/archive

Archives a profile so it frees one of the plan's profiles. Refused while it has posts waiting to go out: cancel them first. Archiving an archived profile changes nothing.

Parameters

  • idstring (uuid)PathRequired

    The profile's id, from List profiles or Create a profile.

Example request

curl -X POST https://post.lnr.ae/api/post/v1/profiles/ID/archive \
  -H "Authorization: Bearer YOUR_API_KEY"

Example response

HTTP 200
{
  "data": {
    "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "name": "Acme Coffee",
    "externalRef": "cus_8841",
    "createdAt": "2026-11-14T09:12:00.000Z",
    "archivedAt": "2026-11-18T12:00:00.000Z"
  },
  "error": null
}
GET/api/post/v1/profiles/{id}/accounts

The profile's connected accounts: the ids Schedule a post takes in `accountIds`, with network, handle, status and timezone.

MCP tool: list_accounts

Parameters

  • idstring (uuid)PathRequired

    The profile's id, from List profiles or Create a profile.

Example request

curl https://post.lnr.ae/api/post/v1/profiles/ID/accounts \
  -H "Authorization: Bearer YOUR_API_KEY"

Example response

HTTP 200
{
  "data": {
    "accounts": [
      {
        "id": "c4a1e2f3-5b6d-4e7f-8a9b-0c1d2e3f4a5b",
        "platform": "instagram",
        "handle": "acmecoffee",
        "avatarUrl": "https://cdn.example.com/avatars/acme.jpg",
        "status": "connected",
        "timezone": "Asia/Dubai"
      },
      {
        "id": "d7e8f9a0-1b2c-4d3e-8f4a-5b6c7d8e9f0a",
        "platform": "tiktok",
        "handle": "acme.coffee",
        "avatarUrl": null,
        "status": "connected",
        "timezone": "Asia/Dubai"
      }
    ]
  },
  "error": null
}

Media

Upload a video or image, or hand over a link to one. Every file is checked before it can be posted.

POST/api/post/v1/media/uploads

Step one of an upload: returns a signed `uploadUrl`, the `headers` to send and the `storagePath`. PUT the file's bytes to `uploadUrl` with those headers within 2 hours, then call Complete an upload. Videos: MP4 or MOV up to 1 GB. Images: JPEG, PNG or WebP up to 20 MB.

MCP tool: create_upload_url

Body

  • fileNamestringRequired

    With its extension, e.g. launch.mp4. (1–200 characters)

  • contentType"video/mp4" | "video/quicktime" | "image/jpeg" | "image/png" | "image/webp"Required
  • sizeBytesintegerRequired

    The file's exact size in bytes. (at least 1)

  • profileIdstring | null (uuid)Optional

    File it under this profile (only its posts can use it). Leave out to share it across your profiles.

Example request

curl -X POST https://post.lnr.ae/api/post/v1/media/uploads \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "fileName": "cold-brew-launch.mp4",
    "contentType": "video/mp4",
    "sizeBytes": 48211904,
    "profileId": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
  }'

Example response

HTTP 201
{
  "data": {
    "storagePath": "1b4e28ba-2fa1-41d2-883f-0016d3cca427/post/7c9e6679-7425-40de-944b-e07fc1f90ae7/0f7e5c1a-3b2d-4e6f-8a9b-1c2d3e4f5a6b-cold-brew-launch.mp4",
    "uploadUrl": "https://storage.example.com/object/upload/sign/media/…?token=…",
    "method": "PUT",
    "headers": {
      "Content-Type": "video/mp4"
    },
    "expiresAt": "2026-11-14T12:02:00.000Z",
    "kind": "video"
  },
  "error": null
}
POST/api/post/v1/media/uploads/complete

Step two, after the PUT: checks the stored file's real size and type (removing it if they don't match what you declared), saves it as media and starts its content check. `contentCheck` stays `pending` until the check is done, usually under a minute.

MCP tool: complete_upload

Body

  • storagePathstringRequired

    (at most 400 characters)

  • sizeBytesintegerRequired

    (at least 1)

  • titlestringOptional

    A name for the media; also the YouTube title when the post has none. (at most 200 characters)

Example request

curl -X POST https://post.lnr.ae/api/post/v1/media/uploads/complete \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "storagePath": "1b4e28ba-2fa1-41d2-883f-0016d3cca427/post/7c9e6679-7425-40de-944b-e07fc1f90ae7/0f7e5c1a-3b2d-4e6f-8a9b-1c2d3e4f5a6b-cold-brew-launch.mp4",
    "sizeBytes": 48211904,
    "title": "Cold brew launch"
  }'

Example response

HTTP 201
{
  "data": {
    "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
    "profileId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "kind": "video",
    "source": "upload",
    "name": "cold-brew-launch.mp4",
    "title": "Cold brew launch",
    "createdAt": "2026-11-14T10:02:00.000Z",
    "contentCheck": "pending"
  },
  "error": null
}
POST/api/post/v1/media/import

Fetches a video or image from a public https link, reads its real type from the file itself, caps its size while it downloads, saves it as media and starts its content check. Links to private or local addresses are refused.

MCP tool: import_media_from_url

Body

  • urlstringRequired

    Public https link to the file itself. (at most 2000 characters)

  • profileIdstring | null (uuid)Optional

    File it under this profile (only its posts can use it). Leave out to share it across your profiles.

  • titlestringOptional

    A name for the media; also the YouTube title when the post has none. (at most 200 characters)

  • fileNamestringOptional

    Name to store it under. Taken from the link when left out. (at most 200 characters)

Example request

curl -X POST https://post.lnr.ae/api/post/v1/media/import \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://cdn.example.com/renders/cold-brew-launch.mp4",
    "profileId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "title": "Cold brew launch"
  }'

Example response

HTTP 201
{
  "data": {
    "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
    "profileId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "kind": "video",
    "source": "link",
    "name": "cold-brew-launch.mp4",
    "title": "Cold brew launch",
    "createdAt": "2026-11-14T10:02:00.000Z",
    "contentCheck": "pending"
  },
  "error": null
}
GET/api/post/v1/media

The workspace's Lunar Post media, newest first, with each item's `contentCheck`: `pending`, `passed` (can be posted) or `held` (waiting for a content review, can't be posted). Pages with `nextCursor`.

MCP tool: list_media

Parameters

  • profileIdstringQueryOptional

    A profile id, or `shared` for media shared across profiles.

  • kind"video" | "image"QueryOptional

    Only videos or only images.

  • limitintegerQueryOptional

    How many to return. Default 20. (1–100)

  • cursorstringQueryOptional

    The nextCursor from the previous page.

Example request

curl https://post.lnr.ae/api/post/v1/media \
  -H "Authorization: Bearer YOUR_API_KEY"

Example response

HTTP 200
{
  "data": {
    "media": [
      {
        "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
        "profileId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
        "kind": "video",
        "source": "upload",
        "name": "cold-brew-launch.mp4",
        "title": "Cold brew launch",
        "createdAt": "2026-11-14T10:02:00.000Z",
        "contentCheck": "passed"
      }
    ],
    "nextCursor": null
  },
  "error": null
}
GET/api/post/v1/media/{id}

One media item with its content check. Poll it after an upload until `contentCheck` is `passed`.

Parameters

  • idstring (uuid)PathRequired

    The media item's id, from Complete an upload, Import from a link or List media.

Example request

curl https://post.lnr.ae/api/post/v1/media/ID \
  -H "Authorization: Bearer YOUR_API_KEY"

Example response

HTTP 200
{
  "data": {
    "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
    "profileId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "kind": "video",
    "source": "upload",
    "name": "cold-brew-launch.mp4",
    "title": "Cold brew launch",
    "createdAt": "2026-11-14T10:02:00.000Z",
    "contentCheck": "passed"
  },
  "error": null
}
DELETE/api/post/v1/media/{id}

Deletes the file. Refused while posts using it are waiting to go out: cancel them first. Posts that already went out keep their history.

Parameters

  • idstring (uuid)PathRequired

    The media item's id, from Complete an upload, Import from a link or List media.

Example request

curl -X DELETE https://post.lnr.ae/api/post/v1/media/ID \
  -H "Authorization: Bearer YOUR_API_KEY"

Example response

HTTP 200
{
  "data": {
    "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
    "deleted": true
  },
  "error": null
}

Posts

Schedule one media item to a profile's accounts with a caption per network, then follow and change the posts.

POST/api/post/v1/posts

Posts one media item to one or more of a profile's accounts: `when` is a time, `"now"` or `"next_free_slot"`. Each account uses one of this month's posts, reserved before anything is written and refused with `plan_limit` at the limit. Keys act as editors: in a workspace that reviews posts, they wait for the owner's approval (`awaitingApproval`).

MCP tool: schedule_post

Parameters

  • preview"1"QueryOptional

    `1` runs every check and returns `{ preview }` (what would happen) without changing anything.

Body

  • mediaIdstring (uuid)Required

    Media whose content check passed.

  • accountIdsarray of string (uuid)Required

    All from the same profile. Each account counts as one post. (1–25 items)

  • whenstringRequired

    "now", "next_free_slot" (each account's next posting time) or an ISO 8601 time with offset at least 2 minutes ahead.

  • profileIdstring | null (uuid)Optional

    The profile whose accounts you post to. Null or absent: the workspace's own accounts.

  • captionsobjectOptional

    One caption per network.

  • captionobjectOptional

    The caption for every network without an entry in captions.

  • titlestringOptional

    YouTube and Pinterest title. (at most 100 characters)

  • sendForReviewbooleanOptional

    Hold the posts for the workspace owner's approval.

  • aiGeneratedbooleanOptional

    Label the post as AI-generated where the network supports it (YouTube, TikTok).

  • tiktokobjectOptional
  • youtubeobjectOptional

Example request

curl -X POST https://post.lnr.ae/api/post/v1/posts \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "mediaId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
    "profileId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "accountIds": [
      "c4a1e2f3-5b6d-4e7f-8a9b-0c1d2e3f4a5b",
      "d7e8f9a0-1b2c-4d3e-8f4a-5b6c7d8e9f0a"
    ],
    "captions": {
      "instagram": {
        "caption": "Cold brew is back. Twelve hours steeped, zero bitterness.",
        "hashtags": [
          "coldbrew",
          "acmecoffee"
        ]
      },
      "tiktok": {
        "caption": "POV: your coffee took 12 hours so you don'\''t have to wait",
        "hashtags": [
          "coldbrew"
        ]
      }
    },
    "when": "2026-11-20T09:00:00+04:00",
    "aiGenerated": true
  }'

Example response

HTTP 201
{
  "data": {
    "postIds": [
      "3f8f6c1e-2b4a-4c3d-9e5f-1a2b3c4d5e6f",
      "8b2d4f6a-1c3e-4a5b-9d7f-0e2a4c6b8d1f"
    ],
    "awaitingApproval": false,
    "usage": {
      "used": 14,
      "limit": 50
    }
  },
  "error": null
}
GET/api/post/v1/posts

Lunar Post posts, latest scheduled first, with status, live link and failure reason. Filter by profile, account, network, status and a window on the scheduled time.

MCP tool: list_posts

Parameters

  • profileIdstringQueryOptional

    A profile id, or `unprofiled` for the workspace's own accounts.

  • accountIdstring (uuid)QueryOptional

    Only this account's posts.

  • platform"instagram" | "tiktok" | "linkedin" | "facebook" | "threads" | "youtube" | "pinterest"QueryOptional

    Only this network.

  • status"draft" | "ready" | "approved" | "scheduled" | "publishing" | "awaiting_creator" | "published" | "failed" | "cancelled"QueryOptional

    Only posts in this status.

  • fromstring (date-time)QueryOptional

    Scheduled at or after (ISO 8601 with offset).

  • tostring (date-time)QueryOptional

    Scheduled before (ISO 8601 with offset).

  • limitintegerQueryOptional

    How many to return. Default 20. (1–100)

Example request

curl https://post.lnr.ae/api/post/v1/posts \
  -H "Authorization: Bearer YOUR_API_KEY"

Example response

HTTP 200
{
  "data": {
    "posts": [
      {
        "id": "3f8f6c1e-2b4a-4c3d-9e5f-1a2b3c4d5e6f",
        "platform": "instagram",
        "accountId": "c4a1e2f3-5b6d-4e7f-8a9b-0c1d2e3f4a5b",
        "status": "scheduled",
        "caption": "Cold brew is back. Twelve hours steeped, zero bitterness.",
        "hashtags": [
          "coldbrew",
          "acmecoffee"
        ],
        "scheduledFor": "2026-11-20T05:00:00.000Z",
        "publishedAt": null,
        "externalUrl": null,
        "failureReason": null,
        "videoId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
        "mediaId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
        "profileId": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
      }
    ]
  },
  "error": null
}
GET/api/post/v1/posts/{id}

One post: its status, when it goes or went out, the live link once published (`externalUrl`) and `failureReason` if it failed.

MCP tool: get_post

Parameters

  • idstring (uuid)PathRequired

    The post's id, from Schedule a post (postIds) or List posts.

Example request

curl https://post.lnr.ae/api/post/v1/posts/ID \
  -H "Authorization: Bearer YOUR_API_KEY"

Example response

HTTP 200
{
  "data": {
    "id": "3f8f6c1e-2b4a-4c3d-9e5f-1a2b3c4d5e6f",
    "platform": "instagram",
    "accountId": "c4a1e2f3-5b6d-4e7f-8a9b-0c1d2e3f4a5b",
    "status": "published",
    "caption": "Cold brew is back. Twelve hours steeped, zero bitterness.",
    "hashtags": [
      "coldbrew",
      "acmecoffee"
    ],
    "scheduledFor": "2026-11-20T05:00:00.000Z",
    "publishedAt": "2026-11-20T05:00:41.000Z",
    "externalUrl": "https://www.instagram.com/reel/C9xYz12AbCd/",
    "failureReason": null,
    "videoId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
    "mediaId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
    "profileId": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
  },
  "error": null
}
PATCH/api/post/v1/posts/{id}

Moves a post that hasn't gone out and/or changes its caption and hashtags. In a workspace that reviews posts, changing an approved post sends it back for approval.

MCP tool: update_post

Parameters

  • idstring (uuid)PathRequired

    The post's id, from Schedule a post (postIds) or List posts.

  • preview"1"QueryOptional

    `1` runs every check and returns `{ preview }` (what would happen) without changing anything.

Body

  • whenstringOptional

    ISO 8601 with offset, at least 2 minutes ahead.

  • captionstringOptional

    (at most 5000 characters)

  • hashtagsarray of stringOptional

    (at most 30 items)

Example request

curl -X PATCH https://post.lnr.ae/api/post/v1/posts/ID \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "when": "2026-11-21T09:00:00+04:00"
  }'

Example response

HTTP 200
{
  "data": {
    "id": "3f8f6c1e-2b4a-4c3d-9e5f-1a2b3c4d5e6f",
    "platform": "instagram",
    "accountId": "c4a1e2f3-5b6d-4e7f-8a9b-0c1d2e3f4a5b",
    "status": "scheduled",
    "caption": "Cold brew is back. Twelve hours steeped, zero bitterness.",
    "hashtags": [
      "coldbrew",
      "acmecoffee"
    ],
    "scheduledFor": "2026-11-21T05:00:00.000Z",
    "publishedAt": null,
    "externalUrl": null,
    "failureReason": null,
    "videoId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
    "mediaId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
    "profileId": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
  },
  "error": null
}
DELETE/api/post/v1/posts/{id}

Cancels a post that hasn't gone out. It won't be posted, and the post it used comes back to its month's allowance.

MCP tool: cancel_post

Parameters

  • idstring (uuid)PathRequired

    The post's id, from Schedule a post (postIds) or List posts.

  • preview"1"QueryOptional

    `1` runs every check and returns `{ preview }` (what would happen) without changing anything.

Example request

curl -X DELETE https://post.lnr.ae/api/post/v1/posts/ID \
  -H "Authorization: Bearer YOUR_API_KEY"

Example response

HTTP 200
{
  "data": {
    "id": "3f8f6c1e-2b4a-4c3d-9e5f-1a2b3c4d5e6f",
    "platform": "instagram",
    "accountId": "c4a1e2f3-5b6d-4e7f-8a9b-0c1d2e3f4a5b",
    "status": "cancelled",
    "caption": "Cold brew is back. Twelve hours steeped, zero bitterness.",
    "hashtags": [
      "coldbrew",
      "acmecoffee"
    ],
    "scheduledFor": "2026-11-20T05:00:00.000Z",
    "publishedAt": null,
    "externalUrl": null,
    "failureReason": null,
    "videoId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
    "mediaId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
    "profileId": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
  },
  "error": null
}
POST/api/post/v1/posts/{id}/retry

Sends a failed post again, about 3 minutes later. If the failure reason says the account needs reconnecting, send the customer a new connect link first.

MCP tool: retry_post

Parameters

  • idstring (uuid)PathRequired

    The post's id, from Schedule a post (postIds) or List posts.

  • preview"1"QueryOptional

    `1` runs every check and returns `{ preview }` (what would happen) without changing anything.

Example request

curl -X POST https://post.lnr.ae/api/post/v1/posts/ID/retry \
  -H "Authorization: Bearer YOUR_API_KEY"

Example response

HTTP 200
{
  "data": {
    "id": "3f8f6c1e-2b4a-4c3d-9e5f-1a2b3c4d5e6f",
    "platform": "instagram",
    "accountId": "c4a1e2f3-5b6d-4e7f-8a9b-0c1d2e3f4a5b",
    "status": "scheduled",
    "caption": "Cold brew is back. Twelve hours steeped, zero bitterness.",
    "hashtags": [
      "coldbrew",
      "acmecoffee"
    ],
    "scheduledFor": "2026-11-20T05:08:00.000Z",
    "publishedAt": null,
    "externalUrl": null,
    "failureReason": null,
    "videoId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
    "mediaId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
    "profileId": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
  },
  "error": null
}
POST/api/post/v1/posts/{id}/approve

Approves a post waiting for review (status `ready`), which schedules it. Needs a key with the approve scope, which only the workspace owner can make.

MCP tool: approve_post

Parameters

  • idstring (uuid)PathRequired

    The post's id, from Schedule a post (postIds) or List posts.

  • preview"1"QueryOptional

    `1` runs every check and returns `{ preview }` (what would happen) without changing anything.

Example request

curl -X POST https://post.lnr.ae/api/post/v1/posts/ID/approve \
  -H "Authorization: Bearer YOUR_API_KEY"

Example response

HTTP 200
{
  "data": {
    "id": "3f8f6c1e-2b4a-4c3d-9e5f-1a2b3c4d5e6f",
    "status": "scheduled",
    "scheduledFor": "2026-11-20T05:00:00.000Z"
  },
  "error": null
}

Usage

Where the workspace stands against its Lunar Post plan this month.

GET/api/post/v1/usage

The plan, this month's posts used against its limit (one post per account posted to, counted by UTC calendar month) and live profiles against the plan's profiles. Limits are hard: past them, writes are refused with `plan_limit`. `plan` is `trial`, `starter`, `growth`, `scale` or `none` (the trial ended without a paid plan: everything stays readable, but posting, uploads and new profiles are refused until a plan is chosen). `trialEndsAt` is when the 14-day trial ends; `renewsAt` is the paid plan's next renewal, and `endsAt` is set instead when it's cancelled.

MCP tool: get_usage

Example request

curl https://post.lnr.ae/api/post/v1/usage \
  -H "Authorization: Bearer YOUR_API_KEY"

Example response

HTTP 200
{
  "data": {
    "plan": "trial",
    "period": "2026-11-01",
    "posts": {
      "used": 12,
      "limit": 50
    },
    "profiles": {
      "used": 2,
      "limit": 3
    },
    "trialEndsAt": "2026-11-28T10:00:00.000Z",
    "renewsAt": null,
    "endsAt": null
  },
  "error": null
}