Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 30 additions & 25 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ All notable changes to this project are documented here. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses
[Semantic Versioning](https://semver.org/).

## [Unreleased]
## [0.3.0] - 2026-10-18

### Added

Expand Down Expand Up @@ -94,6 +94,35 @@ All notable changes to this project are documented here. The format follows
`updateSlackIdentity` (`UpdateSlackIdentityParams`) for a Slack account. A webhook-connected
account answers 409 `webhook_connection`.

- `ads()`: the campaign tree and its objects, read live from Meta: `accountTree`, and
create/get/update/delete/duplicate for campaigns, ad sets and network ads, plus `bulkSetStatus`.
The writes need the `publish` scope as well as `ads`.
- `ads()`: `creatives`, `createCreative` (image, video or carousel), `creative`, `deleteCreative`;
`audience`, `updateAudience`, `deleteAudience`, `addAudienceUsers`; `estimateReach`; `insights`
and `adInsights` with an optional breakdown and daily timeline.
- `ads()`: `leadForm`, `archiveLeadForm`, the cursor-paged `leadsFeed`, and `leadPages`,
`subscribeLeadPage`, `unsubscribeLeadPage`.
- `CreateAdParams.urlTags` and `Ad.Creative.urlTags`.

- `inbox()`: `like`, `unlike`, `pin`, `unpin`, `react`, `editComment`, `startConversation`
(`StartConversationParams`), `setTyping`, and `reply` with `InboxReplyParams` for media and quick
replies. All of them also need the `publish` scope, as does deleting our own reply.
- `InboxItem` gains `liked`, `pinned`, `reaction`, `editedAt` and the `canLike`, `canPin`,
`canEdit`, `canReact`, `canSendMedia`, `canQuickReply` and `canPrivateReply` flags;
`InboxAccount` gains `canStartConversation`.
- `validate` resource (`client.validate()`): `post`, `length` and `media` check a draft, a text
length, or a media url against the platform rules without creating a post. Needs the `posts`
scope.
- `accounts()`: `redditSubreddits`, `redditSubredditRules`, `redditFlairs` and
`setRedditDefaultSubreddit` for a Reddit account, plus `validate().subreddit` to check a
subreddit before publishing and `inbox().vote(itemId, direction)` with `up`, `down` or `none`
to take an earlier vote back. A flair id is valid only in the subreddit it came from.
- `blogs` resource (`client.blogs()`): `listBlogs`, `listArticles`, `getArticle`, `createArticle`,
`updateArticle`, `deleteArticle`, `listProducts` and `updateProduct` reach the blogs, articles
and products a connected site already owns, addressed by the platform's own ids.
`updateArticle` edits the live article in place. Reads need the `posts` scope; writes also
need `publish`.

### Changed

- `blog()` matches the live API contract: the workspace travels as `workspace_id` on the query
Expand All @@ -117,30 +146,6 @@ All notable changes to this project are documented here. The format follows
- **Breaking:** `BlogSettings.autoPublish` and `BlogParams.Settings.autoPublish(...)`. Every
article now goes through `submitArticle` before it publishes.

## [0.3.0] - 2026-09-19

### Added

- `ads()`: the campaign tree and its objects, read live from Meta: `accountTree`, and
create/get/update/delete/duplicate for campaigns, ad sets and network ads, plus `bulkSetStatus`.
The writes need the `publish` scope as well as `ads`.
- `ads()`: `creatives`, `createCreative` (image, video or carousel), `creative`, `deleteCreative`;
`audience`, `updateAudience`, `deleteAudience`, `addAudienceUsers`; `estimateReach`; `insights`
and `adInsights` with an optional breakdown and daily timeline.
- `ads()`: `leadForm`, `archiveLeadForm`, the cursor-paged `leadsFeed`, and `leadPages`,
`subscribeLeadPage`, `unsubscribeLeadPage`.
- `CreateAdParams.urlTags` and `Ad.Creative.urlTags`.

- `inbox()`: `like`, `unlike`, `pin`, `unpin`, `react`, `editComment`, `startConversation`
(`StartConversationParams`), `setTyping`, and `reply` with `InboxReplyParams` for media and quick
replies. All of them also need the `publish` scope, as does deleting our own reply.
- `InboxItem` gains `liked`, `pinned`, `reaction`, `editedAt` and the `canLike`, `canPin`,
`canEdit`, `canReact`, `canSendMedia`, `canQuickReply` and `canPrivateReply` flags;
`InboxAccount` gains `canStartConversation`.
- `validate` resource (`client.validate()`): `post`, `length` and `media` check a draft, a text
length, or a media url against the platform rules without creating a post. Needs the `posts`
scope.

## [0.2.0] - 2026-09-19

### Added
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ long failed = client.posts().stream(PostListParams.create().workspaceId(workspac
| `ads()` | `list`, `external`, `boostable`, `connections`, `sources`, `providers`, `authorize`, `deleteConnection`, `boost`, `create`, `refresh`, `setStatus`, `delete`, `accountTree`, `createCampaign`, `campaign`, `updateCampaign`, `deleteCampaign`, `duplicateCampaign`, `createAdSet`, `adSet`, `updateAdSet`, `deleteAdSet`, `duplicateAdSet`, `createNetworkAd`, `networkAd`, `updateNetworkAd`, `deleteNetworkAd`, `duplicateNetworkAd`, `bulkSetStatus`, `creatives`, `createCreative`, `creative`, `deleteCreative`, `estimateReach`, `insights`, `adInsights`, `audiences`, `createAudience`, `audience`, `updateAudience`, `deleteAudience`, `addAudienceUsers`, `addAudienceCompanies`, `searchTargeting`, `leadForms`, `createLeadForm`, `leadForm`, `archiveLeadForm`, `leads`, `leadsFeed`, `leadPages`, `subscribeLeadPage`, `unsubscribeLeadPage`, `bidPricing`, `supplyForecast`, `conversionRules`, `createConversionRule`, `conversionRule`, `updateConversionRule`, `deleteConversionRule`, `attachConversionRule`, `detachConversionRule`, `conversionMetrics`, `sendConversionEvents` |
| `googleAds()` | `recommendations`, `optimizationScore`, `applyRecommendations`, `dismissRecommendations`, `keywords`, `createKeyword`, `updateKeyword`, `deleteKeyword`, `keywordIdeas`, `keywordMetrics`, `searchTerms`, `bidStrategies`, `createBidStrategy`, `adSchedule`, `setAdSchedule`, `negativeKeywordLists`, `createNegativeKeywordList`, `addNegativeKeywords`, `attachNegativeKeywordList`, `assets`, `createAsset`, `attachAsset`, `deleteAsset`, `assetGroups`, `createAssetGroup`, `updateAssetGroup`, `deleteAssetGroup`, `localServicesLeads`, `conversionActions`, `createConversionAction`, `uploadConversions`, `uploadConversionAdjustments`, `query` |
| `googleBusiness()` | `getLocation`, `updateLocation`, `getAttributes`, `updateAttributes`, `getMenus`, `replaceMenus`, `getServices`, `replaceServices`, `listMedia`, `addMedia`, `deleteMedia`, `listPlaceActions`, `createPlaceAction`, `updatePlaceAction`, `deletePlaceAction`, `getVerificationOptions`, `startVerification`, `completeVerification`, `getPerformance`, `getSearchKeywords`, `assign` |
| `blogs()` | `listBlogs`, `listArticles`, `getArticle`, `createArticle`, `updateArticle`, `deleteArticle`, `listProducts`, `updateProduct` |
| `validate()` | `post`, `length`, `media`, `subreddit` |
| `activity()` | `list` |

Expand Down
8 changes: 8 additions & 0 deletions src/main/java/com/fopost/sdk/FoPost.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import com.fopost.sdk.resource.BlogResource;
import com.fopost.sdk.resource.BroadcastsResource;
import com.fopost.sdk.resource.ContactsResource;
import com.fopost.sdk.resource.BlogsResource;
import com.fopost.sdk.resource.InboxResource;
import com.fopost.sdk.resource.KnowledgeResource;
import com.fopost.sdk.resource.GoogleAdsResource;
Expand Down Expand Up @@ -86,6 +87,7 @@ public final class FoPost {
private final AdsResource ads;
private final GoogleAdsResource googleAds;
private final BlogResource blog;
private final BlogsResource blogs;
private final ValidateResource validate;

private FoPost(ApiClient http) {
Expand All @@ -111,6 +113,7 @@ private FoPost(ApiClient http) {
this.ads = new AdsResource(http);
this.googleAds = new GoogleAdsResource(http);
this.blog = new BlogResource(http);
this.blogs = new BlogsResource(http);
this.validate = new ValidateResource(http);
}

Expand Down Expand Up @@ -186,6 +189,11 @@ public GoogleBusinessResource googleBusiness() {
return googleBusiness;
}

/** Articles and products that already live on a connected site. */
public BlogsResource blogs() {
return blogs;
}

public InboxResource inbox() {
return inbox;
}
Expand Down
25 changes: 25 additions & 0 deletions src/main/java/com/fopost/sdk/model/RemoteArticle.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.fopost.sdk.model;

import java.time.Instant;
import java.util.List;

/**
* An article that already lives on a connected site, addressed by the
* platform's own id.
*
* <p>{@code status} is one of {@code published}, {@code draft}, {@code pending}
* or {@code scheduled}.
*/
public record RemoteArticle(
String id,
String blogId,
String title,
String bodyHtml,
String excerpt,
String status,
String authorName,
List<String> tags,
String imageUrl,
String url,
Instant publishedAt,
Instant updatedAt) {}
10 changes: 10 additions & 0 deletions src/main/java/com/fopost/sdk/model/RemoteBlog.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.fopost.sdk.model;

/**
* A blog on a connected site. {@code id} is the platform's own id, never a
* FoPost id.
*
* <p>A Shopify store reports every blog it has; WordPress has one implicit blog
* and reports it under the id {@code default}, so both answer the same shape.
*/
public record RemoteBlog(String id, String title, String handle, String url) {}
24 changes: 24 additions & 0 deletions src/main/java/com/fopost/sdk/model/RemoteProduct.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.fopost.sdk.model;

import java.time.Instant;
import java.util.List;

/**
* A product on a connected store. {@code price} is the lowest variant price, as
* a decimal string; {@code status} is {@code active}, {@code draft} or
* {@code archived}.
*/
public record RemoteProduct(
String id,
String title,
String handle,
String status,
String description,
String vendor,
String productType,
List<String> tags,
String imageUrl,
String url,
String price,
String currency,
Instant updatedAt) {}
36 changes: 36 additions & 0 deletions src/main/java/com/fopost/sdk/param/ArticleListParams.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.fopost.sdk.param;

import java.util.LinkedHashMap;
import java.util.Map;

/** Filters for listing a blog's articles. Every field is optional. */
public final class ArticleListParams {

private final Map<String, Object> query = new LinkedHashMap<>();

public static ArticleListParams create() {
return new ArticleListParams();
}

/** How many to return, 1 to 50. The API defaults to 20. */
public ArticleListParams limit(int limit) {
query.put("limit", limit);
return this;
}

/** {@code published}, {@code draft}, {@code pending} or {@code scheduled}. */
public ArticleListParams status(String status) {
query.put("status", status);
return this;
}

/** Matches the article title. */
public ArticleListParams q(String q) {
query.put("q", q);
return this;
}

public Map<String, Object> toQuery() {
return new LinkedHashMap<>(query);
}
}
63 changes: 63 additions & 0 deletions src/main/java/com/fopost/sdk/param/ArticleParams.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.fopost.sdk.param;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
* The body of a create or update on an article.
*
* <p>Only what is set here travels, so on an update an omitted field keeps
* whatever the site already had. A create needs at least {@code title} and
* {@code body}; an update needs at least one field.
*/
public final class ArticleParams {

private final Map<String, Object> body = new LinkedHashMap<>();

public static ArticleParams create() {
return new ArticleParams();
}

public ArticleParams title(String title) {
body.put("title", title);
return this;
}

/** FoPost body markup; the site's own format is rendered from it. */
public ArticleParams body(String value) {
body.put("body", value);
return this;
}

public ArticleParams excerpt(String excerpt) {
body.put("excerpt", excerpt);
return this;
}

/** {@code published}, {@code draft}, {@code pending} or {@code scheduled}. */
public ArticleParams status(String status) {
body.put("status", status);
return this;
}

public ArticleParams tags(List<String> tags) {
body.put("tags", tags);
return this;
}

public ArticleParams authorName(String authorName) {
body.put("author_name", authorName);
return this;
}

/** Public http(s) URL of the featured image. */
public ArticleParams imageUrl(String imageUrl) {
body.put("image_url", imageUrl);
return this;
}

public Map<String, Object> toBody() {
return new LinkedHashMap<>(body);
}
}
36 changes: 36 additions & 0 deletions src/main/java/com/fopost/sdk/param/ProductListParams.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.fopost.sdk.param;

import java.util.LinkedHashMap;
import java.util.Map;

/** Filters for listing a store's products. Every field is optional. */
public final class ProductListParams {

private final Map<String, Object> query = new LinkedHashMap<>();

public static ProductListParams create() {
return new ProductListParams();
}

/** How many to return, 1 to 50. The API defaults to 20. */
public ProductListParams limit(int limit) {
query.put("limit", limit);
return this;
}

/** {@code active}, {@code draft} or {@code archived}. */
public ProductListParams status(String status) {
query.put("status", status);
return this;
}

/** Matches the product title. */
public ProductListParams q(String q) {
query.put("q", q);
return this;
}

public Map<String, Object> toQuery() {
return new LinkedHashMap<>(query);
}
}
54 changes: 54 additions & 0 deletions src/main/java/com/fopost/sdk/param/ProductParams.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.fopost.sdk.param;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
* The body of an update on a store product. Only what is set travels, so an
* omitted field keeps whatever the store already had. Set at least one.
*/
public final class ProductParams {

private final Map<String, Object> body = new LinkedHashMap<>();

public static ProductParams create() {
return new ProductParams();
}

public ProductParams title(String title) {
body.put("title", title);
return this;
}

/** Body markup, rendered to HTML on the store. */
public ProductParams description(String description) {
body.put("description", description);
return this;
}

/** {@code active}, {@code draft} or {@code archived}. */
public ProductParams status(String status) {
body.put("status", status);
return this;
}

public ProductParams tags(List<String> tags) {
body.put("tags", tags);
return this;
}

public ProductParams productType(String productType) {
body.put("product_type", productType);
return this;
}

public ProductParams vendor(String vendor) {
body.put("vendor", vendor);
return this;
}

public Map<String, Object> toBody() {
return new LinkedHashMap<>(body);
}
}
Loading
Loading