Skip to content

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.

Download the OpenAPI document

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.

ParameterInTypeDescription
any

Body: ContactInput

FieldTypeDescription
email (required)stringEmail address (normalized: trimmed and lower-cased)
statusSubscriptionChoiceThe status to create a contact with (default subscribed)
fieldsobjectCustom fields used as merge values (up to 100 keys, values up to 1 KB)
tagsstring[]Up to 200 tags
consentConsentInput

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.

ParameterInTypeDescription
any
any
statusqueryContactStatusFilter by status
tagquerystringFilter 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.

ParameterInTypeDescription
any

Body: ContactBatchInput

FieldTypeDescription
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

FieldTypeDescription
email (required)stringEmail 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.

ParameterInTypeDescription
id (required)pathstring

Returns 200 The contact (Contact).

Errors: 404 not_found.

Update a contact's fields, tags or email

PATCH/v1/contacts/{id}

Scope: contacts:write.

ParameterInTypeDescription
id (required)pathstring
any

Body: ContactPatch

FieldTypeDescription
fieldsobjectMerged into existing fields; null removes a field
addTagsstring[]Up to 200 tags
removeTagsstring[]Up to 200 tags
emailstringEmail 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.

ParameterInTypeDescription
id (required)pathstring

Returns 204 Erased.

Errors: 404 not_found.

Unsubscribe a contact

POST/v1/contacts/{id}/unsubscribe

Scope: contacts:write.

ParameterInTypeDescription
id (required)pathstring

Body: UnsubscribeInput

FieldTypeDescription
reasonstring

Returns 200 The contact (Contact).

Errors: 404 not_found.

Resubscribe an unsubscribed contact with new consent

POST/v1/contacts/{id}/resubscribe

Scope: contacts:write.

ParameterInTypeDescription
id (required)pathstring

Body: ResubscribeInput

FieldTypeDescription
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.

ParameterInTypeDescription
any

Body: SegmentInput

FieldTypeDescription
name (required)string
definition (required)SegmentDefinitionFilters 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.

ParameterInTypeDescription
any
any

Returns 200 A page of segments (SegmentPage).

Get a segment

GET/v1/segments/{id}

Scope: contacts:read.

ParameterInTypeDescription
id (required)pathstring

Returns 200 The segment (Segment).

Errors: 404 not_found.

Update a segment

PATCH/v1/segments/{id}

Scope: contacts:write.

ParameterInTypeDescription
id (required)pathstring
any

Body: SegmentPatch

FieldTypeDescription
namestring
definitionSegmentDefinitionFilters 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.

ParameterInTypeDescription
id (required)pathstring

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.

ParameterInTypeDescription
id (required)pathstring

Returns 200 The count (SegmentCount).

Errors: 404 not_found.

Count sendable contacts matching a definition

POST/v1/segments/count

Scope: contacts:read.

Body: SegmentCountInput

FieldTypeDescription
definition (required)SegmentDefinitionFilters 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.

ParameterInTypeDescription
any

Body: TemplateCreateInput

FieldTypeDescription
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.

ParameterInTypeDescription
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.

ParameterInTypeDescription
id (required)pathstring
any

Body: TemplateVersionInput

FieldTypeDescription
subject (required)string
preheaderstring
html (required)stringLiquid HTML. Must contain {{ unsubscribe_url }} and {{ footer_address }}. The HTML and plain text together must stay under 380 KB.
textstringGenerated from the HTML if left out
fromName (required)string
fromEmail (required)stringAn address on the verified sending domain
replyTostringEmail 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.

ParameterInTypeDescription
id (required)pathstring
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.

ParameterInTypeDescription
id (required)pathstring

Returns 200 The template (Template).

Errors: 404 not_found.

Render a preview

POST/v1/templates/{id}/render

Scope: campaigns:write.

ParameterInTypeDescription
id (required)pathstring

Body: RenderInput

FieldTypeDescription
versioninteger
contactIdstringContact ID
sampleRenderSampleSample 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.

ParameterInTypeDescription
any

Body: CampaignCreateInput

FieldTypeDescription
name (required)string
templateId (required)stringTemplate ID
templateVersion (required)integer
segmentIdstringSegment ID
segmentSegmentDefinitionFilters on tags, fields and engagement, up to 3 levels deep and 50 conditions. An empty definition matches every sendable contact.
stagePlanStagePlanA preset plan or your own, told apart by type
sendAtstringISO 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.

ParameterInTypeDescription
any
any
statequeryCampaignState

Returns 200 A page of campaigns (CampaignPage).

Errors: 422 validation_failed.

Update a draft campaign

PATCH/v1/campaigns/{id}

