Files
plunk/apps/wiki/content/docs/api-reference/overview.mdx
T

462 lines
27 KiB
Plaintext

---
title: API Reference
description: Complete Plunk API documentation
---
## Base URL
```
{{API_URL}}
```
All API requests use this base URL.
## Authentication
Include your API key in the `Authorization` header:
```bash
Authorization: Bearer YOUR_API_KEY
```
- **Secret Key (sk_*)** — Required for all endpoints except `/v1/track`
- **Public Key (pk_*)** — Only works with `/v1/track` for client-side event tracking
## Making requests
### Send transactional email
```bash
curl -X POST {{API_URL}}/v1/send \
-H "Authorization: Bearer sk_your_secret_key" \
-H "Content-Type: application/json" \
-d '{
"to": "user@example.com",
"subject": "Hello",
"body": "<p>Your message here</p>"
}'
```
### Track event
```bash
curl -X POST {{API_URL}}/v1/track \
-H "Authorization: Bearer pk_your_public_key" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"event": "signed_up"
}'
```
### Create contact
```bash
curl -X POST {{API_URL}}/contacts \
-H "Authorization: Bearer sk_your_secret_key" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"subscribed": true,
"data": {
"firstName": "John",
"plan": "pro"
}
}'
```
## Response format
### Success responses
Public API endpoints (`/v1/send`, `/v1/track`, `/v1/verify`) return a wrapped envelope:
```json
{
"success": true,
"data": {
"contact": "cnt_abc123",
"event": "evt_xyz789",
"timestamp": "2025-11-30T10:30:00.000Z"
}
}
```
Dashboard endpoints (contacts, templates, campaigns, segments, workflows, etc.) return the resource directly — no `success`/`data` wrapper:
```json
{
"id": "cnt_abc123",
"email": "user@example.com",
"createdAt": "2025-11-30T10:30:00.000Z"
}
```
List endpoints with cursor pagination return:
```json
{
"data": [ /* items */ ],
"cursor": "def456",
"hasMore": true,
"total": 10000
}
```
### Error response
All errors include detailed information to help you debug issues:
```json
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"statusCode": 422,
"requestId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"errors": [
{
"field": "email",
"message": "Invalid email",
"code": "invalid_string"
}
],
"suggestion": "One or more fields have incorrect types. Check that strings are quoted, numbers are unquoted, and booleans are true/false."
},
"timestamp": "2025-11-30T10:30:00.000Z"
}
```
**Error fields:**
- `code` — Machine-readable error code for programmatic handling
- `message` — Human-readable description
- `statusCode` — HTTP status code
- `requestId` — Unique ID for debugging (include when contacting support)
- `errors` — Field-level validation details (when applicable)
- `suggestion` — Helpful guidance for fixing the error
See the [Error Codes documentation](/api-reference/errors) for complete details and examples.
## Pagination
Most list endpoints use **cursor-based** pagination:
```bash
GET /contacts?limit=100&cursor=abc123
```
**Parameters:**
- `limit` — items per page (default: 20, max: 100)
- `cursor` — pagination cursor from the previous response's `cursor` field
**Response:**
```json
{
"data": [ /* items */ ],
"cursor": "def456",
"hasMore": true,
"total": 10000
}
```
Pass the response's `cursor` value as the next request's `cursor` query parameter. When `hasMore` is `false`, you've reached the end. The `total` count is only included on the first page (when no `cursor` is supplied) — subsequent pages return `total: 0` to keep listing fast.
A few endpoints (e.g. `GET /segments/:id/contacts`) use **page-based** pagination instead, with `page` and `pageSize` parameters. Their responses include `total`, `page`, and `pageSize` fields.
## Rate limits
Plunk enforces reasonable rate limits to ensure service quality:
- **Email sending** — throttled per project to protect deliverability.
- **API requests** — 1000 requests/minute per project.
- **Bulk operations** — automatically queued for asynchronous processing.
If you exceed limits, you'll receive a `429 Too Many Requests` response.
## Error codes
The API uses standard HTTP status codes along with machine-readable error codes:
**400 Bad Request** — Invalid request parameters or malformed request body
**401 Unauthorized** — Missing or invalid API key
**403 Forbidden** — Not authorized to access this resource or project disabled
**404 Not Found** — Resource doesn't exist
**422 Unprocessable Entity** — Request validation failed (see `errors` array for details)
**429 Too Many Requests** — Rate limit exceeded
**500 Internal Server Error** — An unexpected error occurred (contact support with request ID)
For a complete list of error codes and troubleshooting guidance, see the [Error Codes documentation](/api-reference/errors).
## API endpoints
A complete, grouped reference of every endpoint exposed by the Plunk API.
### Public API
The `/v1/*` endpoints are designed for use from your applications and accept simple, denormalised payloads. `POST /v1/track` is the only endpoint callable with a public (`pk_*`) key.
| Method | Path | Description | Key |
| ------ | ------------ | -------------------------------------------------------------------------------------------- | -------- |
| POST | `/v1/send` | Send a transactional email. Single or multiple recipients, template or inline content, attachments, headers, custom data. | `sk_*` |
| POST | `/v1/track` | Track an event for a contact. Auto-creates or upserts the contact. Triggers workflows. | `pk_*` or `sk_*` |
| POST | `/v1/verify` | Validate an email address — format, MX records, disposable domains, typo detection. | `sk_*` |
### Contacts
| Method | Path | Description |
| ------ | ------------------------------------- | -------------------------------------------------------------------------------------------- |
| GET | `/contacts` | List contacts. Supports `search` (by email substring), `limit`, `cursor`. |
| POST | `/contacts` | Create or upsert a contact by email. Returns `_meta.isNew` and `_meta.isUpdate`. |
| GET | `/contacts/:id` | Get a single contact. |
| PATCH | `/contacts/:id` | Update a contact's email, subscription state, or `data` fields. |
| DELETE | `/contacts/:id` | Delete a contact. |
| POST | `/contacts/lookup` | Bulk email-existence check (max 500 emails per call). |
| **Custom fields** | | |
| GET | `/contacts/fields` | List standard and custom fields with inferred types and coverage percentages. |
| GET | `/contacts/fields/:field/values` | Distinct values for a custom field — used by segment / workflow filter UIs. |
| GET | `/contacts/fields/:field/usage` | Where a custom field is referenced (segments, campaigns, workflows). |
| DELETE | `/contacts/fields/:field` | Delete a custom field across every contact in the project. |
| **CSV import** | | |
| POST | `/contacts/import` | Upload a CSV file (multipart, ≤ 5 MB). Queued — returns a `jobId`. |
| GET | `/contacts/import/:jobId` | Poll the status of a CSV import job. |
| **Bulk operations** | | |
| POST | `/contacts/bulk-subscribe` | Subscribe up to 1,000 contacts by ID. Queued — returns a `jobId`. |
| POST | `/contacts/bulk-unsubscribe` | Unsubscribe up to 1,000 contacts by ID. Queued. |
| POST | `/contacts/bulk-delete` | Delete up to 1,000 contacts by ID. Queued. |
| GET | `/contacts/bulk/:jobId` | Poll the status of a bulk job. |
### Templates
| Method | Path | Description |
| ------ | ----------------------------- | -------------------------------------------------------------------------------------------- |
| GET | `/templates` | List all templates. |
| POST | `/templates` | Create a template. `from` must be on a verified domain. |
| GET | `/templates/:id` | Get a template. |
| PATCH | `/templates/:id` | Update a template. |
| DELETE | `/templates/:id` | Delete a template. |
| POST | `/templates/:id/duplicate` | Duplicate a template — returns the new template ID. |
| GET | `/templates/:id/usage` | List campaigns and workflow steps that reference this template. |
### Campaigns
| Method | Path | Description |
| ------ | ----------------------------- | -------------------------------------------------------------------------------------------- |
| GET | `/campaigns` | List all campaigns. |
| POST | `/campaigns` | Create a campaign in `DRAFT`. `from` must be on a verified domain. |
| GET | `/campaigns/:id` | Get a campaign. |
| PUT | `/campaigns/:id` | Update a campaign (replace). |
| DELETE | `/campaigns/:id` | Delete a campaign. Returns 409 if it has active executions. |
| POST | `/campaigns/:id/duplicate` | Duplicate a campaign — returns the new campaign in `DRAFT`. |
| POST | `/campaigns/:id/send` | Send (or schedule) the campaign. Pass `scheduledFor` for delayed sends. |
| POST | `/campaigns/:id/cancel` | Cancel a `SCHEDULED` or `SENDING` campaign. |
| POST | `/campaigns/:id/test` | Send a test email to a single address (`{ email: "you@example.com" }`). |
| GET | `/campaigns/:id/stats` | Get current send / open / click / bounce counts. |
### Segments
| Method | Path | Description |
| ------ | ----------------------------- | -------------------------------------------------------------------------------------------- |
| GET | `/segments` | List all segments (no pagination — small list). |
| POST | `/segments` | Create a segment. `type: "DYNAMIC"` requires `condition`; `type: "STATIC"` rejects it. |
| GET | `/segments/:id` | Get a segment, including cached `memberCount`. |
| PATCH | `/segments/:id` | Update name, description, condition (dynamic only), or `trackMembership`. |
| DELETE | `/segments/:id` | Delete a segment. Returns 409 if used by an active campaign. |
| GET | `/segments/:id/contacts` | Page-based list of segment members. `page`, `pageSize` (max 100). Live for dynamic segments. |
| POST | `/segments/:id/members` | Add emails to a **static** segment. Body: `{ emails, createMissing?, subscribed? }`. |
| DELETE | `/segments/:id/members` | Remove emails from a **static** segment. Body: `{ emails }`. |
| POST | `/segments/:id/compute` | Recompute membership for a tracked dynamic segment — fires entry/exit events. |
| POST | `/segments/:id/refresh` | Cheap count refresh — no events, no membership writes. |
### Workflows
Each workflow consists of a **workflow** record, a graph of **steps**, **transitions** between them, and per-contact **executions**. The API mirrors that structure.
| Method | Path | Description |
| ------ | ------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| GET | `/workflows` | List all workflows. |
| GET | `/workflows/fields` | Fields available to use in `CONDITION` step filters (contact + event fields). |
| POST | `/workflows` | Create a workflow. Always starts with `triggerType: EVENT` and `enabled: false`. |
| GET | `/workflows/:id` | Get a workflow with its steps and transitions. |
| PATCH | `/workflows/:id` | Update workflow metadata, trigger type / config, `enabled`, `allowReentry`. |
| DELETE | `/workflows/:id` | Delete a workflow. Active executions must be cancelled or completed first. |
| **Steps** | | |
| POST | `/workflows/:id/steps` | Add a step (`SEND_EMAIL`, `DELAY`, `WAIT_FOR_EVENT`, `CONDITION`, `WEBHOOK`, `UPDATE_CONTACT`, `EXIT`). |
| PATCH | `/workflows/:id/steps/:stepId` | Update a step's config. |
| DELETE | `/workflows/:id/steps/:stepId?splice=true` | Delete a step. Pass `splice=true` to auto-reconnect surrounding transitions. |
| **Transitions** | | |
| POST | `/workflows/:id/transitions` | Add a transition between two steps. For `CONDITION` steps, include `branch: "yes" \| "no"`. |
| DELETE | `/workflows/:id/transitions/:transitionId` | Delete a transition. |
| **Executions** | | |
| POST | `/workflows/:id/executions` | Manually start an execution for a contact. Optional `context` JSON for per-execution variables. |
| GET | `/workflows/:id/executions` | List executions, filterable by `status`. |
| GET | `/workflows/:id/executions/:executionId` | Get a single execution. |
| DELETE | `/workflows/:id/executions/:executionId` | Cancel a running or waiting execution. |
| POST | `/workflows/:id/executions/cancel-all` | Cancel every active execution at once. |
### Events
| Method | Path | Description |
| ------ | ----------------------------- | -------------------------------------------------------------------------------------------- |
| POST | `/events/track` | Internal alias for `/v1/track`, for dashboard use. Use `/v1/track` from your apps. |
| GET | `/events` | List recent tracked events for a project. |
| GET | `/events/stats` | Aggregated event statistics. |
| GET | `/events/contact/:contactId` | All events for a single contact. |
| GET | `/events/names` | All distinct event names tracked in this project. |
| GET | `/events/:eventName/usage` | Where an event name is referenced (segment filters, workflow triggers, conditions). |
| DELETE | `/events/:eventName` | Delete every event with the given name from the project. |
### Domains
| Method | Path | Description |
| ------ | ----------------------------- | -------------------------------------------------------------------------------------------- |
| GET | `/domains/project/:projectId` | List domains configured for a project (verified and pending). |
| POST | `/domains` | Add a domain for verification. Returns the DNS records you need to add. |
| GET | `/domains/:id/verify` | Force a verification check now (otherwise checked every 5 minutes in the background). |
| DELETE | `/domains/:id` | Remove a domain. |
### Activity & analytics
| Method | Path | Description |
| ------ | ----------------------------- | -------------------------------------------------------------------------------------------- |
| GET | `/activity` | Cross-resource activity feed (sends, opens, clicks, bounces, complaints, inbound, etc.). |
| GET | `/activity/stats` | Aggregated counts for dashboard charts. |
| GET | `/activity/recent-count` | Recent event count for the dashboard's "live" indicator. |
| GET | `/activity/types` | Distinct activity types in the project. |
| GET | `/activity/upcoming` | Upcoming scheduled sends and active executions. |
| GET | `/analytics/timeseries` | Email send / open / click time-series. |
| GET | `/analytics/top-campaigns` | Top-performing campaigns by metric. |
| GET | `/analytics/campaign-stats` | Campaign-level breakdown. |
| GET | `/analytics/top-events` | Most frequent custom event names. |
### Uploads
| Method | Path | Description |
| ------ | ---------------- | ---------------------------------------------------------------------------------------------------- |
| POST | `/uploads/image` | Upload an image (multipart) for use inside template bodies. Returns a public URL. |
### Authentication & user management
The dashboard authenticates with JWT cookies; these endpoints mostly aren't useful from server-to-server integrations but are documented here for completeness.
| Method | Path | Description |
| ------ | ---------------------------------------------------------- | ------------------------------------------------------------ |
| POST | `/auth/login` | Email + password login. Sets a JWT cookie. |
| POST | `/auth/signup` | Sign up a new user (subject to `DISABLE_SIGNUPS`). |
| GET | `/auth/logout` | Clear the auth cookie. |
| GET | `/auth/oauth-config` | Which OAuth providers are configured. |
| POST | `/auth/verify-email` | Verify an email with a token from an email link. |
| POST | `/auth/request-verification` | Resend the verification email. |
| POST | `/auth/request-password-reset` | Send a password reset email. |
| POST | `/auth/reset-password` | Reset a password with a token. |
| GET | `/users/@me` | Get the current user. |
| GET | `/users/@me/projects` | List the user's projects. |
| POST | `/users/@me/projects` | Create a project. |
| PATCH | `/users/@me/projects/:id` | Update project settings. |
| POST | `/users/@me/projects/:id/regenerate-keys` | Rotate both API keys. |
| POST | `/users/@me/projects/:id/checkout` | Create a Stripe Checkout session. |
| POST | `/users/@me/projects/:id/billing-portal` | Open the Stripe billing portal. |
| GET | `/users/@me/projects/:id/billing-limits` | Read per-category billing caps. |
| PUT | `/users/@me/projects/:id/billing-limits` | Update per-category billing caps. |
| GET | `/users/@me/projects/:id/billing-consumption` | Current period consumption for the project. |
| GET | `/users/@me/projects/:id/billing-invoices` | Stripe invoices for the project. |
| GET | `/users/@me/projects/:id/security` | Security info — bounce/complaint rates, recent suspensions. |
| POST | `/users/@me/projects/:id/reset` | Wipe project data (irreversible). |
| DELETE | `/users/@me/projects/:id` | Delete the project entirely. |
| GET | `/projects/:id/setup-state` | Onboarding setup state. |
| GET | `/projects/:id/security` | Security state for a single project. |
| GET | `/projects/:id/members` | Project team members. |
| POST | `/projects/:id/members` | Invite a team member. |
| PATCH | `/projects/:id/members/:userId` | Change a member's role. |
| DELETE | `/projects/:id/members/:userId` | Remove a member. |
### Configuration
| Method | Path | Description |
| ------ | ---------- | -------------------------------------------------------------------------------------------------------- |
| GET | `/config` | Public, no-auth feature flags — which integrations are enabled (OAuth providers, billing, S3, SMTP, …). |
### Internal webhook endpoints
These endpoints receive events from the underlying email and billing infrastructure. You don't call them from your applications — they're listed for completeness for self-hosters.
| Method | Path |
| ------ | ----------------------------- |
| POST | `/webhooks/sns` |
| POST | `/webhooks/incoming/stripe` |
## Client libraries
### Node.js
```javascript
const PLUNK_SECRET_KEY = process.env.PLUNK_SECRET_KEY;
async function sendEmail(to, subject, body) {
const response = await fetch('{{API_URL}}/v1/send', {
method: 'POST',
headers: {
'Authorization': `Bearer ${PLUNK_SECRET_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ to, subject, body })
});
const data = await response.json();
if (!data.success) {
throw new Error(`[${data.error.code}] ${data.error.message}`);
}
return data.data;
}
```
### Python
```python
import os
import requests
PLUNK_SECRET_KEY = os.environ['PLUNK_SECRET_KEY']
def send_email(to, subject, body):
response = requests.post(
'{{API_URL}}/v1/send',
headers={
'Authorization': f'Bearer {PLUNK_SECRET_KEY}',
'Content-Type': 'application/json'
},
json={'to': to, 'subject': subject, 'body': body}
)
data = response.json()
if not data.get('success'):
error = data['error']
raise Exception(f"[{error['code']}] {error['message']}")
return data['data']
```
### cURL
```bash
curl -X POST {{API_URL}}/v1/send \
-H "Authorization: Bearer $PLUNK_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"to": "user@example.com", "subject": "Hello", "body": "Message"}'
```
## What's next
- [Send a transactional email](/api-reference/public-api/sendEmail)
- [Track an event](/api-reference/public-api/trackEvent)
- [Error codes reference](/api-reference/errors)