listDiscordChannels(String accountId) {
return http.convertList(ApiClient.unwrap(http.get(discord(accountId, "/channels"), null)), DiscordChannel.class);
}
- /** Move the account to another channel in the same server. */
+ /**
+ * Move the account to another channel in the same server.
+ *
+ * Wraps the API's {@code switchDiscordChannel} operation.
+ */
public DiscordChannel switchDiscordChannel(String accountId, String channelId) {
ObjectNode body = Json.MAPPER.createObjectNode().put("channel_id", channelId);
return http.convert(
@@ -395,25 +401,41 @@ public DiscordChannel switchDiscordChannel(String accountId, String channelId) {
DiscordChannel.class);
}
- /** The nickname and avatar the bot wears in the server. */
+ /**
+ * The nickname and avatar the bot wears in the server.
+ *
+ *
Wraps the API's {@code getDiscordIdentity} operation.
+ */
public DiscordIdentity getDiscordIdentity(String accountId) {
return http.convert(ApiClient.unwrap(http.get(discord(accountId, "/identity"), null)), DiscordIdentity.class);
}
- /** Change the nickname or avatar the bot wears in the server. */
+ /**
+ * Change the nickname or avatar the bot wears in the server.
+ *
+ *
Wraps the API's {@code updateDiscordIdentity} operation.
+ */
public DiscordIdentity updateDiscordIdentity(String accountId, UpdateDiscordIdentityParams params) {
return http.convert(
ApiClient.unwrap(http.request("PATCH", discord(accountId, "/identity"), params.toJson(), null)),
DiscordIdentity.class);
}
- /** Pinned messages in the account's channel. */
+ /**
+ * Pinned messages in the account's channel.
+ *
+ *
Wraps the API's {@code listDiscordPins} operation.
+ */
public List listDiscordPins(String accountId) {
return http.convertList(
ApiClient.unwrap(http.get(discord(accountId, "/messages/pinned"), null)), DiscordMessage.class);
}
- /** Remove a message from the account's channel. */
+ /**
+ * Remove a message from the account's channel.
+ *
+ * Wraps the API's {@code deleteDiscordMessage} operation.
+ */
public DiscordAck deleteDiscordMessage(String accountId, String messageId) {
return ack(http.delete(discord(accountId, "/messages/" + messageId)));
}
@@ -452,7 +474,11 @@ public DiscordThread createDiscordThread(
DiscordThread.class);
}
- /** Send one message to a member of the server. */
+ /**
+ * Send one message to a member of the server.
+ *
+ *
Wraps the API's {@code sendDiscordDm} operation.
+ */
public DiscordMessageRef sendDiscordDm(String accountId, String memberId, String content) {
ObjectNode body = Json.MAPPER.createObjectNode();
body.put("member_id", memberId);
@@ -461,39 +487,63 @@ public DiscordMessageRef sendDiscordDm(String accountId, String memberId, String
ApiClient.unwrap(http.post(discord(accountId, "/dm"), body)), DiscordMessageRef.class);
}
- /** The server's scheduled events. */
+ /**
+ * The server's scheduled events.
+ *
+ *
Wraps the API's {@code listDiscordEvents} operation.
+ */
public List listDiscordEvents(String accountId) {
return http.convertList(
ApiClient.unwrap(http.get(discord(accountId, "/events"), null)), DiscordScheduledEvent.class);
}
- /** One scheduled event. */
+ /**
+ * One scheduled event.
+ *
+ * Wraps the API's {@code getDiscordEvent} operation.
+ */
public DiscordScheduledEvent getDiscordEvent(String accountId, String eventId) {
return http.convert(
ApiClient.unwrap(http.get(discord(accountId, "/events/" + eventId), null)),
DiscordScheduledEvent.class);
}
- /** Add an event to the server's calendar. */
+ /**
+ * Add an event to the server's calendar.
+ *
+ *
Wraps the API's {@code createDiscordEvent} operation.
+ */
public DiscordScheduledEvent createDiscordEvent(String accountId, DiscordEventParams params) {
return http.convert(
ApiClient.unwrap(http.post(discord(accountId, "/events"), params.toJson())),
DiscordScheduledEvent.class);
}
- /** Change a scheduled event. */
+ /**
+ * Change a scheduled event.
+ *
+ *
Wraps the API's {@code updateDiscordEvent} operation.
+ */
public DiscordScheduledEvent updateDiscordEvent(String accountId, String eventId, DiscordEventParams params) {
return http.convert(
ApiClient.unwrap(http.request("PATCH", discord(accountId, "/events/" + eventId), params.toJson(), null)),
DiscordScheduledEvent.class);
}
- /** Remove a scheduled event. */
+ /**
+ * Remove a scheduled event.
+ *
+ *
Wraps the API's {@code deleteDiscordEvent} operation.
+ */
public DiscordAck deleteDiscordEvent(String accountId, String eventId) {
return ack(http.delete(discord(accountId, "/events/" + eventId)));
}
- /** The server's roster. */
+ /**
+ * The server's roster.
+ *
+ *
Wraps the API's {@code listDiscordMembers} operation.
+ */
public List listDiscordMembers(String accountId) {
return listDiscordMembers(accountId, null, null);
}
@@ -512,30 +562,50 @@ public List listDiscordMembers(String accountId, String query, In
DiscordMember.class);
}
- /** One member of the server. */
+ /**
+ * One member of the server.
+ *
+ * Wraps the API's {@code getDiscordMember} operation.
+ */
public DiscordMember getDiscordMember(String accountId, String memberId) {
return http.convert(
ApiClient.unwrap(http.get(discord(accountId, "/members/" + memberId), null)), DiscordMember.class);
}
- /** The server's roles, highest first. */
+ /**
+ * The server's roles, highest first.
+ *
+ *
Wraps the API's {@code listDiscordRoles} operation.
+ */
public List listDiscordRoles(String accountId) {
return http.convertList(ApiClient.unwrap(http.get(discord(accountId, "/roles"), null)), DiscordRole.class);
}
- /** Add a role to the server. */
+ /**
+ * Add a role to the server.
+ *
+ * Wraps the API's {@code createDiscordRole} operation.
+ */
public DiscordRole createDiscordRole(String accountId, DiscordRoleParams params) {
return http.convert(ApiClient.unwrap(http.post(discord(accountId, "/roles"), params.toJson())), DiscordRole.class);
}
- /** Change a role on the server. */
+ /**
+ * Change a role on the server.
+ *
+ *
Wraps the API's {@code updateDiscordRole} operation.
+ */
public DiscordRole updateDiscordRole(String accountId, String roleId, DiscordRoleParams params) {
return http.convert(
ApiClient.unwrap(http.request("PATCH", discord(accountId, "/roles/" + roleId), params.toJson(), null)),
DiscordRole.class);
}
- /** Remove a role from the server. */
+ /**
+ * Remove a role from the server.
+ *
+ *
Wraps the API's {@code deleteDiscordRole} operation.
+ */
public DiscordAck deleteDiscordRole(String accountId, String roleId) {
return ack(http.delete(discord(accountId, "/roles/" + roleId)));
}
diff --git a/src/main/java/com/fopost/sdk/resource/AdsResource.java b/src/main/java/com/fopost/sdk/resource/AdsResource.java
index cc1b2c9..4461c1e 100644
--- a/src/main/java/com/fopost/sdk/resource/AdsResource.java
+++ b/src/main/java/com/fopost/sdk/resource/AdsResource.java
@@ -1082,13 +1082,21 @@ public ReachFrequencyPrediction reachFrequencyPrediction(
ReachFrequencyPrediction.class);
}
- /** Holds the inventory the prediction priced. Also needs the {@code publish} scope. */
+ /**
+ * Holds the inventory the prediction priced. Also needs the {@code publish} scope.
+ *
+ *
Wraps the API's {@code reserveReachFrequency} operation.
+ */
public ReachFrequencyPrediction reserveReachFrequency(
String predictionId, ReachFrequencyActionParams params) {
return reachFrequencyAction(predictionId, "reserve", params);
}
- /** Also needs the {@code publish} scope. */
+ /**
+ * Also needs the {@code publish} scope.
+ *
+ *
Wraps the API's {@code cancelReachFrequency} operation.
+ */
public ReachFrequencyPrediction cancelReachFrequency(
String predictionId, ReachFrequencyActionParams params) {
return reachFrequencyAction(predictionId, "cancel", params);
diff --git a/src/main/java/com/fopost/sdk/resource/AnalyticsResource.java b/src/main/java/com/fopost/sdk/resource/AnalyticsResource.java
index dcfa2a5..045ce7b 100644
--- a/src/main/java/com/fopost/sdk/resource/AnalyticsResource.java
+++ b/src/main/java/com/fopost/sdk/resource/AnalyticsResource.java
@@ -6,6 +6,7 @@
import com.fopost.sdk.model.CollectSummary;
import com.fopost.sdk.model.ContentDecay;
import com.fopost.sdk.model.Demographics;
+import com.fopost.sdk.model.InboxAnalytics;
import com.fopost.sdk.model.LabelAnalytics;
import com.fopost.sdk.model.MetricChangePage;
import com.fopost.sdk.model.NativePost;
@@ -78,6 +79,28 @@ public Demographics demographics(AnalyticsParams params) {
ApiClient.unwrap(http.get("/v1/analytics/demographics", params.toQuery())), Demographics.class);
}
+ /**
+ * How the inbox performed: volume received and sent, reply coverage, median first-reply time,
+ * per-platform and per-account rows, response-time buckets and a weekday-by-hour heatmap.
+ * Honours {@code accountId}, {@code workspaceId} and {@code days}; {@code tz} buckets the
+ * heatmap and defaults to UTC.
+ */
+ public InboxAnalytics inbox(AnalyticsParams params, String tz) {
+ Map query = new LinkedHashMap<>();
+ if (params != null) {
+ // This endpoint reads the account under its snake_case name only.
+ params.toQuery().forEach((key, value) -> query.put("accountId".equals(key) ? "account_id" : key, value));
+ }
+ if (tz != null) {
+ query.put("tz", tz);
+ }
+ return http.convert(ApiClient.unwrap(http.get("/v1/analytics/inbox", query)), InboxAnalytics.class);
+ }
+
+ public InboxAnalytics inbox(AnalyticsParams params) {
+ return inbox(params, null);
+ }
+
/** Fetch fresh figures from the platforms now, rather than waiting for the next scheduled pass. */
public CollectSummary collect() {
return collect(null);
diff --git a/src/main/java/com/fopost/sdk/resource/AutomationsResource.java b/src/main/java/com/fopost/sdk/resource/AutomationsResource.java
index 127a396..a013ff9 100644
--- a/src/main/java/com/fopost/sdk/resource/AutomationsResource.java
+++ b/src/main/java/com/fopost/sdk/resource/AutomationsResource.java
@@ -3,9 +3,12 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fopost.sdk.internal.ApiClient;
import com.fopost.sdk.model.Automation;
+import com.fopost.sdk.model.AutomationRestore;
import com.fopost.sdk.model.AutomationRun;
import com.fopost.sdk.model.AutomationStats;
import com.fopost.sdk.model.AutomationTrigger;
+import com.fopost.sdk.model.AutomationVersion;
+import com.fopost.sdk.model.AutomationVersionPage;
import com.fopost.sdk.model.Page;
import com.fopost.sdk.model.PageMeta;
import com.fopost.sdk.param.AutomationParams;
@@ -87,6 +90,43 @@ public AutomationTrigger trigger(String automationId, Map payloa
AutomationTrigger.class);
}
+ /** Every saved graph, newest first, with the nodes each save added and removed. */
+ public AutomationVersionPage versions(String automationId) {
+ return versions(automationId, null, null);
+ }
+
+ public AutomationVersionPage versions(String automationId, Integer page, Integer perPage) {
+ Map query = new LinkedHashMap<>();
+ if (page != null) {
+ query.put("page", page);
+ }
+ if (perPage != null) {
+ query.put("per_page", perPage);
+ }
+ JsonNode body = http.get("/v1/automations/" + automationId + "/versions", query);
+ return new AutomationVersionPage(
+ http.convertList(body.path("data"), AutomationVersion.class),
+ http.convert(body.path("meta"), AutomationVersionPage.Meta.class));
+ }
+
+ /** One version, with its full graph. */
+ public AutomationVersion version(String automationId, int version) {
+ return http.convert(
+ ApiClient.unwrap(http.get("/v1/automations/" + automationId + "/versions/" + version, null)),
+ AutomationVersion.class);
+ }
+
+ /**
+ * Save that version's graph as a new version on top of the history; nothing is rewritten. The
+ * old graph must still validate under the automation's current trigger and workspace.
+ */
+ public AutomationRestore restoreVersion(String automationId, int version) {
+ return http.convert(
+ ApiClient.unwrap(http.post(
+ "/v1/automations/" + automationId + "/versions/" + version + "/restore", null)),
+ AutomationRestore.class);
+ }
+
public AutomationStats stats() {
return http.convert(ApiClient.unwrap(http.get("/v1/automations/stats", null)), AutomationStats.class);
}
diff --git a/src/main/java/com/fopost/sdk/resource/BlogResource.java b/src/main/java/com/fopost/sdk/resource/BlogResource.java
index c6583b4..6af80a0 100644
--- a/src/main/java/com/fopost/sdk/resource/BlogResource.java
+++ b/src/main/java/com/fopost/sdk/resource/BlogResource.java
@@ -204,7 +204,11 @@ public BlogArticleFull updateArticle(String workspaceId, String articleId, BlogP
BlogArticleFull.class);
}
- /** Write the article again from its instructions. Spends AI credits. */
+ /**
+ * Write the article again from its instructions. Spends AI credits.
+ *
+ * Wraps the API's {@code regenerateBlogArticle} operation.
+ */
public BlogArticle regenerateArticle(String workspaceId, String articleId) {
return action(workspaceId, articleId, "regenerate");
}
@@ -218,12 +222,20 @@ public BlogArticle publishArticle(String workspaceId, String articleId) {
return action(workspaceId, articleId, "publish");
}
- /** Move a draft, failed or rejected article to {@code in_review}. Any other state answers 409. */
+ /**
+ * Move a draft, failed or rejected article to {@code in_review}. Any other state answers 409.
+ *
+ *
Wraps the API's {@code submitBlogArticle} operation.
+ */
public BlogArticle submitArticle(String workspaceId, String articleId) {
return action(workspaceId, articleId, "submit");
}
- /** Take an article out of review without publishing it. */
+ /**
+ * Take an article out of review without publishing it.
+ *
+ *
Wraps the API's {@code rejectBlogArticle} operation.
+ */
public BlogArticle rejectArticle(String workspaceId, String articleId) {
return action(workspaceId, articleId, "reject");
}
diff --git a/src/main/java/com/fopost/sdk/resource/ConnectSessionsResource.java b/src/main/java/com/fopost/sdk/resource/ConnectSessionsResource.java
new file mode 100644
index 0000000..652d66c
--- /dev/null
+++ b/src/main/java/com/fopost/sdk/resource/ConnectSessionsResource.java
@@ -0,0 +1,37 @@
+package com.fopost.sdk.resource;
+
+import com.fopost.sdk.internal.ApiClient;
+import com.fopost.sdk.model.ConnectSession;
+import com.fopost.sdk.param.ConnectSessionParams;
+
+/**
+ * Hosted connect links: a single-use page, open for one hour, where one of your end users connects
+ * a social account into your workspace with no FoPost login. The account is tagged with the
+ * {@code externalUserId} you pass. Needs the {@code accounts} scope.
+ *
+ *
{@code
+ * ConnectSession session = client.connectSessions()
+ * .create(ConnectSessionParams.of("user_42", List.of("linkedin")).workspaceId(workspaceId));
+ * // hand session.url() to the user, then poll:
+ * ConnectSession later = client.connectSessions().get(session.id());
+ * }
+ */
+public final class ConnectSessionsResource {
+
+ private final ApiClient http;
+
+ public ConnectSessionsResource(ApiClient http) {
+ this.http = http;
+ }
+
+ /** Mint a link. {@code url} is on the returned session and is never shown again. */
+ public ConnectSession create(ConnectSessionParams params) {
+ return http.convert(ApiClient.unwrap(http.post("/v1/connect-sessions", params.toMap())), ConnectSession.class);
+ }
+
+ /** The session's state, and the connected account once it has been used. */
+ public ConnectSession get(String sessionId) {
+ return http.convert(
+ ApiClient.unwrap(http.get("/v1/connect-sessions/" + sessionId, null)), ConnectSession.class);
+ }
+}
diff --git a/src/main/java/com/fopost/sdk/resource/GoogleBusinessResource.java b/src/main/java/com/fopost/sdk/resource/GoogleBusinessResource.java
index 40f10b8..24f0001 100644
--- a/src/main/java/com/fopost/sdk/resource/GoogleBusinessResource.java
+++ b/src/main/java/com/fopost/sdk/resource/GoogleBusinessResource.java
@@ -36,7 +36,11 @@ public GoogleBusinessResource(ApiClient http) {
this.http = http;
}
- /** The connected location, in the Business Information shape. */
+ /**
+ * The connected location, in the Business Information shape.
+ *
+ * Wraps the API's {@code getGoogleBusinessLocation} operation.
+ */
public JsonNode getLocation(String accountId) {
return ApiClient.unwrap(http.get(path(accountId, "/location"), null));
}
@@ -46,12 +50,18 @@ public JsonNode getLocation(String accountId) {
* clears that field. Keys are the API's own snake_case names, such as
* {@code title}, {@code description}, {@code website_uri} and
* {@code primary_phone}.
+ *
+ *
Wraps the API's {@code updateGoogleBusinessLocation} operation.
*/
public JsonNode updateLocation(String accountId, Map fields) {
return ApiClient.unwrap(http.request("PATCH", path(accountId, "/location"), body(fields), null));
}
- /** The attribute values set on the location. */
+ /**
+ * The attribute values set on the location.
+ *
+ * Wraps the API's {@code getGoogleBusinessAttributes} operation.
+ */
public JsonNode getAttributes(String accountId) {
return getAttributes(accountId, false, null, null, null);
}
@@ -72,33 +82,57 @@ public JsonNode getAttributes(
return ApiClient.unwrap(http.get(path(accountId, "/attributes"), query.isEmpty() ? null : query));
}
- /** Only the named attributes change; every other one is left alone. */
+ /**
+ * Only the named attributes change; every other one is left alone.
+ *
+ *
Wraps the API's {@code updateGoogleBusinessAttributes} operation.
+ */
public JsonNode updateAttributes(String accountId, List