Scope: campaigns:write.

ParameterInTypeDescription
id (required)pathstring
any

Body: CampaignPatch

FieldTypeDescription
namestring
templateIdstringTemplate ID
templateVersioninteger
segmentIdstringSegment ID
segmentSegmentDefinitionFilters on tags, fields and engagement, up to 3 levels deep and 50 conditions. An empty definition matches every sendable contact.
stagePlanStagePlanA preset plan or your own, told apart by type
sendAtstringISO 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.

ParameterInTypeDescription
id (required)pathstring

Returns 204 Deleted.

Errors: 404 not_found, 409 not_draft.

Get a campaign

GET/v1/campaigns/{id}

Scope: campaigns:write.

ParameterInTypeDescription
id (required)pathstring

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.

ParameterInTypeDescription
id (required)pathstring
any

Body: TestSendInput

FieldTypeDescription
to (required)string[]
contactIdstringRender 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.

ParameterInTypeDescription
id (required)pathstring
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.

ParameterInTypeDescription
id (required)pathstring

Body: PauseInput

FieldTypeDescription
reasonstring

Returns 200 Paused (Campaign).

Errors: 404 not_found, 409 invalid_state.

Resume a paused campaign

POST/v1/campaigns/{id}/resume

Scope: campaigns:send.

ParameterInTypeDescription
id (required)pathstring
any

Body: ResumeInput

FieldTypeDescription
acknowledge (required)stringMust 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.

ParameterInTypeDescription
id (required)pathstring

Returns 200 Cancelled (Campaign).

Errors: 404 not_found, 409 invalid_state.

Get a campaign report

GET/v1/campaigns/{id}/report

Scope: campaigns:write.

ParameterInTypeDescription
id (required)pathstring
byquery"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.

ParameterInTypeDescription
id (required)pathstring
any
any
statequeryRecipientStateWhere one send is. unknown: claimed for over 30 minutes with no outcome
stagequeryinteger 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.

ParameterInTypeDescription
any

Body: CsvImportInput

FieldTypeDescription
consentAttestationConsentAttestationAttest 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.

ParameterInTypeDescription
any

Body: MailchimpImportInput

FieldTypeDescription
method (required)MailchimpAuthMethodConnect with OAuth, or with a Mailchimp API key
apiKeystringRequired when method is apiKey
audienceIdsstring[]
filtersImportFiltersDefaults: engagedWithinDays 365, minRating 2, includeUnengaged false
importTemplatesboolean

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.

ParameterInTypeDescription
any
any
statequeryImportStateFilter 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.

ParameterInTypeDescription
id (required)pathstring

Returns 200 The import (Import).

Errors: 404 not_found.

Cancel an uncommitted import

DELETE/v1/imports/{id}

Scope: contacts:write.

ParameterInTypeDescription
id (required)pathstring

Returns 204 Cancelled.

Errors: 404 not_found, 409 already_committed.

Commit a dry-run import

POST/v1/imports/{id}/commit

Scope: contacts:write.

ParameterInTypeDescription
id (required)pathstring
any

Body: ImportCommitInput

FieldTypeDescription
filtersImportFiltersDefaults: 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.

ParameterInTypeDescription
any
any
kindqueryExportKindFilter 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.

ParameterInTypeDescription
any

Body: ExportInput

FieldTypeDescription
kind (required)ExportKind
campaignIdstringCampaign ID
contactIdstringContact ID
fromstringevents only: keep events at or after this time
tostringevents 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.

ParameterInTypeDescription
id (required)pathstring

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

FieldTypeDescription
apiDomainstring
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.

ParameterInTypeDescription
any

Body: SettingsPatch

FieldTypeDescription
defaultFromNamestring
defaultFromEmailstring
defaultReplyTostring
footerAddressstringYour physical mailing address (CAN-SPAM)
pausePauseSettingsPatch
sunsetSunsetSettingsPatch
quotaReserveinteger
iamScopesobjectMaps 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

FieldTypeDescription
name (required)string
scopes (required)Scope[]

Returns 201 Created, with secret (ApiKey).

Errors: 422 validation_failed.

List API keys

GET/v1/keys

Scope: admin.

ParameterInTypeDescription
any
any

Returns 200 A page of keys (no secrets) (ApiKeyPage).

Revoke an API key

DELETE/v1/keys/{id}

Scope: admin.

ParameterInTypeDescription
id (required)pathstring

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.

ParameterInTypeDescription
token (required)pathstring

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.

ParameterInTypeDescription
token (required)pathstring

Body: object

FieldTypeDescription
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.

ParameterInTypeDescription
codequerystring
state (required)querystring
errorquerystring

Returns 200 Connected page.