Skip to content

Management API

UmbSocial’s backoffice API is part of Umbraco’s management API, under:

/umbraco/management/api/v1/umbsocial/

It is documented in its own OpenAPI document, umbsocial (titled “UmbSocial API”), alongside Umbraco’s own in the Swagger UI. The OpenAPI document is the definitive description of each request and response; this page summarises it.

Every endpoint needs a signed-in backoffice user with access to the Social section and the permission in the table (see Permissions). Times are in UTC.

Method Route Permission Use
GET posts View List posts. Query: fromUtc, toUtc, status, channel (a channel key). At most 500.
GET posts/{key} View Get one post. 404 when it doesn’t exist or is hidden.
POST posts Write (+ Schedule) Create a post.
PUT posts/{key} Write (+ Schedule) Change a post.
DELETE posts/{key} Write Delete a post (from Social only).
POST posts/check Write Check a post against its channels without saving: live counts.
Method Route Permission Use
GET links/preview Write A link card read from the page. Query: url. Returns title, description, imageUrl, or a refusal (link.https_required, link.not_allowed, link.unreachable). Rate limited: 30 a minute per user (429). See Links and link cards.
POST media/preview Write Each network’s cut of an image at preview size. Body: { "mediaKey": …, "imageFraming": …, "networks": ["bluesky", "instagram"] } (at most 20 networks). Returns { "previews": [ { "network": "bluesky", "url": "…" } ] }; url is null when there’s none. The URLs come from the site’s image URL generator, so they are signed when the site has an imaging HMAC key. Refused with media.unknown or image.framing_invalid.

Creating or changing a post with any when other than Draft also needs Schedule (403 without it).

A post request (PostRequest):

{
"text": "Our new release is out.",
"linkUrl": "https://www.example.com/news/release",
"linkTitle": "What's new",
"linkDescription": null,
"linkImageUrl": "https://www.example.com/images/release-card.png",
"mediaKey": null,
"imageAltText": null,
"imageFraming": null,
"channels": {
"6f1c4c1e-3b0a-4f53-9a51-2d7e0b2f6a10": null,
"0b9d7a52-8f3e-4c1d-b6a4-7e5f3c2d1a90": "Our new release is out. Shorter for X."
},
"when": "Queue",
"atUtc": null
}

channels maps each channel key to that channel’s own text, or null for the main text. when is Draft, Queue, At (with atUtc) or Now. A mediaKey that isn’t a media item is refused (media.unknown). linkImageUrl is the link card’s image, an absolute https URL.

imageFraming frames the image for each network (see Images), as fractions from 0 to 1. A crop gives how much to trim from each side, Umbraco’s crop coordinates, and a network has at most one. Anything outside the image is refused (image.framing_invalid):

"imageFraming": {
"focalPoint": { "left": 0.7, "top": 0.4 },
"crops": [ { "network": "instagram", "left": 0.2, "top": 0, "right": 0.2, "bottom": 0 } ]
}

Create and update return { "key": "<post key>" }. posts/check always returns 200 with errors (about the post itself) and, per channel, channelKey, network, count, max and errors.

Method Route Permission Use
POST targets/{id}/retry Resolve Send a Failed or Unknown target again, as a new attempt.
POST targets/{id}/mark-published Resolve Record that a Failed or Unknown target is live. Body: { "postUrl": "https://…" } (or null).

{id} is the target’s id from a post’s targets. Both return { "key": "<post key>" }.

Method Route Permission Use
GET channels View The channels a post can go to, each with its network’s limits.
GET channels/networks ManageSettings The networks whose Automate provider is installed, with their connection type and limits.
GET channels/connections ManageSettings The Automate connections a new channel on a network can use. Query: network.
POST channels ManageSettings Add a channel. Body: network, name, automateConnectionId, options.
PUT channels/{key} ManageSettings Change a channel. Body: name, isEnabled, options.
DELETE channels/{key} ManageSettings Remove a channel.
POST channels/{key}/automation ManageSettings Create the channel’s managed automation if it’s missing.
POST channels/{key}/connection-test ManageSettings Test the channel’s Automate connection.

options are the network’s channel options, as a string-to-string object, for example { "organizationId": "12345" }.

A channel’s limits carry what a client needs to count as you type: maxCharacters, urlWeight, countsGraphemes, weightsWideCharacters, requiresImage, requiresLinkTitle, supportsAltText, link (Card, InText or NotSent) and linkOrImage. No response carries connection credentials or Automate settings: channels expose only Automate ids.

Method Route Permission Use
GET schedule View The time zone and the shared slots.
PUT schedule ManageSettings Replace the shared slots, and set the time zone with them when timeZoneId is given (both or neither are saved). Body: { "slots": [ { "day": …, "localTime": "09:00:00" } ], "timeZoneId": "Europe/London" }.
GET schedule/slots View The shared slots’ times in a range, and whether a post has taken each. Query: fromUtc, toUtc, at most 42 days apart (schedule.range_too_long).
GET schedule/next-free-slot View The next free slot, { "utc": … } (null when there is none).
GET settings ManageSettings { "timeZoneId": "Europe/London", "publishingEnabled": true, "environmentName": "Production", "includePostTextInWebhooks": false, "includeAuthorInWebhooks": false }.
PUT settings ManageSettings Set the webhook opt-ins and, when timeZoneId is given, the time zone (both or neither are saved). Body: { "timeZoneId": null, "includePostTextInWebhooks": false, "includeAuthorInWebhooks": false }. Every request sets both opt-ins: one left out is saved as false.
Method Route Permission Use
GET edition View The allowance in use: { "isLicensed": false, "maxChannels": 3, "historyDays": 90, "channelsUsed": 1 }. maxChannels and historyDays are null when there is no limit.
Status When
200 Done.
400 Refused. The body is problem details with the error codes in its codes extension (ProblemDetails.Extensions["codes"], written as a top-level codes property).
403 The user lacks the permission, or isn’t a backoffice user.
404 Something wasn’t found: a code ending .not_found (problem details), or a post that doesn’t exist or is hidden (GET posts/{key}, no body).
429 Rate limited (see below).

A refusal looks like this:

{
"status": 400,
"title": "The request was refused.",
"codes": [ "text.too_long:x", "link.title_required:linkedin" ]
}

Every problem found is listed, not only the first.

Channel responses are { "key": "<channel key>", "warnings": [ … ] }.

  • After adding a channel, warnings can hold channel.no_workspace (or another automation problem): the channel was saved, but without its automation. Fix the workspace and call POST channels/{key}/automation.
  • A refused change — adding, changing, removing a channel or retrying its automation (for example channel.settings_invalid, channel.cancelled:<message> or post.in_flight) — comes back as a problem response (400, or 404 for an unknown channel), and the channel is left as it was.

To stop a compromised editor account flooding your social accounts, each user can make at most 10 requests a minute that send posts out at once:

  • creating or changing a post with when set to Now, or At a time within the next 5 minutes;
  • retrying a target.

Over the limit, the request is refused with 429, and isn’t counted. Queued posts aren’t counted, because each takes its own slot. Marking as published isn’t counted. The limit is counted on each server: with several servers behind a load balancer, it applies on each.