API reference
Version 1.0.0 of the API. Email campaigns where your list never leaves your AWS account. Every request acts on exactly one tenant, set by the credential. Requests go tohttps://api.rhinorelay.com/v1 for accounts in us-east-2, https://api.<region>.rhinorelay.com/v1 for other Regions, or your own API domain, with an API key; or to your Region's SigV4 endpoint. See Authentication, Errors,Pagination and Idempotency.
The interactive reference adds search and code samples in curl, JavaScript and Python. It loads only when you open it, from our own site.
Any authenticated endpoint can also return bad_request, unauthenticated, forbidden, tenant_disconnected, wrong_region, rate_limited, internal, dependency_unavailable. Each endpoint below lists the others it can return.
Contacts
Create or update a contact
POST/v1/contacts
Upserts by email. Returns 201 when the contact is new and 200 when it existed. A subscribed contact needs a consent record.
Scope: contacts:write.
| Parameter | In | Type | Description |
|---|---|---|---|
| any |
Body: ContactInput
| Field | Type | Description |
|---|---|---|
email (required) | string | Email address (normalized: trimmed and lower-cased) |
status | SubscriptionChoice | The status to create a contact with (default subscribed) |
fields | object | Custom fields used as merge values (up to 100 keys, values up to 1 KB) |
tags | string[] | Up to 200 tags |
consent | ConsentInput |
Returns 200 Updated (Contact), 201 Created (Contact).
Errors: 409 contact_suppressed, 409 idempotency_conflict, 422 validation_failed, 422 consent_required.
List contacts
GET/v1/contacts
Scope: contacts:read.
| Parameter | In | Type | Description |
|---|---|---|---|
| any | ||
| any | ||
status | query | ContactStatus | Filter by status |
tag | query | string | Filter by tag |
Returns 200 A page of contacts (ContactPage).
Errors: 422 validation_failed.
Create or update up to 1,000 contacts
POST/v1/contacts/batch
Each item is processed like POST /contacts; results are returned per item.
Scope: contacts:write.
| Parameter | In | Type | Description |
|---|---|---|---|
| any |
Body: ContactBatchInput
| Field | Type | Description |
|---|---|---|
contacts (required) | object[] | Up to 1,000 ContactInput objects |
Returns 207 Per-item results (ContactBatchResult).
Errors: 409 idempotency_conflict, 413 batch_too_large, 422 validation_failed.
Look up a contact by email
POST/v1/contacts/lookup
Email addresses never go in URLs, so lookups by email use POST.
Scope: contacts:read.
Body: ContactLookupInput
| Field | Type | Description |
|---|---|---|
email (required) | string | Email address (normalized: trimmed and lower-cased) |
Returns 200 Matching contacts (zero or one) (ContactLookupResult).
Errors: 422 validation_failed.
Get a contact
GET/v1/contacts/{id}
Scope: contacts:read.
| Parameter | In | Type | Description |
|---|---|---|---|
id (required) | path | string |
Returns 200 The contact (Contact).
Errors: 404 not_found.
Update a contact's fields, tags or email
PATCH/v1/contacts/{id}
Scope: contacts:write.
| Parameter | In | Type | Description |
|---|---|---|---|
id (required) | path | string | |
| any |
Body: ContactPatch
| Field | Type | Description |
|---|---|---|
fields | object | Merged into existing fields; null removes a field |
addTags | string[] | Up to 200 tags |
removeTags | string[] | Up to 200 tags |
email | string | Email address (normalized: trimmed and lower-cased) |
Returns 200 The updated contact (Contact).
Errors: 404 not_found, 409 email_taken, 412 version_conflict, 422 validation_failed.
Erase a contact
DELETE/v1/contacts/{id}
Deletes the contact, its email index and every send record, and keeps a salted hash of the address so it can't be re-imported.
Scope: contacts:write.
| Parameter | In | Type | Description |
|---|---|---|---|
id (required) | path | string |
Returns 204 Erased.
Errors: 404 not_found.
Unsubscribe a contact
POST/v1/contacts/{id}/unsubscribe
Scope: contacts:write.
| Parameter | In | Type | Description |
|---|---|---|---|
id (required) | path | string |
Body: UnsubscribeInput
| Field | Type | Description |
|---|---|---|
reason | string |
Returns 200 The contact (Contact).
Errors: 404 not_found.
Resubscribe an unsubscribed contact with new consent
POST/v1/contacts/{id}/resubscribe
Scope: contacts:write.
| Parameter | In | Type | Description |
|---|---|---|---|
id (required) | path | string |
Body: ResubscribeInput
| Field | Type | Description |
|---|---|---|
consent (required) | ConsentInput |
Returns 200 The contact (Contact).
Errors: 404 not_found, 409 cannot_resubscribe, 422 validation_failed.
Segments
Create a segment
POST/v1/segments
Scope: contacts:write.
| Parameter | In | Type | Description |
|---|---|---|---|
| any |
Body: SegmentInput
| Field | Type | Description |
|---|---|---|
name (required) | string | |
definition (required) | SegmentDefinition | Filters on tags, fields and engagement, up to 3 levels deep and 50 conditions. An empty definition matches every sendable contact. |
Returns 201 Created (Segment).
Errors: 409 idempotency_conflict, 422 segment_invalid, 422 validation_failed.
List segments
GET/v1/segments
Scope: contacts:read.
| Parameter | In | Type | Description |
|---|---|---|---|
| any | ||
| any |
Returns 200 A page of segments (SegmentPage).
Get a segment
GET/v1/segments/{id}
Scope: contacts:read.
| Parameter | In | Type | Description |
|---|---|---|---|
id (required) | path | string |
Returns 200 The segment (Segment).
Errors: 404 not_found.
Update a segment
PATCH/v1/segments/{id}
Scope: contacts:write.
| Parameter | In | Type | Description |
|---|---|---|---|
id (required) | path | string | |
| any |
Body: SegmentPatch
| Field | Type | Description |
|---|---|---|
name | string | |
definition | SegmentDefinition | Filters on tags, fields and engagement, up to 3 levels deep and 50 conditions. An empty definition matches every sendable contact. |
Returns 200 The updated segment (Segment).
Errors: 404 not_found, 412 version_conflict, 422 segment_invalid, 422 validation_failed.
Delete a segment
DELETE/v1/segments/{id}
Scope: contacts:write.
| Parameter | In | Type | Description |
|---|---|---|---|
id (required) | path | string |
Returns 204 Deleted.
Errors: 404 not_found, 409 segment_in_use.
Count a saved segment's sendable contacts
GET/v1/segments/{id}/count
Scope: contacts:read.
| Parameter | In | Type | Description |
|---|---|---|---|
id (required) | path | string |
Returns 200 The count (SegmentCount).
Errors: 404 not_found.
Count sendable contacts matching a definition
POST/v1/segments/count
Scope: contacts:read.
Body: SegmentCountInput
| Field | Type | Description |
|---|---|---|
definition (required) | SegmentDefinition | Filters on tags, fields and engagement, up to 3 levels deep and 50 conditions. An empty definition matches every sendable contact. |
Returns 200 The count (SegmentCount).
Errors: 422 segment_invalid, 422 validation_failed.
Templates
Create a template with its first version
POST/v1/templates
Scope: campaigns:write.
| Parameter | In | Type | Description |
|---|---|---|---|
| any |
Body: TemplateCreateInput
| Field | Type | Description |
|---|---|---|
name (required) | string | |
version (required) | TemplateVersionInput |
Returns 201 Created (Template).
Errors: 409 idempotency_conflict, 422 template_invalid, 422 from_domain_unverified, 422 validation_failed.
List templates
GET/v1/templates
Scope: campaigns:write.
| Parameter | In | Type | Description |
|---|---|---|---|
| any | ||
| any |
Returns 200 A page of templates (TemplatePage).
Add a template version
POST/v1/templates/{id}/versions
Versions never change once created.
Scope: campaigns:write.
| Parameter | In | Type | Description |
|---|---|---|---|
id (required) | path | string | |
| any |
Body: TemplateVersionInput
| Field | Type | Description |
|---|---|---|
subject (required) | string | |
preheader | string | |
html (required) | string | Liquid HTML. Must contain {{ unsubscribe_url }} and {{ footer_address }}. The HTML and plain text together must stay under 380 KB. |
text | string | Generated from the HTML if left out |
fromName (required) | string | |
fromEmail (required) | string | An address on the verified sending domain |
replyTo | string | Email address (normalized: trimmed and lower-cased) |
Returns 201 Created (TemplateVersion).
Errors: 404 not_found, 409 idempotency_conflict, 422 template_invalid, 422 from_domain_unverified, 422 validation_failed.
List a template's versions
GET/v1/templates/{id}/versions
Every version, newest first, including those older than the newest 20.
Scope: campaigns:write.
| Parameter | In | Type | Description |
|---|---|---|---|
id (required) | path | string | |
| any | ||
| any |
Returns 200 A page of versions (TemplateVersionPage).
Errors: 404 not_found.
Get a template and its newest 20 versions
GET/v1/templates/{id}
Scope: campaigns:write.
| Parameter | In | Type | Description |
|---|---|---|---|
id (required) | path | string |
Returns 200 The template (Template).
Errors: 404 not_found.
Render a preview
POST/v1/templates/{id}/render
Scope: campaigns:write.
| Parameter | In | Type | Description |
|---|---|---|---|
id (required) | path | string |
Body: RenderInput
| Field | Type | Description |
|---|---|---|
version | integer | |
contactId | string | Contact ID |
sample | RenderSample | Sample values to render with instead of a stored contact |
Returns 200 The rendered email (RenderResult).
Errors: 404 not_found, 422 render_failed, 422 validation_failed.
Campaigns
Create a draft campaign
POST/v1/campaigns
Scope: campaigns:write.
| Parameter | In | Type | Description |
|---|---|---|---|
| any |
Body: CampaignCreateInput
| Field | Type | Description |
|---|---|---|
name (required) | string | |
templateId (required) | string | Template ID |
templateVersion (required) | integer | |
segmentId | string | Segment ID |
segment | SegmentDefinition | Filters on tags, fields and engagement, up to 3 levels deep and 50 conditions. An empty definition matches every sendable contact. |
stagePlan | StagePlan | A preset plan or your own, told apart by type |
sendAt | string | ISO 8601 date-time |
Returns 201 Created (Campaign).
Errors: 404 not_found, 409 idempotency_conflict, 422 validation_failed, 422 segment_invalid.
List campaigns
GET/v1/campaigns
Scope: campaigns:write.
| Parameter | In | Type | Description |
|---|---|---|---|
| any | ||
| any | ||
state | query | CampaignState |
Returns 200 A page of campaigns (CampaignPage).
Errors: 422 validation_failed.
Update a draft campaign
PATCH/v1/campaigns/{id}
Scope: campaigns:write.
| Parameter | In | Type | Description |
|---|---|---|---|
id (required) | path | string | |
| any |
Body: CampaignPatch
| Field | Type | Description |
|---|---|---|
name | string | |
templateId | string | Template ID |
templateVersion | integer | |
segmentId | string | Segment ID |
segment | SegmentDefinition | Filters on tags, fields and engagement, up to 3 levels deep and 50 conditions. An empty definition matches every sendable contact. |
stagePlan | StagePlan | A preset plan or your own, told apart by type |
sendAt | string | ISO 8601 date-time |
Returns 200 The updated campaign (Campaign).
Errors: 404 not_found, 409 not_draft, 412 version_conflict, 422 validation_failed.
Delete a draft campaign
DELETE/v1/campaigns/{id}
Only drafts can be deleted; cancel a campaign that has been scheduled.
Scope: campaigns:write.
| Parameter | In | Type | Description |
|---|---|---|---|
id (required) | path | string |
Returns 204 Deleted.
Errors: 404 not_found, 409 not_draft.
Get a campaign
GET/v1/campaigns/{id}
Scope: campaigns:write.
| Parameter | In | Type | Description |
|---|---|---|---|
id (required) | path | string |
Returns 200 The campaign (Campaign).
Errors: 404 not_found.
Send test emails
POST/v1/campaigns/{id}/test
Up to 10 addresses, with a [TEST] subject prefix. Test sends don't change campaign counters and count against the quota reserve.
Scope: campaigns:write.
| Parameter | In | Type | Description |
|---|---|---|---|
id (required) | path | string | |
| any |
Body: TestSendInput
| Field | Type | Description |
|---|---|---|
to (required) | string[] | |
contactId | string | Render with this contact's fields |
Returns 202 Accepted by SES (TestSendResult).
Errors: 404 not_found, 409 quota_reserve_exhausted, 409 idempotency_conflict, 422 render_failed, 422 validation_failed.
Schedule a campaign
POST/v1/campaigns/{id}/schedule
Runs the pre-send checks (template, connection, entitlement, SES production access, DKIM, MAIL FROM, DMARC, non-empty segment) and schedules stage 0 at sendAt or now.
Scope: campaigns:send.
| Parameter | In | Type | Description |
|---|---|---|---|
id (required) | path | string | |
| any |
Returns 202 Scheduled (Campaign).
Errors: 402 entitlement_inactive, 404 not_found, 409 invalid_state, 409 idempotency_conflict, 422 schedule_checks_failed.
Pause a sending campaign
POST/v1/campaigns/{id}/pause
Scope: campaigns:send.
| Parameter | In | Type | Description |
|---|---|---|---|
id (required) | path | string |
Body: PauseInput
| Field | Type | Description |
|---|---|---|
reason | string |
Returns 200 Paused (Campaign).
Errors: 404 not_found, 409 invalid_state.
Resume a paused campaign
POST/v1/campaigns/{id}/resume
Scope: campaigns:send.
| Parameter | In | Type | Description |
|---|---|---|---|
id (required) | path | string | |
| any |
Body: ResumeInput
| Field | Type | Description |
|---|---|---|
acknowledge (required) | string | Must equal the campaign's pauseReason |
Returns 202 Resumed (Campaign).
Errors: 402 entitlement_inactive, 404 not_found, 409 invalid_state, 409 idempotency_conflict, 422 acknowledge_mismatch.
Cancel a campaign
POST/v1/campaigns/{id}/cancel
Scope: campaigns:send.
| Parameter | In | Type | Description |
|---|---|---|---|
id (required) | path | string |
Returns 200 Cancelled (Campaign).
Errors: 404 not_found, 409 invalid_state.
Get a campaign report
GET/v1/campaigns/{id}/report
Scope: campaigns:write.
| Parameter | In | Type | Description |
|---|---|---|---|
id (required) | path | string | |
by | query | "stage" | "link" |
Returns 200 Counters and rates (Report).
Errors: 404 not_found.
List a campaign's send records
GET/v1/campaigns/{id}/recipients
Scope: campaigns:write.
| Parameter | In | Type | Description |
|---|---|---|---|
id (required) | path | string | |
| any | ||
| any | ||
state | query | RecipientState | Where one send is. unknown: claimed for over 30 minutes with no outcome |
stage | query | integer or null |
Returns 200 A page of recipients (RecipientPage).
Errors: 404 not_found, 422 validation_failed.
Imports
Start a CSV import
POST/v1/imports/csv
Returns a presigned PUT URL for your own imports bucket. Upload the file there; it never passes through our account.
Scope: contacts:write.
| Parameter | In | Type | Description |
|---|---|---|---|
| any |
Body: CsvImportInput
| Field | Type | Description |
|---|---|---|
consentAttestation | ConsentAttestation | Attest that every subscribed row consented; used for rows without consent_source and consent_at |
Returns 201 Created, with uploadUrl (Import).
Errors: 402 entitlement_inactive, 409 idempotency_conflict, 422 validation_failed.
Start a Mailchimp import
POST/v1/imports/mailchimp
With an API key the import starts at once; with OAuth, send the user to authorizeUrl. Credentials are kept in your account's SSM and deleted on commit.
Scope: contacts:write.
| Parameter | In | Type | Description |
|---|---|---|---|
| any |
Body: MailchimpImportInput
| Field | Type | Description |
|---|---|---|
method (required) | MailchimpAuthMethod | Connect with OAuth, or with a Mailchimp API key |
apiKey | string | Required when method is apiKey |
audienceIds | string[] | |
filters | ImportFilters | Defaults: engagedWithinDays 365, minRating 2, includeUnengaged false |
importTemplates | boolean |
Returns 201 Created (Import).
Errors: 402 entitlement_inactive, 409 idempotency_conflict, 422 validation_failed, 422 mailchimp_auth_failed.
List imports
GET/v1/imports
Newest first.
Scope: contacts:read.
| Parameter | In | Type | Description |
|---|---|---|---|
| any | ||
| any | ||
state | query | ImportState | Filter by state |
Returns 200 A page of imports (ImportPage).
Errors: 422 validation_failed.
Get an import and its dry-run summary
GET/v1/imports/{id}
Scope: contacts:read.
| Parameter | In | Type | Description |
|---|---|---|---|
id (required) | path | string |
Returns 200 The import (Import).
Errors: 404 not_found.
Cancel an uncommitted import
DELETE/v1/imports/{id}
Scope: contacts:write.
| Parameter | In | Type | Description |
|---|---|---|---|
id (required) | path | string |
Returns 204 Cancelled.
Errors: 404 not_found, 409 already_committed.
Commit a dry-run import
POST/v1/imports/{id}/commit
Scope: contacts:write.
| Parameter | In | Type | Description |
|---|---|---|---|
id (required) | path | string | |
| any |
Body: ImportCommitInput
| Field | Type | Description |
|---|---|---|
filters | ImportFilters | Defaults: engagedWithinDays 365, minRating 2, includeUnengaged false |
Returns 202 Committing (Import).
Errors: 402 entitlement_inactive, 404 not_found, 409 not_ready, 409 idempotency_conflict, 422 validation_failed.
Exports
List exports
GET/v1/exports
Newest first.
Scope: admin.
| Parameter | In | Type | Description |
|---|---|---|---|
| any | ||
| any | ||
kind | query | ExportKind | Filter by kind |
Returns 200 A page of exports (ExportPage).
Errors: 422 validation_failed.
Start an export
POST/v1/exports
Writes to your own exports bucket. contact_data exports one contact's data (a GDPR access request).
Scope: admin.
| Parameter | In | Type | Description |
|---|---|---|---|
| any |
Body: ExportInput
| Field | Type | Description |
|---|---|---|
kind (required) | ExportKind | |
campaignId | string | Campaign ID |
contactId | string | Contact ID |
from | string | events only: keep events at or after this time |
to | string | events only: keep events before this time |
Returns 202 Running (Export).
Errors: 404 not_found, 409 idempotency_conflict, 422 validation_failed.
Get an export and its download link
GET/v1/exports/{id}
Scope: admin.
| Parameter | In | Type | Description |
|---|---|---|---|
id (required) | path | string |
Returns 200 The export (Export).
Errors: 404 not_found.
Health
Check setup and sending health
GET/v1/health
Returns 200 even when ok is false; each failing check says how to fix it.
Any valid credential.
Returns 200 Health checks (Health).
Setup
Connect the installed template
POST/v1/setup/verify
Scope: admin.
Returns 200 Health after connecting (Health).
Errors: 422 stack_not_found, 422 role_assume_failed.
Domains
Set the tracking domain and an optional custom API domain
POST/v1/setup/domains
Scope: admin.
Body: DomainsInput
| Field | Type | Description |
|---|---|---|
apiDomain | string | |
trackingDomain (required) | string |
Returns 202 Certificates requested; add the validation records (Domains).
Errors: 422 validation_failed.
Get domain and certificate status
GET/v1/setup/domains
Scope: admin.
Returns 200 Domains (Domains).
Settings
Get settings
GET/v1/settings
Scope: admin.
Returns 200 Settings (Settings).
Update settings
PATCH/v1/settings
Scope: admin.
| Parameter | In | Type | Description |
|---|---|---|---|
| any |
Body: SettingsPatch
| Field | Type | Description |
|---|---|---|
defaultFromName | string | |
defaultFromEmail | string | |
defaultReplyTo | string | |
footerAddress | string | Your physical mailing address (CAN-SPAM) |
pause | PauseSettingsPatch | |
sunset | SunsetSettingsPatch | |
quotaReserve | integer | |
iamScopes | object | Maps SigV4 caller role ARNs to scopes (default admin) |
Returns 200 Settings (Settings).
Errors: 412 version_conflict, 422 validation_failed.
API keys
Create an API key
POST/v1/keys
The secret is returned once.
Scope: admin.
Body: ApiKeyInput
| Field | Type | Description |
|---|---|---|
name (required) | string | |
scopes (required) | Scope[] |
Returns 201 Created, with secret (ApiKey).
Errors: 422 validation_failed.
List API keys
GET/v1/keys
Scope: admin.
| Parameter | In | Type | Description |
|---|---|---|---|
| any | ||
| any |
Returns 200 A page of keys (no secrets) (ApiKeyPage).
Revoke an API key
DELETE/v1/keys/{id}
Scope: admin.
| Parameter | In | Type | Description |
|---|---|---|---|
id (required) | path | string |
Returns 204 Revoked.
Errors: 404 not_found.
Public
Unsubscribe confirmation page
GET/u/{token}
Shows a button. A GET never unsubscribes (link scanners open every link).
No authentication.
| Parameter | In | Type | Description |
|---|---|---|---|
token (required) | path | string |
Returns 200 Confirmation page, 404 Invalid link page.
Unsubscribe (one-click or the page's button)
POST/u/{token}
RFC 8058 one-click: body List-Unsubscribe=One-Click. Unsubscribes immediately.
No authentication.
| Parameter | In | Type | Description |
|---|---|---|---|
token (required) | path | string |
Body: object
| Field | Type | Description |
|---|---|---|
List-Unsubscribe | "One-Click" |
Returns 200 Unsubscribed page, 404 Invalid link page.
Mailchimp OAuth redirect
GET/oauth/mailchimp/callback
Mailchimp redirects here after the user approves access. The token is written straight to the tenant's SSM.
No authentication.
| Parameter | In | Type | Description |
|---|---|---|---|
code | query | string | |
state (required) | query | string | |
error | query | string |
Returns 200 Connected page.