workspacesbreaking
Workspaces keep their brand in strategy and design_style; the legacy brand fields are removed
Brand settings live in strategy and design_style
- Removed from the workspace (
GET/POST/PATCHon/api/workspacesand/api/businesses, and theget_workspace/update_workspaceMCP tools):tone,audience_combined,caption_writing_rules,caption_suffix,custom_caption_prompt,custom_meme_prompt,custom_carousel_prompt,tagline,ctas,palettes,header_font,body_font,logo_url,logo_background_color,logo_width,include_stock,aspect_ratio,creative_typesanduse_logo_on_post. - Where they went — write brand voice, audience, caption rules and calls to action into
strategy, and colours, fonts, logo placement, content mix, stock photos and aspect ratio intodesign_style. Both are markdown documents on the workspace. - No compatibility window — this applies to every
Marky-Version. A request that names a removed field gets a422whose message names the field and says where it goes now. Reads no longer include the keys. - Kept —
strategy,design_style,brand_style_grid_url,platform_writing_instructions,eventsandtimezone. Settings saved in the Marky app are not deleted; the app still edits them. - Marky plugin users: update the plugin to its latest version.
Businesses are now workspaces
- New: workspace endpoints —
GET /api/workspaces(list_workspaces),POST /api/workspaces(create_workspace),GET,PATCHandDELETE /api/workspaces/{workspace_id}(get_workspace,update_workspace,delete_workspace). Same request and response bodies as the business endpoints, including under a pinnedMarky-Version. - Deprecated, still working —
/api/businessesand/api/businesses/{business_id}(list_businesses,create_business,get_business,update_business,delete_business) are marked deprecated in the OpenAPI document. Nothing you call today stops working, and no removal date is set. - Media uploads take
workspace_id—POST /api/media/from-url,/api/media/from-referenceand/api/media/uploadsacceptworkspace_id.business_idis deprecated but still accepted; if you send both, they must match or you get a422. - MCP tools renamed — tools/list shows
list_workspaces,get_workspaceandupdate_workspaceinstead oflist_businesses,get_businessandupdate_business. The old names are hidden but still callable, so existing agents keep working. See MCP Server. - Unchanged — nested paths such as
/api/businesses/{business_id}/posts, thebusiness_idargument on MCP tools, andbusiness_idin response bodies and webhook payloads. That value is the workspace ID. - Error messages say “Workspace not found” and “Workspace does not belong to this org”. Error codes are unchanged. Full mapping: Versioning.
SDKs and an n8n node
- New: SDKs for TypeScript and Python (
mymarky). Generated from this API’s OpenAPI document, so every endpoint is typed. They pinMarky-Version, raise typed errors, retry safely (create calls get an automaticIdempotency-Key), and verify webhook signatures (timestamp + replay window included). - New: n8n community node (
n8n-nodes-marky): list workspaces and connected accounts, create and schedule posts, upload media from a URL, and a trigger forpost.published. See n8n.
Webhook signatures you can verify
X-Marky-Signaturenow verifies. It issha256=+ the hex HMAC-SHA256 of"{X-Marky-Timestamp}." + raw body, keyed with your webhook secret, and the body we send is byte-for-byte the one we signed. Previously the signed bytes and the sent bytes differed, so the signature could not be verified.- New
X-Marky-Timestampheader — part of the signed string. Reject deliveries more than 5 minutes old so a captured request cannot be replayed. Retries are re-signed with a fresh timestamp. - Private addresses are refused —
POST /api/webhooksreturns400when the URL’s host resolves to a private, loopback, link-local or cloud-metadata address, and every delivery re-checks the address it connects to. - Verification code for Python and Node.js: Webhooks.
Schedule a list of posts in one call
- New:
POST /api/businesses/{business_id}/posts/schedule(schedule_posts) — send up to 50post_idsin the order they should go out.mode: "queue"(default) appends them to the posting queue in exactly that order;mode: "at"schedules each at the matchingtimes_local(e.g.2026-10-05T09:00), read in the business’s timezone with daylight saving handled for you. Each post gets its own answer:publish_time_local,warnings, arevision_id, or anerrorfor that post only, so one bad post never fails the batch. Calling it again for posts already queued changes nothing (ALREADY_SCHEDULED). queue_postis safe to call in parallel — several calls at once used to be able to land on the same place in line. They now always get distinct places. To keep a specific order, useschedule_postswith the list.- A post’s date is kept — a NEW post created with
scheduled_publish_timeis scheduled at that time byqueue_post(andschedule_posts), instead of being moved to the next open slot. The response hasscheduled_publish_timeset andqueuenull. - Unfilled placeholders are refused — a caption that still contains a
[[placeholder]](for example[[phone number]]) is not queued:queue_postanswers409 CAPTION_HAS_PLACEHOLDERand leaves the post unchanged. Fill it in withupdate_postfirst. queue.warningson queued posts —get_post,list_postsandlist_queued_postsnow warn when a queued post’s caption says “today”, a weekday or “this week” but it lands on another day (DATE_BOUND_COPY), or when it lands more than 90 days out (FAR_FUTURE).list_queued_postsrows also gainestimated_publish_time_local. Warnings never block anything.
Renaming a business onto another business’s name is refused
PATCH /api/businesses/{business_id}returns409 NAME_MATCHES_OTHER_WORKSPACEwhen the newnameis already used by another business in your org. The match ignores case and surrounding spaces; otherwise it is exact. Nothing in the request is saved, including any other fields sent with the name.- Why — a rename onto a sibling’s name almost always means the content was meant for that other business. The error body carries
other_business_idandother_business_nameso you can send the update there instead. - Override — send
allow_duplicate_name: truewhen you really do want two businesses with the same name (for example while swapping two names). Renames to a unique name, re-casing a business’s own name, and updates that don’t touchnamebehave exactly as before.
Schedule accepts approved (DRAFT) posts
POST /api/businesses/{business_id}/posts/{post_id}/scheduleacceptsDRAFT— a post you have approved sits inDRAFT, and scheduling it is the normal next step. The endpoint used to answer400 invalid_request(“Cannot schedule a post with status ‘DRAFT’”), so integrations had to move the post back toNEWfirst, which threw away the approval. Now it schedules directly and the post becomesSCHEDULED.- Nothing else changes —
NEWandSCHEDULEDposts behave as before.REJECTEDposts still return400 invalid_request, and published posts still return400 POST_ALREADY_PUBLISHED. If you worked around the old 400 by demoting posts toNEW, you can drop that step.
Set your own video cover
cover_media_idon create and update — pass the Marky media id of an image to use as the cover (the still shown before the video plays). Upload the still first withPOST /api/media/from-url(or/api/media/uploads) and pass the id it returns. Instagram uses it as the Reel cover; Facebook and Pinterest use it too. Without it, Marky keeps making a cover from the video’s first frame, exactly as before.video_cover_timestamp_ms— TikTok’s API only accepts a frame, never an image, so this picks the millisecond it uses (default1000). Send it alone if TikTok is the only place you care about the cover.- Both fields come back on reads —
get_postandlist_postsnow returncover_media_idandvideo_cover_timestamp_ms, so you can confirm what landed. - Nothing breaks — both fields are optional, and a post that omits them behaves exactly as it does today. LinkedIn, X, YouTube and Google Business choose their own cover and ignore these fields. Replacing a post’s media later does not clear the cover; send a new
cover_media_id.
List businesses returns summary rows now
- Slimmer list payload (breaking) —
GET /businesses(list_businesses) now returns summary rows only:id,name,description,industry,website,language,created_at,updated_at. The brand profile (tone, palettes, fonts, logo, writing rules) that used to ride along on every row is gone from the list. Action: if you read brand fields off the list, callget_businesswith the id instead. The list exists to find abusiness_id;get_businessis the full profile. - Name filter — pass
?name=to filter by a case-insensitive substring (bakerymatchesEli's Bakery).%and_are matched literally, not as wildcards. - Higher default limit — the default page size is now 100 (max 100). A typical org fits in one page. If
has_moreis true, keep paging withnext_cursorbefore concluding a business does not exist.
Breaking: publish_to → restrict_publish_to
- Clearer name for platform targeting — the optional post field
publish_tois nowrestrict_publish_toon create, update, schedule, and publish, and in responses. The name makes the behavior obvious: omit it or set it to null to publish to ALL connected platforms; provide a list (e.g.["instagram", "facebook"]) to restrict to just those. - Action required — the old
publish_tokey is no longer accepted; sending it returns a422naming the unknown field. Rename it torestrict_publish_toin your requests, and readrestrict_publish_tofrom responses. (It briefly shipped as an accepted alias; that alias is now removed.)
Rate limits
- Daily and weekly limits added — alongside the existing 100 requests/minute, each API key now also has a 2,000 requests/day and 10,000 requests/week limit. These are generous for normal automation (a weekly batch of 10 posts uses ~20 requests). A
429response names the specific window you hit, and theX-RateLimit-*andRetry-Afterheaders tell you when to retry. See Errors for header details.
Breaking changes
- Business-scoped endpoints are now nested under the business — endpoints that operate on a single business now live under
/api/businesses/{business_id}/...(for examplePOST /api/businesses/{business_id}/posts,GET /api/businesses/{business_id}/topics,POST /api/businesses/{business_id}/media). Thebusiness_idmoves out of the JSON body and query string and into the URL path. Org-level endpoints (/api/businesses,/api/keys,/api/webhooks,/api/feedback,/api/health) are unchanged.
New features
- Publish a post immediately —
POST /api/businesses/{business_id}/posts/{post_id}/publishfires the publish flow in the background and returns the post right away. PollGET /businesses/{business_id}/posts/{post_id}for the final status. Optionalpublish_tobody restricts the publish to a subset of connected platforms. - List integrations on a business —
GET /api/businesses/{business_id}/integrationsreturns the connected social accounts (platform, username, page, status). Use theidfield as theintegration_idin stats endpoints. - List posts on a connected platform —
GET /api/businesses/{business_id}/integrations/{integration_id}/postslists posts on the connected account (Marky-posted or otherwise) with stats. Supports cursor-based pagination. Covers all 7 platforms. - Per-post stats — all platforms —
GET /api/businesses/{business_id}/posts/{post_id}/statsreturns per-platform metrics for a post published through Marky, hydrated from your connected integrations. Covers Facebook, Instagram, LinkedIn, Twitter/X, TikTok, Pinterest, and YouTube. Stats are normalized into a consistent shape (likes,comment_count,reach,views,impressions, etc.) — no per-platform branching in your code. - Per-integration stats —
GET /api/businesses/{business_id}/integrations/{integration_id}/statsreturns account metrics for a single integration.GET /api/businesses/{business_id}/integrations/{integration_id}/posts/{external_post_id}/statsreturns metrics for a single post on a single platform — useful with thepublish_idvalues from/businesses/{business_id}/integrations/{integration_id}/posts.
Updates
- Giphy URLs in media uploads —
giphy.comis now an allowed host suffix when uploading media, so you can pass Giphy-hosted GIFs directly without re-uploading. - Better image upload handling — Images with malformed EXIF metadata no longer fail to upload, and image generation errors now return clearer messages.
- Retry on transient errors — CRUD endpoints now automatically retry transient database errors, improving reliability for posts, businesses, topics, and media.
- Restored analytics fields — Post-count and period columns are back on business analytics views, so business summaries return complete data.
Bug fixes
- Fixed search fallback when looking up uploaded images — orphaned uploads are now correctly resolved.
- Plan restrictions are kept in sync when Stripe sends subscription updates, preventing stale limits after plan changes.
- Narrowed the checkout fraud block to true fraud signals so legitimate customers are no longer incorrectly blocked.
- Stripe
InvalidRequestErrorresponses are now logged with full context before being surfaced as a400, making billing failures easier to debug.
Security
- Row-level security is now enforced on API keys and Canva connections, so credentials are only readable by the org that owns them.
New features
- Creative format selection — You can now specify a
creative_formatsarray when creating posts to control the visual format of each post. Options includedesign,carousel,meme,image,gif, andvideo. Formats are cycled round-robin across your generated posts. - AI image style modifiers — Marky now applies style modifiers and creative type mapping when generating AI images, producing visuals that better match the tone and format of each post.
- Reference images for video generation — Pass
reference_imageswhen generating videos to guide the visual style. Useful for maintaining brand consistency across video content. - Audio URL for shorts — You can now provide an
audio_urlwhen generating shorts, giving you direct control over the audio track instead of relying on auto-selected music.
Improvements
- Canva design owner filtering — Marky now identifies the owner of imported Canva designs and surfaces a clearer error when a design is shared as view-only, so you know exactly what to fix.
- Imagery preferences in media search — Your business-level imagery preferences are now consistently applied during media selection, so generated posts better reflect your visual brand.
- Webhook reliability — Background poster generation and webhook delivery have been restored for more reliable post-publish notifications.
- Analytics accuracy — Post analytics now correctly handle posts with zero views instead of treating them as missing data.
Bug fixes
- Fixed thumbnail generation for deleted or inaccessible videos — the system now handles these gracefully instead of failing silently.
- Improved video URL detection to support additional formats during thumbnail extraction.
- Fixed an issue where providing a
custom_ideawithout topics would not use the idea as the post topic. - Corrected the post restriction period reset schedule for subscription limits.
New features
- First comment on publish — When a post goes live on Facebook or LinkedIn, Marky can now automatically add the post link as the first comment. Useful for driving traffic without cluttering the caption.
- Video speedup transforms — Speed up video clips during post generation. Marky applies the transform automatically when a faster pace fits the design.
- Bring Your Own Key (BYOK) for Gemini — Connect your own Google Gemini API key to generate images and videos. BYOK orgs are not charged Marky credits for AI generation in Max chat.
- Carousel video support — Video generation now works with multi-page (carousel) templates, so you can create richer video posts.
- AppSumo credit allowance — AppSumo users now receive a monthly credit allowance.
Improvements
- Smarter text sizing — Post designs now auto-cap character counts based on the actual box dimensions and a minimum readable font size. Overflowing CTA text is shortened automatically.
- Layout-aware copy — The LLM receives
max_linesandchars_per_lineconstraints so generated text fits the design without overflow. - Smart image cropping — Google Vision
CROP_HINTSare used to frame subjects better across all generated images. - Better video music — Background music volume is reduced, and the music picker uses genre-based fallback instead of random selection when no audio tags are set.
- Imagery preferences — Your business-level imagery preferences now influence the image keywords Marky generates, giving you more relevant visuals.
- Onboarding improvements — New businesses generate 15 candidate posts during onboarding and surface the best 10 (previously 10 → 3).
Bug fixes
- Fixed a crash caused by non-numeric
letterSpacingvalues in post designs. - Fixed text vertical alignment in video rendering.
- Fixed an issue where video audio tracks were not persisted when saving designs.
- Corrected media type detection — uploaded videos are no longer incorrectly tagged as images.
- Fixed empty-string UUID handling in post placeholder creation.
- Resolved an error when extracting frames from inaccessible video URLs — the API now returns an actionable error message.
- Fixed video foreground animations using an unsupported
ZOOMeffect.
- Clarified rate limits in the Introduction and Errors guides (100 requests per minute).
- Added this changelog with an RSS feed for integrations and automation tools.
Public API (preview) — Automate posting and workspace management:
- Org-scoped API keys (create in Organization Settings in the dashboard).
- Posts, businesses, topics, library / media, webhooks, and feedback endpoints.
- Base URL:
https://api.mymarky.ai/api
