Java SDK
ai.syntermedia:synter-sdk on Maven Central. A typed Java 17+ client for POST /api/v1/tools/run, built on java.net.http. Use version 0.1.1 or later.
Install
<dependency>
<groupId>ai.syntermedia</groupId>
<artifactId>synter-sdk</artifactId>
<version>0.1.1</version>
</dependency>If a search says the artifact does not exist
Authenticate
Pass the key to the builder. The SDK sends it as Authorization: Bearer syn_… on every request. Read it from the environment; never hardcode it.
import ai.syntermedia.sdk.Synter;
Synter synter = Synter.builder()
.apiKey(System.getenv("SYNTER_API_KEY"))
.build();| Surface | Accepted | Use |
|---|---|---|
| REST tools endpoint | Authorization: Bearer syn_…, X-Synter-Key: syn_…, X-API-Key: syn_… | Authorization: Bearer syn_… |
| REST resource endpoints | Authorization: Bearer syn_…, X-Synter-Key: syn_… | Authorization: Bearer syn_… |
| SDKs (Python, TypeScript, Rust, Java, Go) and the synter CLI | The SDK sets the header for you | Pass the key to the client. Every SDK sends Authorization: Bearer syn_… |
| Local stdio MCP (npm @synterai/mcp-server) | Set SYNTER_API_KEY in the server's env block | The package sends Authorization: Bearer syn_… |
| Hosted MCP | Browser OAuth (the client sends Authorization: Bearer <OAuth access token>), or X-Synter-Key: syn_… for headless clients | OAuth. Headless: X-Synter-Key. Do not put an API key in Authorization on this host. |
Keys start with syn_ (sandbox keys with syn_test_). Create one at synterai.com/developer, keep it in SYNTER_API_KEY or a secret store, and send one header per request. On the hosted MCP endpoint, the Authorization header is reserved for the OAuth token the client gets from browser sign-in, so a failed key there shows up as a sign-in prompt rather than a key error. Use X-Synter-Key when you connect a hosted MCP client with a key. Details: Authentication.
Read: last 7 days of Google Ads performance
import ai.syntermedia.sdk.Synter;
import ai.syntermedia.sdk.SynterException;
import ai.syntermedia.sdk.requests.GetPerformanceRequest;
import ai.syntermedia.sdk.requests.ListCampaignsRequest;
import java.util.Map;
public class PerformanceReport {
public static void main(String[] args) {
Synter synter = Synter.builder()
.apiKey(System.getenv("SYNTER_API_KEY"))
.build();
try {
Map<String, Object> campaigns = synter.campaigns().list(
ListCampaignsRequest.builder()
.platform("google")
.status("ENABLED")
.build());
Map<String, Object> performance = synter.analytics().getPerformance(
GetPerformanceRequest.builder()
.platform("google")
.dateRange("LAST_7_DAYS")
.build());
System.out.println(performance.keySet());
} catch (SynterException error) {
System.err.println("Request failed (status " + error.getStatus()
+ ", code " + error.getCode() + ")");
}
}
}getPerformance sends the REST script pull_google_ads with --days 7. pull_google_ads_performance is the hosted MCP tool name, not a REST script name.
Write: pause a campaign and change its daily budget
Both calls change a live Google Ads campaign. In 0.1.1 the platform argument is required but the request always goes to Google Ads. Use the REST endpoints below for other platforms.
import java.util.Map;
String campaignId = "1234567890";
// Pause (Google Ads)
Map<String, Object> paused = synter.campaigns().pause(campaignId, "google");
// Change the daily budget to 75.00 in the account currency.
// 0.1.1's campaigns().updateBudget(...) sends --budget, which the API rejects
// with 400 UNRECOGNIZED_ARGS before anything runs. Use execute() until the
// next release:
Map<String, Object> budget = synter.execute(
"update_campaign_budget",
Map.of("campaign_id", campaignId, "daily_budget", 75.0),
"google");
if ("pending_review".equals(budget.get("status"))) {
// Held by the safety gate: nothing ran. See "Writes the safety gate holds".
System.out.println("Waiting for approval: " + budget.get("audit_id"));
}execute(scriptName, args, platform) turns each map key into a flag (daily_budget becomes --daily-budget). 2xx responses, including 202, come back as a Map; anything else throws SynterException.
Writes the safety gate holds or refuses
With the default workspace policy, pausing a campaign and changing a budget run straight away and are recorded in the audit log. Enabling or resuming a campaign, deleting or removing anything, sending creative to a platform, and creating new campaigns can be held for approval. A held write returns HTTP 202 with status: "pending_review", an audit_id and the exact reviewed_args. Nothing has run at that point. The SDKs return this body as a normal result, so check status before you report success.
A write the policy refuses (blocked tool, outside operating hours, over a monetary limit) returns HTTP 403 with status: "blocked", and the SDKs raise their error type. Approval cannot override a block.
An API key cannot approve its own pending write. See Approval model and safety controls for who approves what and where.
Errors
| HTTP | Meaning | What to do |
|---|---|---|
| 400 | Invalid arguments, for example UNRECOGNIZED_ARGS | Fix the arguments. Rejected before submission; nothing is charged. |
| 401 | Missing or invalid key | Check SYNTER_API_KEY |
| 402 | Not enough credit, or a payment method is needed for a launch | Read the response body; check billing. Do not retry blindly. |
| 403 | Missing scope, workspace access, or a safety-policy block | Check the key's scopes and the status field |
| 429 | Rate limited | The SDK retries up to three times and honors Retry-After |
