The official Java SDK. Java 11+, built on java.net.http.HttpClient, single
dependency (Jackson), Jackson JsonNode responses with a fluent params builder.
Or OmniSocials.fromEnv() (reads OMNISOCIALS_API_KEY). A missing key throws
AuthenticationException.
Create a post
Java
JsonNode res = client.posts().create(Params.builder() .put("content", "New drop this Friday") .put("channels", List.of("instagram", "facebook", "linkedin")) .put("scheduled_at", "2026-08-01T09:00:00Z") .put("media_urls", List.of("https://example.com/teaser.jpg")) .build());JsonNode post = res.get("data");System.out.println(post.get("id").asText() + " " + post.get("status").asText());
Any media_urls (or media_ids) entry can also be a map carrying per-media
alt text (max 1500 chars), delivered to Mastodon, Bluesky, X (photos),
Pinterest, Instagram (images), and LinkedIn (images):
Java
.put("media_urls", List.of(Params.of( "url", "https://example.com/teaser.jpg", "alt", "Red sneaker on a white background")))
Publish immediately:
Java
client.posts().createAndPublish(Params.builder() .put("content", "Going live right now") .put("channels", List.of("x", "bluesky")) .build());
import java.nio.file.Path;// From a path (filename and content type detected from the file)JsonNode res = client.media().upload(Path.of("photos/product.jpg"), Params.of("name", "product-hero"));// Or from bytes + filenamebyte[] bytes = java.nio.file.Files.readAllBytes(Path.of("photos/product.jpg"));client.media().upload(bytes, "product.jpg");
Analytics
Java
// One post's latest per-platform metricsJsonNode stats = client.analytics().post("post_id");System.out.println(stats.get("data").get("platforms").path("instagram").path("metrics"));// Batch: up to 100 posts in one callJsonNode batch = client.analytics().posts(List.of("id1", "id2", "id3"));// or: client.analytics().posts("id1", "id2", "id3");// Workspace-wide overviewJsonNode overview = client.analytics().overview(Params.of("period", "30d"));System.out.println(overview.get("data").get("total_impressions").asLong());// Account-level stats (followers etc)JsonNode accountStats = client.analytics().accounts(Params.of("platform", "instagram"));
Social Inbox
Read and answer DMs, comments and mentions from your connected accounts. The API key
needs the opt-in inbox:read / inbox:write scopes. The lists use cursor pagination
(cursor in, pagination.next_cursor out).
inbox().next() hands out the next conversation that still needs an answer, with the
thread and the post it belongs to. On a comment thread, pass message_id so the reply
goes under the comment you answer and not under the newest comment on the post.
Java
JsonNode next = client.inbox().next().get("data");while (next != null && !next.isNull()) { JsonNode message = next.get("message"); if (!next.get("reply_window").get("open").asBoolean()) { // An Instagram/Facebook DM past Meta's 24-hour window: answer it in the app client.inbox().markRead(message.get("conversation_id").asText()); next = client.inbox().next().get("data"); continue; } JsonNode reply = client.inbox().reply( message.get("conversation_id").asText(), Params.of( "text", "Thanks! DM sent.", "message_id", message.get("id").asText(), "include_next", true)); next = reply.get("next");}
See the Social Inbox guide for the platforms, the reply rules and the error
codes.
Pinterest product tags
Tag products on a Pin so people can shop the items in the image. List the product Pins
of the connected Pinterest account, then pass up to 24 of their Pin ids as
product_tags in the pinterest map of the post. Only product Pins of your own account
can be tagged; products of other merchants cannot.
The list answers with HTTP 200 in both cases: products on success, or an error
object (pinterest_not_connected, pinterest_catalog_access_required,
platform_error) when it could not be read. It reads the Pinterest catalog when the
connection has catalog access (given one time in the OmniSocials composer: Pinterest
options, Add products, Connect catalog), else the account's own Pins. The tags are added
right after the Pin is published. A product that Pinterest refuses never fails the post:
the post then carries pinterest.product_tags_result with the tagged and the skipped
Pin ids. client.pinterest().validateProduct(id) checks one Pin id or Pin link before
you post. On posts().update() the pinterest map replaces the stored one, so leave
product_tags out to remove the tags.
Verify webhooks
Spring Boot:
Java
import com.fasterxml.jackson.databind.JsonNode;import com.omnisocials.Webhooks;import com.omnisocials.errors.WebhookVerificationException;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestHeader;import org.springframework.web.bind.annotation.RestController;@RestControllerpublic class OmniSocialsWebhookController { private final String secret = System.getenv("OMNISOCIALS_WEBHOOK_SECRET"); @PostMapping("/omnisocials/webhook") public ResponseEntity<Void> handle( @RequestBody String rawBody, // Spring gives you the raw body as a String @RequestHeader("X-OmniSocials-Signature") String signature) { JsonNode event; try { event = Webhooks.verifySignature(rawBody, signature, secret, 300); } catch (WebhookVerificationException e) { return ResponseEntity.badRequest().build(); } switch (event.get("type").asText()) { case "post.published": System.out.println("Published: " + event.get("data").get("post_id").asText()); break; case "post.failed": System.err.println("Failed: " + event.get("data").get("post_id").asText()); break; case "post.rejected": System.err.println("Rejected: " + event.get("data").get("post_id").asText() + " " + event.get("data").get("approval").get("reason").asText()); break; default: break; } return ResponseEntity.ok().build(); }}
Events: post.scheduled, post.published, post.failed, post.approved and post.rejected.
The two approval events carry data.approval with status, decided_by (the approver's
user id) and reason (null on post.approved). To read the full review of a post (steps,
approvers, the rejection reason and the comment thread), call client.posts().getApproval(id).
Errors
All exceptions extend OmniSocialsException (unchecked) and live in
com.omnisocials.errors. ApiException exposes getStatus(), getCode(),
getMessage(), and getBody(). Types: ValidationException (400/422),
AuthenticationException (401), PermissionDeniedException (403),
NotFoundException (404), RateLimitException (429, getRetryAfter()),
ServerException (5xx), ApiConnectionException, WebhookVerificationException.