RelayAPI

Java SDK

Official Java client library for RelayAPI. Post to 22 platforms from any JVM application.

Official Java SDK for RelayAPI. Post to 22 platforms, manage accounts, upload media, and track analytics. Works with any JVM language and ships both synchronous and asynchronous clients.

The Java client is generated and released separately from the RelayAPI monorepo. Check the relay-java repository for the surface, coordinates, and version in the release you install.

Setup

Install

implementation("dev.relayapi:relay-java:0.1.0")

Requires Java 8 or later.

Authenticate

import dev.relayapi.client.RelayClient;
import dev.relayapi.client.okhttp.RelayOkHttpClient;

RelayClient client = RelayOkHttpClient.fromEnv(); // reads RELAY_API_KEY / RELAY_BASE_URL

Or configure the key explicitly:

RelayClient client = RelayOkHttpClient.builder()
    .apiKey("rlay_live_...")
    .build();

Get your API key from relayapi.dev under Settings > API Keys.

Quick Examples

Create and Publish a Post

import dev.relayapi.models.posts.PostCreateParams;
import dev.relayapi.models.posts.PostCreateResponse;

PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Hello from the Java SDK!")
    .addTarget("twitter")
    .addTarget("linkedin")
    .scheduledAt("now")
    .build());

System.out.println("Post created: " + post.id() + " — status: " + post.status());

Schedule a Post

PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("This will go out tomorrow morning")
    .addTarget("twitter")
    .addTarget("linkedin")
    .scheduledAt("2025-01-15T09:00:00Z")
    .timezone("America/New_York")
    .build());

Save as Draft

PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Work in progress...")
    .addTarget("instagram")
    .scheduledAt("draft")
    .build());

List Connected Accounts

import dev.relayapi.models.accounts.AccountListResponse;

AccountListResponse accounts = client.accounts().list();
accounts.data().forEach(acc ->
    System.out.println(acc.platform() + ": " + acc.displayName() + " (" + acc.id() + ")"));

Upload Media

import dev.relayapi.models.media.MediaGetPresignUrlParams;
import dev.relayapi.models.media.MediaGetPresignUrlResponse;
import dev.relayapi.models.media.MediaConfirmParams;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;

// Get a presigned upload URL
MediaGetPresignUrlResponse presign = client.media().getPresignUrl(
    MediaGetPresignUrlParams.builder()
        .filename("photo.jpg")
        .contentType("image/jpeg")
        .build());

// PUT the bytes once using both headers required by the presign contract
HttpURLConnection upload = (HttpURLConnection) new URL(presign.uploadUrl()).openConnection();
upload.setRequestMethod("PUT");
upload.setRequestProperty("Content-Type", "image/jpeg");
upload.setRequestProperty("If-None-Match", "*");
upload.setDoOutput(true);
try (OutputStream body = upload.getOutputStream()) {
    body.write(Files.readAllBytes(Paths.get("photo.jpg")));
}
if (upload.getResponseCode() < 200 || upload.getResponseCode() >= 300) {
    throw new IllegalStateException("Media upload failed: " + upload.getResponseCode());
}

// Confirm the object so the pending upload becomes ready
String storageKey = URI.create(presign.url()).getPath().replaceFirst("^/", "");
client.media().confirm(MediaConfirmParams.builder()
    .storageKey(storageKey)
    .build());

// Posts take a media object with the canonical URL, not the media ID
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Check out this photo!")
    .addTarget("instagram")
    .addMedia(PostCreateParams.Media.builder()
        .url(presign.url())
        .type(PostCreateParams.Media.Type.IMAGE)
        .build())
    .scheduledAt("now")
    .build());

Cross-Post with Per-Platform Content

import dev.relayapi.core.JsonValue;
import java.util.Map;

PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Default content for all platforms")
    .addTarget("twitter")
    .addTarget("linkedin")
    .targetOptions(PostCreateParams.TargetOptions.builder()
        .putAdditionalProperty("twitter", JsonValue.from(Map.of("content", "Short version for Twitter")))
        .putAdditionalProperty("linkedin", JsonValue.from(Map.of("content", "Longer professional version for LinkedIn")))
        .build())
    .scheduledAt("now")
    .build());

Get Post Analytics

import dev.relayapi.models.analytics.AnalyticsRetrieveParams;
import dev.relayapi.models.analytics.AnalyticsRetrieveResponse;

AnalyticsRetrieveResponse analytics = client.analytics().retrieve(
    AnalyticsRetrieveParams.builder()
        .postId("post_abc123")
        .build());

Check Account Health

// All accounts
client.accounts().health().list();

// A single account by ID
client.accounts().health().retrieve("acc_twitter_123");

Async Usage

Every client has an async counterpart that returns CompletableFuture:

import dev.relayapi.client.RelayClientAsync;
import dev.relayapi.client.okhttp.RelayOkHttpClientAsync;
import dev.relayapi.models.posts.PostListResponse;
import java.util.concurrent.CompletableFuture;

RelayClientAsync client = RelayOkHttpClientAsync.fromEnv();
CompletableFuture<PostListResponse> posts = client.posts().list();

Error Handling

When the API returns a non-success status code, the SDK throws an unchecked exception. All extend RelayServiceException:

Status CodeException
400BadRequestException
401UnauthorizedException
404NotFoundException
429RateLimitException
>=500InternalServerException
import dev.relayapi.errors.RateLimitException;
import dev.relayapi.errors.RelayServiceException;

try {
    client.posts().create(PostCreateParams.builder()
        .content("Hello!")
        .addTarget("twitter")
        .scheduledAt("now")
        .build());
} catch (RateLimitException e) {
    System.out.println("Rate limited");
} catch (RelayServiceException e) {
    System.out.println("API error " + e.statusCode());
}

Raw Responses

Access the underlying HTTP response (status code, headers) with withRawResponse():

import dev.relayapi.core.http.HttpResponseFor;

HttpResponseFor<PostListResponse> response = client.posts().withRawResponse().list();
int statusCode = response.statusCode();
PostListResponse parsed = response.parse();

Configuration

Builder methodEnvironment VariableDefault
apiKey()RELAY_API_KEYRequired
baseUrl()RELAY_BASE_URLhttps://api.relayapi.dev
maxRetries()2

Timeouts

import dev.relayapi.core.RequestOptions;
import java.time.Duration;

client.posts().list(RequestOptions.builder()
    .timeout(Duration.ofSeconds(30))
    .build());

Found something wrong? Help us improve this page.

On this page