Docs / 개발자
이 문서는 영어로 표시됩니다.
REST API
Use tenant API keys from your CMS or backend. Dashboard session cookies are for the EngageNudge app only. Call the API from your server, never put the key in the browser.
| Base URL | https://api.engagenudge.com |
| Auth | Authorization: Bearer en_… |
| Content type | application/json |
Create an API key
- Dashboard → Integrations → REST API
- As a tenant owner, create a key and copy the secret once (
en_…) - Store it in your secret manager. It is never shown again.
Key management (dashboard session / owner only):
| Method | Path |
|---|---|
GET | /v1/tenants/:tenantId/api-keys |
POST | /v1/tenants/:tenantId/api-keys body { "name": "CMS production" } |
DELETE | /v1/tenants/:tenantId/api-keys/:keyId |
Auth
Authorization: Bearer en_YOUR_API_KEYThe key’s tenant must match the campaign tenant. Do not log the raw key. You also need tenant id and site id (UUIDs) from the install snippet.
Recommended flow
(optional) POST /v1/content/preview
POST /v1/tenants/:tenantId/campaigns → campaignId (draft)
POST /v1/campaigns/:campaignId/test → test devices only
or POST /v1/campaigns/:campaignId/send → live audience
or POST /v1/campaigns/:campaignId/schedule → scheduleAt (ISO-8601)Live send requires DPA accepted, sends not paused, billing allowing the audience, and a sendable campaign status (usually draft). Test send requires at least one test subscriber.
Preview
curl -X POST "https://api.engagenudge.com/v1/content/preview" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $API_KEY" \
-d '{
"url": "https://publisher.example/story",
"siteId": "'"$SITE_ID"'",
"tenantId": "'"$TENANT_ID"'"
}'url is required (HTTPS, SSRF-safe fetch). siteId and tenantId make the decision audience-aware.
Create campaign
curl -X POST "https://api.engagenudge.com/v1/tenants/$TENANT_ID/campaigns" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $API_KEY" \
-d '{
"siteId": "'"$SITE_ID"'",
"url": "https://publisher.example/story",
"imageUrl": "https://publisher.example/image.jpg"
}'| Field | Required | Notes |
|---|---|---|
siteId | yes | UUID on the tenant |
url | yes | Destination / article URL |
mode | no | "url" or "manual" |
title / body | manual | Max 120 / 240 chars |
imageUrl | no | HTTPS URL, or "" to clear |
topicIds | no | Topic UUID filter |
forceSend | no | URL mode: create a sendable draft even when the decision is skip. RSS auto-push sets it only for a feed with the override turned on. |
Without forceSend, a rejected story may land as cancelled. Dashboard compose sends forceSend: true so editors can ship.
The JSON response includes campaignId. Use that id for test, send, or schedule.
Examples
Create a campaign, then send it. Same path and headers in every language. Read API_KEY, TENANT_ID, and SITE_ID from your environment. Do not hard-code the secret.
JavaScript
const API_URL = "https://api.engagenudge.com";
const API_KEY = process.env.ENGAGENUDGE_API_KEY;
const TENANT_ID = process.env.ENGAGENUDGE_TENANT_ID;
const SITE_ID = process.env.ENGAGENUDGE_SITE_ID;
const created = await fetch(`${API_URL}/v1/tenants/${TENANT_ID}/campaigns`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
siteId: SITE_ID,
url: "https://publisher.example/story",
imageUrl: "https://publisher.example/image.jpg",
}),
});
if (!created.ok) {
throw new Error(`Create failed: ${created.status} ${await created.text()}`);
}
const { campaignId } = await created.json();
const sent = await fetch(`${API_URL}/v1/campaigns/${campaignId}/send`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Idempotency-Key": crypto.randomUUID(),
},
});
if (!sent.ok) {
throw new Error(`Send failed: ${sent.status} ${await sent.text()}`);
}TypeScript
const API_URL = "https://api.engagenudge.com";
type CampaignCreated = { campaignId: string };
async function createAndSendNudge(input: {
apiKey: string;
tenantId: string;
siteId: string;
url: string;
imageUrl?: string;
}): Promise<CampaignCreated> {
const created = await fetch(`${API_URL}/v1/tenants/${input.tenantId}/campaigns`, {
method: "POST",
headers: {
Authorization: `Bearer ${input.apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
siteId: input.siteId,
url: input.url,
imageUrl: input.imageUrl,
}),
});
if (!created.ok) {
throw new Error(`Create failed: ${created.status} ${await created.text()}`);
}
const campaign = (await created.json()) as CampaignCreated;
const sent = await fetch(`${API_URL}/v1/campaigns/${campaign.campaignId}/send`, {
method: "POST",
headers: {
Authorization: `Bearer ${input.apiKey}`,
"Idempotency-Key": crypto.randomUUID(),
},
});
if (!sent.ok) {
throw new Error(`Send failed: ${sent.status} ${await sent.text()}`);
}
return campaign;
}Python
import json
import os
import uuid
import urllib.error
import urllib.request
API_URL = "https://api.engagenudge.com"
API_KEY = os.environ["ENGAGENUDGE_API_KEY"]
TENANT_ID = os.environ["ENGAGENUDGE_TENANT_ID"]
SITE_ID = os.environ["ENGAGENUDGE_SITE_ID"]
def request(method: str, path: str, payload: dict | None = None, extra_headers: dict | None = None) -> dict:
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
**(extra_headers or {}),
}
data = None if payload is None else json.dumps(payload).encode()
req = urllib.request.Request(f"{API_URL}{path}", data=data, headers=headers, method=method)
try:
with urllib.request.urlopen(req) as res:
body = res.read().decode()
return json.loads(body) if body else {}
except urllib.error.HTTPError as exc:
raise RuntimeError(f"{method} {path} failed: {exc.code} {exc.read().decode()}") from exc
campaign = request(
"POST",
f"/v1/tenants/{TENANT_ID}/campaigns",
{
"siteId": SITE_ID,
"url": "https://publisher.example/story",
"imageUrl": "https://publisher.example/image.jpg",
},
)
request(
"POST",
f"/v1/campaigns/{campaign['campaignId']}/send",
extra_headers={"Idempotency-Key": str(uuid.uuid4())},
)PHP
<?php
$apiUrl = 'https://api.engagenudge.com';
$apiKey = getenv('ENGAGENUDGE_API_KEY');
$tenantId = getenv('ENGAGENUDGE_TENANT_ID');
$siteId = getenv('ENGAGENUDGE_SITE_ID');
function en_request(string $method, string $url, string $apiKey, ?array $payload = null, array $extraHeaders = []): array {
$headers = array_merge([
"Authorization: Bearer $apiKey",
'Content-Type: application/json',
], $extraHeaders);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => $payload === null ? null : json_encode($payload),
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status < 200 || $status >= 300) {
throw new RuntimeException("$method $url failed: $status $body");
}
return $body ? json_decode($body, true) : [];
}
$campaign = en_request('POST', "$apiUrl/v1/tenants/$tenantId/campaigns", $apiKey, [
'siteId' => $siteId,
'url' => 'https://publisher.example/story',
'imageUrl' => 'https://publisher.example/image.jpg',
]);
en_request(
'POST',
"$apiUrl/v1/campaigns/{$campaign['campaignId']}/send",
$apiKey,
null,
['Idempotency-Key: ' . bin2hex(random_bytes(16))]
);Java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.UUID;
static String jsonField(String json, String field) {
String key = "\"" + field + "\"";
int at = json.indexOf(key);
if (at < 0) throw new IllegalStateException(field + " missing: " + json);
int colon = json.indexOf(':', at + key.length());
int start = json.indexOf('"', colon + 1) + 1;
return json.substring(start, json.indexOf('"', start));
}
String apiUrl = "https://api.engagenudge.com";
String apiKey = System.getenv("ENGAGENUDGE_API_KEY");
String tenantId = System.getenv("ENGAGENUDGE_TENANT_ID");
String siteId = System.getenv("ENGAGENUDGE_SITE_ID");
HttpClient client = HttpClient.newHttpClient();
String createJson = """
{
"siteId": "%s",
"url": "https://publisher.example/story",
"imageUrl": "https://publisher.example/image.jpg"
}
""".formatted(siteId);
HttpResponse<String> created = client.send(
HttpRequest.newBuilder()
.uri(URI.create(apiUrl + "/v1/tenants/" + tenantId + "/campaigns"))
.header("Authorization", "Bearer " + apiKey)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(createJson))
.build(),
HttpResponse.BodyHandlers.ofString());
if (created.statusCode() < 200 || created.statusCode() >= 300) {
throw new IllegalStateException("Create failed: " + created.statusCode() + " " + created.body());
}
String campaignId = jsonField(created.body(), "campaignId");
HttpResponse<String> sent = client.send(
HttpRequest.newBuilder()
.uri(URI.create(apiUrl + "/v1/campaigns/" + campaignId + "/send"))
.header("Authorization", "Bearer " + apiKey)
.header("Idempotency-Key", UUID.randomUUID().toString())
.POST(HttpRequest.BodyPublishers.noBody())
.build(),
HttpResponse.BodyHandlers.ofString());
if (sent.statusCode() < 200 || sent.statusCode() >= 300) {
throw new IllegalStateException("Send failed: " + sent.statusCode() + " " + sent.body());
}C# (.NET)
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
const string apiUrl = "https://api.engagenudge.com";
var apiKey = Environment.GetEnvironmentVariable("ENGAGENUDGE_API_KEY");
var tenantId = Environment.GetEnvironmentVariable("ENGAGENUDGE_TENANT_ID");
var siteId = Environment.GetEnvironmentVariable("ENGAGENUDGE_SITE_ID");
using var client = new HttpClient { BaseAddress = new Uri(apiUrl) };
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
var created = await client.PostAsJsonAsync(
$"/v1/tenants/{tenantId}/campaigns",
new
{
siteId,
url = "https://publisher.example/story",
imageUrl = "https://publisher.example/image.jpg",
});
created.EnsureSuccessStatusCode();
using var createdJson = JsonDocument.Parse(await created.Content.ReadAsStringAsync());
var campaignId = createdJson.RootElement.GetProperty("campaignId").GetString();
using var sendRequest = new HttpRequestMessage(HttpMethod.Post, $"/v1/campaigns/{campaignId}/send");
sendRequest.Headers.TryAddWithoutValidation("Idempotency-Key", Guid.NewGuid().ToString());
var sent = await client.SendAsync(sendRequest);
sent.EnsureSuccessStatusCode();Test, send, schedule
curl -X POST "https://api.engagenudge.com/v1/campaigns/$CAMPAIGN_ID/test" \
-H "Authorization: Bearer $API_KEY"
curl -X POST "https://api.engagenudge.com/v1/campaigns/$CAMPAIGN_ID/send" \
-H "Authorization: Bearer $API_KEY" \
-H "Idempotency-Key: optional-unique-key"
curl -X POST "https://api.engagenudge.com/v1/campaigns/$CAMPAIGN_ID/schedule" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $API_KEY" \
-d '{ "scheduleAt": "2026-08-10T09:00:00.000Z" }'scheduleAt must be ISO-8601 in the future. Idempotent send replay returns "duplicate": true.
Common errors
| Status | Code | Meaning |
|---|---|---|
| 401 | - | Missing or invalid API key |
| 403 | dpa_required | Accept the DPA in Settings |
| 402 | billing | Plan or audience cap blocked |
| 409 | illegal_transition | Campaign not sendable |
| 423 | send_paused | Site or tenant pause is on |
Stop an in-flight run from the dashboard or POST /v1/runs/:runId/stop.
Not in this API yet
- Published OpenAPI
- Outbound webhooks
- Live CMS HMAC ingest (the dashboard shows the intended payload only)
- Subscriber CRUD beyond the send path