Skip to main content

Newsletter API

The Newsletter API manages the same contacts, consent, campaigns and delivery queues as the Grigora dashboard. Base URL: https://api.grigora.co/general/api/v1/newsletter. Management operations use POST with JSON, following the existing Grigora v1 API convention. The machine-readable OpenAPI 3.1 contract is available at GET /general/api/v1/newsletter/openapi.json. It is also included in the public Swagger documentation at /api-docs/public.

What existed before this API

  • /project/newsletter/*: authenticated dashboard operations using the logged-in user's session and project role. These are not API-key endpoints.
  • /general/newsletter/*: public signup, confirmation, tracking, unsubscribe, preference-center and provider webhook endpoints. Tokens protect recipient actions; provider-specific verification protects inbound delivery events.
  • /general/project/form/entry: website form submissions and newsletter opt-in.

Use the new API v1 namespace for server-side integrations. Never put project API keys, OAuth tokens or SMTP credentials in a website's browser code.

Authentication and permissions

For a project API key, send Authorization: YOUR_PROJECT_API_KEY. The key's project is authoritative. The key creator must still be a member, and their current role determines allowed operations. Legacy keys use the platform's existing owner-resolution behavior. A conflicting project_id is rejected.

For OAuth, send Authorization: Bearer YOUR_ACCESS_TOKEN and include project_id in the JSON body. The project must be part of the grant. The original authorizing user's current role is checked, even if another platform endpoint internally resolves an effective owner account.

OAuth scopeCapabilitiesMinimum project role
newsletter:readRead contacts, audiences, campaigns and reportsMember
newsletter:writeManage contacts, audiences, drafts, templates and importsEditor
newsletter:sendSend, schedule, resume, test and retry deliveryEditor
newsletter:adminSender settings, connections and deleting listsAdministrator

Scopes are independent: newsletter:send does not imply newsletter:read. Exports require an editor. Provider webhook URL discovery requires an administrator and newsletter:admin. Scope grants never override project roles. Existing OAuth integrations must add the required newsletter scopes to their app registration and obtain a fresh user authorization; a refresh token does not expand the original grant.

Responses, limits and retries

Successful JSON responses have data and request_id. Errors have error.code, error.message, error.retryable, optional error.details, and request_id. Authentication failures can use the existing platform auth error format. X-Request-ID identifies requests that reach the newsletter handler.

  • Reads: 600 requests/minute/project.
  • Mutations: 120 requests/minute/project.
  • All API keys and OAuth callers share the project's corresponding bucket.
  • Responses include RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset (seconds until reset).
  • A 429 includes Retry-After in seconds. Use exponential backoff with jitter.
  • Database rate-limit failures fail closed with 503.
  • Page sizes default to 50 and cannot exceed 200. Unknown request properties and out-of-range limits return 400; they are not silently ignored.

Every mutation requires Idempotency-Key: YOUR_STABLE_KEY, or the equivalent idempotency_key JSON field. Use a different key for each intended action, and reuse the original key and body after a network timeout or retryable error.

Receipts are persisted in PostgreSQL and scoped to project, actor and operation. A duplicate completed request returns the stored status and response with Idempotent-Replayed: true, without running it again. Reusing a key with a different body returns 409 IDEMPOTENCY_CONFLICT. A concurrent request returns 409 REQUEST_IN_PROGRESS.

Completed responses are retained for at least 24 hours, then expire during hourly worker maintenance. After expiry, the same key can execute again. An interrupted request may have taken effect before its receipt was completed; its pending receipt deliberately does not expire or automatically execute again. Inspect the campaign/contact/import/job before reconciling that operation. This is duplicate-execution protection, not a claim of an atomic transaction across every business operation and the receipt store.

202 means an operation was accepted or queued, not that emails were delivered. Poll jobs/get, imports/get, or the campaign status and report as appropriate.

Subscribe a contact

curl https://api.grigora.co/general/api/v1/newsletter/contacts/upsert \
-H 'Authorization: YOUR_PROJECT_API_KEY' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: signup-EXTERNAL_EVENT_ID' \
--data '{
"email": "ada@example.com",
"first_name": "Ada",
"last_name": "Lovelace",
"list_ids": ["YOUR_LIST_ID"],
"custom_fields": {"city": "London"},
"consent_text": "Signed up through the newsletter form"
}'

Create the city field using custom-fields/create before collecting it. Custom values use the project's definitions and types. contacts/update changes profile data without subscribing anyone. contacts/upsert requires consent text, respects suppression and list double opt-in, queues confirmation messages when needed, and does not resubscribe an opt-out unless explicitly requested. API list-membership subscriptions and API bulk list additions follow the same confirmation rules. Imports retain their existing explicit consent/confirmation options; review those options during dry-run.

For an email-only upsert with no list IDs, the contact can exist without list membership. Pass the intended list IDs to receive those list's campaigns. contacts/delete remains available; there is no anonymize/erase operation.

Pagination and filtering

Pass limit and the prior response's data.next_cursor. Stop when it is null. Keep filters unchanged while paging. Configuration resources, campaigns, templates, imports and jobs use immutable creation time plus ID as the cursor order. Email documents and private import storage keys are omitted from list responses; fetch individual resources when needed. templates/list also returns a bounded system gallery of built-in template metadata; pass its IDs to campaigns/create to seed a design.

Contacts support search, status, list_id, list_status, tag_id and filter.rules. Suppressions support search and reason. Campaigns support search and status. Jobs support kinds and statuses arrays. Lists omit archived entries unless include_archived is true.

Campaign events and recipients retain their existing after_id pagination: pass the last item's ID, with the same filters, until the next page is empty. contacts/export streams text/csv with backpressure; it does not return a JSON envelope and is deliberately not an MCP tool.

A segment rule group looks like:

{
"op": "and",
"rules": [
{"field": "global_status", "operator": "eq", "value": "subscribed"},
{"field": "custom.city", "operator": "eq", "value": "London"}
]
}

Supported fields include standard contact fields, custom.FIELD_KEY, list, tag, segment and engagement conditions. Groups can nest up to four levels with at most 60 rules. Use segments/preview to validate and count an audience before saving. For contacts/bulk, provide either explicit contact_ids (up to 1000) or a nonempty filter, and any action-specific list/tag IDs. Adding contacts to a list also requires consent_text. A rule group with an empty rules array explicitly matches all contacts; use that deliberately, especially for deletion. The worker processes API bulk actions asynchronously in bounded pages without loading the entire audience into memory. Filters are evaluated as each page is read, rather than representing a frozen snapshot. For filtered jobs, progress reports a processed count with no total until completion. This is not a general-purpose workflow engine.

Create, preview and deliver a campaign

  1. Read settings/get, connections/list and the intended audience.
  2. Create a draft with campaigns/create, supplying subject, editor_json and audience_spec. template_id can seed a saved or built-in template.
  3. Call render for HTML/text preview and campaigns/readiness for send checks.
  4. Call campaigns/test-send with up to five test addresses when authorized.
  5. Call campaigns/send, or campaigns/schedule with an ISO timestamp including an offset (for example 2027-02-18T10:30:00+05:30) and timezone.
  6. Poll campaigns/get, campaigns/stats, campaigns/events and campaigns/recipients. Pause, resume or cancel through their named actions.

An audience specification uses includes and exclusions:

{
"include": [{"type": "list", "id": "YOUR_LIST_ID"}],
"exclude": [{"type": "tag", "id": "YOUR_EXCLUDED_TAG_ID"}]
}

schema/get documents the email block format and available merge tags. Example:

{
"version": 1,
"settings": {},
"blocks": [
{"id": "greeting", "type": "text", "props": {
"html": "<p>Hello {{ contact.full_name | default: \"friend\" }}!</p>"
}}
]
}

To import existing email markup, use an html block with props.html; the newsletter sanitizer still applies. track_opens and track_clicks can be set on a campaign to control tracking.

The same renderer, footer checks, consent rules, suppression checks, plan limits, provider limits and leased delivery outbox are used by the dashboard and API. API access does not bypass sending readiness or provider quotas.

Bulk imports

imports/create accepts a csv string up to 5 MiB and an optional filename. It stages the existing import pipeline. imports/dry-run accepts import_id, a column-index mapping such as {"0":"email","1":"first_name"}, and import options. Inspect the validation result before imports/commit, which queues processing. Poll imports/get for counters and row errors. Split larger datasets into separate imports; do not submit them as thousands of serial upserts.

MCP actions

The 78 newsletter MCP wrappers are prepared for future use, but their imports, registration, and builder policy entries are commented out. No newsletter actions are discoverable or callable through MCP, including website-builder sessions. The HTTP API is available independently. The operation table lists the deferred MCP names for reference only.

Operation reference

The OpenAPI contract is authoritative for parameters and validation. All paths below are relative to /general/api/v1/newsletter and use POST.

API pathOAuth scope suffixMCP action (deferred)
/schema/getreadget_newsletter_schema
/overviewreadget_newsletter_overview
/contacts/listreadlist_newsletter_contacts
/contacts/statsreadget_newsletter_contact_stats
/contacts/getreadget_newsletter_contact
/contacts/upsertwriteupsert_newsletter_contact
/contacts/updatewriteupdate_newsletter_contact
/contacts/deletewritedelete_newsletter_contacts
/contacts/unsubscribewriteunsubscribe_newsletter_contact
/contacts/reset-statuswritereset_newsletter_contact_status
/contacts/membershipwriteset_newsletter_membership
/contacts/tagswritetag_newsletter_contacts
/contacts/bulkwritebulk_manage_newsletter_contacts
/contacts/exportreadCSV stream only
/custom-fields/listreadlist_newsletter_custom_fields
/custom-fields/createwritecreate_newsletter_custom_field
/custom-fields/updatewriteupdate_newsletter_custom_field
/custom-fields/deletewritedelete_newsletter_custom_field
/lists/listreadlist_newsletter_lists
/lists/createwritecreate_newsletter_list
/lists/updatewriteupdate_newsletter_list
/lists/deleteadmindelete_newsletter_list
/tags/listreadlist_newsletter_tags
/tags/createwritecreate_newsletter_tag
/tags/updatewriteupdate_newsletter_tag
/tags/deletewritedelete_newsletter_tag
/segments/listreadlist_newsletter_segments
/segments/getreadget_newsletter_segment
/segments/previewreadpreview_newsletter_segment
/segments/createwritecreate_newsletter_segment
/segments/updatewriteupdate_newsletter_segment
/segments/deletewritedelete_newsletter_segment
/segments/materializewritematerialize_newsletter_segment
/suppressions/listreadlist_newsletter_suppressions
/suppressions/addwritesuppress_newsletter_addresses
/suppressions/removewriteremove_newsletter_suppression
/settings/getreadget_newsletter_settings
/settings/updateadminupdate_newsletter_settings
/connections/listreadlist_newsletter_connections
/connections/createadmincreate_newsletter_connection
/connections/updateadminupdate_newsletter_connection
/connections/deleteadmindelete_newsletter_connection
/connections/testadmintest_newsletter_connection
/connections/verify-dnsadminverify_dns_newsletter_connection
/connections/webhook-urlsadminget_newsletter_provider_webhook_urls
/templates/listreadlist_newsletter_templates
/templates/getreadget_newsletter_template
/templates/createwritecreate_newsletter_template
/templates/updatewriteupdate_newsletter_template
/templates/deletewritedelete_newsletter_template
/renderreadrender_newsletter_email
/campaigns/listreadlist_newsletter_campaigns
/campaigns/getreadget_newsletter_campaign
/campaigns/createwritecreate_newsletter_campaign
/campaigns/updatewriteupdate_newsletter_campaign
/campaigns/audience-countreadcount_newsletter_audience
/campaigns/readinessreadcheck_newsletter_campaign
/campaigns/duplicatewriteduplicate_newsletter_campaign
/campaigns/deletewritedelete_newsletter_campaign
/campaigns/unschedulewriteunschedule_newsletter_campaign
/campaigns/pausewritepause_newsletter_campaign
/campaigns/cancelwritecancel_newsletter_campaign
/campaigns/sendsendsend_newsletter_campaign
/campaigns/resumesendresume_newsletter_campaign
/campaigns/requeue-failedsendrequeue_failed_newsletter_campaign
/campaigns/schedulesendschedule_newsletter_campaign
/campaigns/test-sendsendsend_newsletter_test
/campaigns/statsreadget_newsletter_campaign_stats
/campaigns/eventsreadlist_newsletter_campaign_events
/campaigns/recipientsreadlist_newsletter_campaign_recipients
/imports/createwritecreate_newsletter_import
/imports/listreadlist_newsletter_imports
/imports/getreadget_newsletter_import
/imports/dry-runwritevalidate_newsletter_import
/imports/commitwritecommit_newsletter_import
/imports/cancelwritecancel_newsletter_import
/jobs/listreadlist_newsletter_jobs
/jobs/getreadget_newsletter_job
/jobs/requeuesendrequeue_newsletter_job

Performance and compatibility

This API adopts established integration patterns: bounded cursor pages, queued bulk processing, project-wide throttling, persistent retry receipts, versioned schemas and shared API/MCP contracts. Mailchimp recommends pagination and batches for large workloads (fundamentals); Klaviyo documents account-level throttles and 429 handling (rate limits). Those are design references, not benchmark results.

These endpoints cover Grigora's newsletter management capabilities. Production throughput, latency and deliverability parity with Mailchimp or Klaviyo have not been established. The API does not add their complete automation/flows, SMS, ecommerce event-ingestion or outbound integration-webhook platforms. Provider webhooks here are inbound bounce/complaint events; integrations can poll campaign events for delivery changes.