Developer docs

Everything the Ishtaqna dashboard does is available from code through the REST API, and from AI assistants like Claude Code and Cursor through the MCP server. Both run the same checks as the dashboard: permissions, plan limits, sending reputation guardrails and consent.

Getting started

Create an API key in Dashboard → Developers. It is shown once and starts with isht_live_. Send it with every request:

Authorization: Bearer isht_live_…

Key levels

  • fullEverything, sending and deleting included.
  • readRead only: lists, reports and previews.
  • signupsAdding contacts only (add_contact), for signup forms on your site.

Connect an AI assistant (MCP)

The MCP server gives an assistant tools with the same names and inputs as the actions. It only sees the tools the key's level allows, and the sending and deleting tools are marked as irreversible, so the assistant asks you before using them.

Endpoint

https://ishtaqna.com/mcp

Setup per client

Claude Code

In a terminal:

claude mcp add --transport http ishtaqna https://ishtaqna.com/mcp \
  --header "Authorization: Bearer isht_live_…"

Cursor

~/.cursor/mcp.json

{
  "mcpServers": {
    "ishtaqna": {
      "url": "https://ishtaqna.com/mcp",
      "headers": {
        "Authorization": "Bearer isht_live_…"
      }
    }
  }
}

VS Code

.vscode/mcp.json

{
  "servers": {
    "ishtaqna": {
      "type": "http",
      "url": "https://ishtaqna.com/mcp",
      "headers": {
        "Authorization": "Bearer ${input:ishtaqna-key}"
      }
    }
  },
  "inputs": [
    {
      "type": "promptString",
      "id": "ishtaqna-key",
      "description": "Ishtaqna API key",
      "password": true
    }
  ]
}

Codex

~/.codex/config.toml, with the key in the ISHTAQNA_API_KEY environment variable:

[mcp_servers.ishtaqna]
url = "https://ishtaqna.com/mcp"
bearer_token_env_var = "ISHTAQNA_API_KEY"

Claude on the web and Claude Desktop connect remote servers through OAuth only, which comes later. Until then, use Claude Code, Cursor, VS Code or Codex.

Try asking

  • How many of my customers in Riyadh haven't been emailed yet?
  • Create a “we missed you” campaign for VIP customers with a short subject in Saudi dialect, then send me a test.
  • How did the last campaign do, and which link got the most clicks?

Safety

  • Every tool goes through the same runAction as the dashboard, so plan limits, automatic pauses and content review apply to the assistant as they apply to you.
  • The sending tools (send_test_email, send_campaign, resume_campaign) and the delete tools can't be undone, and they tell the assistant to get your explicit yes first.
  • Contact names, subjects and replies are data other people wrote; the assistant is told never to follow instructions inside them.
  • Every call is in the audit log with via: mcp, and you can revoke the key at any time.

REST API

All requests are JSON under /api/v1, at most 300 a minute per key. Long lists return nextCursor; send it back as cursor for the next page.

curl https://ishtaqna.com/api/v1/campaigns \
  -H "Authorization: Bearer isht_live_…" \
  -H "Content-Type: application/json" \
  -d '{"template": "win_back"}'

Error codes

Every error is { "error": { "code", "message" } }. Codes are stable and never change meaning, so branch on them, not on the text.

CodeHTTPMeaning
unauthorized401The API key or session is missing, invalid or revoked.
forbidden403Your role or key level doesn't allow this action.
not_found404Not found in this organization.
validation_failed400The input is invalid.
conflict409The current state doesn't allow this.
revision_conflict409It changed since you loaded it. Reload and try again.
rate_limited429Too many requests. Wait and retry.
subscription_required402This needs an active plan.
plan_limit_emails402Not enough emails left on your plan this month.
plan_limit_contacts402Your plan's contact limit is reached.
plan_limit_brands402Your plan's brand limit is reached.
credit_insufficient402Not enough message credit.
account_blocked403This organization is blocked. Only reading, pausing and billing work.
sender_not_allowed422The sender address isn't allowed.
send_check_failed422A pre-send check failed.
no_recipients422Nobody is left to send to.
channel_not_connected422This channel isn't connected yet.
template_not_approved422The WhatsApp template isn't approved yet.
quiet_hours422Marketing messages can't be sent during quiet hours.
sending_disabled503No email provider is configured here, so nothing can be sent.
unavailable503A required service isn't available. Retry later.
internal_error500Something failed on our side.

Action reference

Every action is available three ways: a REST route, an MCP tool with the same name, and the dashboard.

Organization

GET/api/v1/organization·MCP tool: get_organization·Keys: full, read

Gets the organization the key or session belongs to: its name, country, time zone, plan and the plan's limits.

No input.

PATCH/api/v1/organization·MCP tool: update_organization·Keys: full

Changes the organization's name, time zone or language. Send only the fields to change; the others stay as they are.

FieldTypeRequiredDescription
namestringThe business name, as customers know it.
timezonestringIANA time zone used for schedules and quiet hours, e.g. Asia/Riyadh.
locale"ar" | "en"Language of the dashboard and system emails.

GET/api/v1/organization/sending-health·MCP tool: get_sending_health·Keys: full, read

Shows how healthy this organization's email sending is: bounce and complaint rates over 30 days against the pause and block levels, the sending tier, each brand's Gmail warm-up allowance, and how many contacts are dormant or suppressed.

No input.

GET/api/v1/organization/stats·MCP tool: get_overview_stats·Keys: full, read

The organization's numbers for the last 7 or 30 days next to the period before: messages sent, opened, clicked and unsubscribed (campaigns and automations, tests excluded), how many contacts it has and gained with a daily series, the latest sent campaigns with their open rates, the same numbers day by day, and the 12 latest events (opens, clicks, unsubscribes, paid orders, signups and imports) for an activity feed.

FieldTypeRequiredDescription
daysinteger7 or 30 (default).

Delete the account

can't be undone

DELETE/api/v1/organization·MCP tool: delete_organization·Keys: full

Deletes the organization and everything in it: contacts, campaigns, brands, automations, store connections, keys and members. The account locks at once and all sending stops; the data is erased for good 30 days later. Opt-out records are kept as one-way hashes so unsubscribed people are never contacted again. Send the organization's exact name and acknowledged: true (the owner read /legal/delete-account) to confirm. It can't be undone.

FieldTypeRequiredDescription
confirmstringyesThe organization's exact name, as a confirmation.
acknowledgedbooleanyestrue: the owner has read /legal/delete-account and understands the deletion is permanent.

Brands

List brands

read only

GET/api/v1/brands·MCP tool: list_brands·Keys: full, read

Lists the organization's brands (its shops, clinics or stores), oldest first, with their sending addresses.

No input.

Get a brand

read only

GET/api/v1/brands/{id}·MCP tool: get_brand·Keys: full, read

Gets one brand of the organization by its ID, with its names, reply-to details and sending address.

FieldTypeRequiredDescription
idstringyesThe resource's ID.

GET/api/v1/brands/slugs/{slug}·MCP tool: check_brand_slug·Keys: full, read

Says whether a slug is free to use. The slug becomes the brand's subdomain on the shared sending domain.

FieldTypeRequiredDescription
slugstringyesThe slug to check, e.g. alsanabel.

POST/api/v1/brands·MCP tool: create_brand·Keys: full

Adds a brand (a shop, clinic or store) to the organization. It can send at once from its shared address hello@<slug>.<shared domain>.

FieldTypeRequiredDescription
nameArstringyesThe brand name in Arabic, as customers see it in messages.
nameEnstringThe brand name in English, if it has one.
postalAddressstringBusiness address printed in email footers. Emails can't be sent without one.
replyToEmailstringWhere email replies go, e.g. the owner's Gmail.
replyToWhatsappstringWhatsApp number for the reply button in emails, in international format.
websiteUrlstringThe brand's website or Instagram page.
logoUrlstringSquare logo, https link.
slugstringLowercase letters, digits and dashes, 3 to 40 characters. Default: made from nameEn, or random.

PATCH/api/v1/brands/{id}·MCP tool: update_brand·Keys: full

Changes a brand's names, address, reply-to details, website or logo. Send only the fields to change.

FieldTypeRequiredDescription
idstringyesThe resource's ID.
nameArstringThe brand name in Arabic, as customers see it in messages.
nameEnstringThe brand name in English, if it has one.
postalAddressstringBusiness address printed in email footers. Emails can't be sent without one.
replyToEmailstringWhere email replies go, e.g. the owner's Gmail.
replyToWhatsappstringWhatsApp number for the reply button in emails, in international format.
websiteUrlstringThe brand's website or Instagram page.
logoUrlstring | nullSquare logo, https link; null removes it.

Delete a brand

can't be undone

DELETE/api/v1/brands/{id}·MCP tool: delete_brand·Keys: full

Deletes a brand. Its campaigns stop and its shared sending address stops working. It can't be undone. The last brand can't be deleted.

FieldTypeRequiredDescription
idstringyesThe resource's ID.

PATCH/api/v1/brands/{id}/page·MCP tool: update_brand_page·Keys: full

Changes the brand's public signup page at <slug>.ishtaqna.com: whether it's on, its headline and description, its color (a preset or the brand's own), theme and background, what it asks for (email, phone or both, and a first name), the button and thank-you text, the subscriber count, and links (WhatsApp, Instagram, X, TikTok, Snapchat, Facebook, Telegram, YouTube, Threads, a website). Send only what changes.

FieldTypeRequiredDescription
idstringyesThe resource's ID.
enabledbooleanfalse hides the page.
headlinestringThe big line; the brand name when empty.
descriptionstringOne or two sentences: what subscribers get.
accent"teal" | "emerald" | "sky" | "indigo" | "plum" | "rose" | "amber" | "ink"teal, emerald, sky, indigo, plum, rose, amber or ink.
collect"email" | "phone" | "both"email, phone or both.
askNamebooleanAsk for a first name.
buttonTextstringThe subscribe button text.
thanksTextstringShown after subscribing.
showCountbooleanShow the number of subscribers (from 50).
customColorstringThe brand's own color, e.g. #0F766E; empty goes back to accent.
theme"light" | "soft" | "dark"light, soft (tinted) or dark.
background"plain" | "dots" | "grid" | "glow" | "aurora"plain, dots, grid, glow or aurora.
linksobjectLinks under the form; send only the ones that change.

PATCH/api/v1/brands/{id}/design·MCP tool: update_brand_design·Keys: full

Changes how the brand's emails look: background, text and button colors, button and image corners, alignment (right or center) and what sits at the top (logo, name or nothing). Every email the brand sends uses it. Send only what changes.

FieldTypeRequiredDescription
idstringyesThe resource's ID.
backgroundstringThe email's background, e.g. #FFFFFF.
textstringHeadings and text, e.g. #171717.
accentstringButtons and links, e.g. #0F766E. Button text turns white or black to stay readable.
corners"none" | "small" | "medium" | "large" | "full"Button and image corners: none, small, medium, large or full (round buttons).
align"right" | "center"right: aligned to the start, like a letter. center: centered, like an announcement.
header"logo" | "name" | "none"What sits at the top: the logo (the name when there's no logo), the name, or nothing.

Contacts

List contacts

read only

GET/api/v1/contacts·MCP tool: list_contacts·Keys: full, read

Lists the organization's contacts, newest first, 50 a page, with their tags and the status of each channel. Pass nextCursor back as cursor for the next page.

FieldTypeRequiredDescription
searchstringPart of an email, phone number or name.
tagstringOnly contacts with this tag.
citystringOnly contacts in this city.
channel"email" | "whatsapp"Only contacts subscribed (active) on this channel.
segmentstringOnly contacts a saved segment picks.
limitintegerContacts per page, 50 by default, 100 at most.
cursorstringnextCursor from the previous page.

Get a contact

read only

GET/api/v1/contacts/{id}·MCP tool: get_contact·Keys: full, read

Gets one contact by its ID, with its details, tags, custom field values and the consent record and status of each channel.

FieldTypeRequiredDescription
idstringyesThe resource's ID.

POST/api/v1/contacts·MCP tool: add_contact·Keys: full, signups

Adds one customer, or updates them if the email or phone is already a contact. Consent is recorded per channel; an unknown basis keeps the channel pending, so no marketing goes out until they agree. Works with signups-only keys.

FieldTypeRequiredDescription
emailstringEmail address. Typos like gmial.com are fixed; role and throwaway inboxes are refused.
phonestringPhone number in any common format; stored in international format.
firstNamestringFirst name, used to greet them.
lastNamestringLast or family name.
citystringCity.
tagsstring[] | stringTags, as a list or comma-separated text.
fieldsobjectValues for the org's custom fields, by key.
sourcestringWhere they came from, e.g. website, qr, store. Default: api or dashboard.
consentobjectHow each channel's consent was given: signup_form, qr, checkout, in_store, whatsapp_optin or unknown (the default).

PATCH/api/v1/contacts/{id}·MCP tool: update_contact·Keys: full

Changes a contact's names, city, tags or custom field values. Send only what changes: tags replace the contact's tags, fields only change the keys given.

FieldTypeRequiredDescription
idstringyesThe resource's ID.
firstNamestring | nullFirst name; null removes it.
lastNamestring | nullLast name; null removes it.
citystring | nullCity; null removes it.
tagsstring[] | stringThe contact's tags, replacing the current ones.
fieldsobjectCustom field values by key; null removes one.

PATCH/api/v1/contacts/{id}/channels/{channel}·MCP tool: update_contact_channel·Keys: full

Unsubscribes a contact from a channel, or makes it active again. Making it active needs consent: true, your word that the person asked for it; bounced and complained channels never become active.

FieldTypeRequiredDescription
idstringyesThe resource's ID.
channel"email" | "whatsapp"yesemail or whatsapp.
status"active" | "unsubscribed"yesactive or unsubscribed.
consentbooleanRequired (true) to make a channel active: the person asked for it.

POST/api/v1/contacts/tag·MCP tool: tag_contacts·Keys: full

Adds tags to contacts, keeping the tags they already have. Segments can then pick them by tag.

FieldTypeRequiredDescription
idsstring[]yesContact IDs, 1,000 at most.
tagsstring[]yesTags to add, 10 at most.

POST/api/v1/contacts/untag·MCP tool: untag_contacts·Keys: full

Removes tags from contacts; their other tags stay as they are.

FieldTypeRequiredDescription
idsstring[]yesContact IDs, 1,000 at most.
tagsstring[]yesTags to remove, 10 at most.

Delete contacts

can't be undone

POST/api/v1/contacts/delete·MCP tool: delete_contacts·Keys: full

Deletes contacts with their details and history. It can't be undone. Opted-out, bounced and complained addresses stay blocked, so they are never messaged again even if imported later.

FieldTypeRequiredDescription
idsstring[]yesContact IDs, 1,000 at most.

List tags

read only

GET/api/v1/contacts/tags·MCP tool: list_contact_tags·Keys: full, read

Lists the tags the organization's contacts have, most used first, with how many contacts have each (200 at most).

No input.

GET/api/v1/contacts/fields·MCP tool: list_contact_fields·Keys: full, read

Lists the organization's custom contact fields, like last visit or birthday, with their key and type.

No input.

POST/api/v1/contacts/fields·MCP tool: create_contact_field·Keys: full

Adds a custom field contacts can hold, like last_visit or birthday, 20 at most. Dates are stored as YYYY-MM-DD.

FieldTypeRequiredDescription
keystringyesThe field's key, e.g. last_visit.
labelstringyesThe field's name as owners see it, e.g. آخر زيارة.
type"text" | "number" | "date"yestext, number or date.

Delete a custom field

can't be undone

DELETE/api/v1/contacts/fields/{key}·MCP tool: delete_contact_field·Keys: full

Deletes a custom field definition. It can't be undone, though contacts keep their values hidden until a field with the same key is added again.

FieldTypeRequiredDescription
keystringyesThe field's key.

Imports

POST/api/v1/contacts/import·MCP tool: import_contacts·Keys: full

Imports up to 1,000 rows per call. Phones are fixed to international format, email typos are corrected, and invalid, role, throwaway, duplicate and previously opted-out addresses are left out and counted. Send the next chunk with the importId of the first; 250,000 rows per import at most.

FieldTypeRequiredDescription
rowsobject[]yesThe rows, 1,000 at most, with columns already mapped to fields.
consentobjectyesHow these people agreed to hear from you, per channel. unknown keeps them pending until they agree.
attestbooleanyesMust be true unless both channels are unknown: these people agreed to receive your messages; the list wasn't bought or scraped.
importIdstringThe importId the first chunk answered with, to add rows to the same import.
fileNamestringThe uploaded file's name, shown in the import history.
totalintegerWith the first chunk: rows in the whole file, so the import turns done once all arrive.
firstRowNumberintegerRow number of rows[0] in the file, for the left-out report. Default: continues from the import's count.

Get an import

read only

GET/api/v1/contacts/imports/{id}·MCP tool: get_import·Keys: full, read

An import's status and counts: rows processed, contacts added and updated, rows left out by reason, and typos corrected.

FieldTypeRequiredDescription
idstringyesThe resource's ID.

List imports

read only

GET/api/v1/contacts/imports·MCP tool: list_imports·Keys: full, read

The organization's latest imports, newest first, with their status and counts.

FieldTypeRequiredDescription
limitintegerHow many, 10 by default, 50 at most.

GET/api/v1/contacts/imports/{id}/left-out·MCP tool: list_left_out·Keys: full, read

The rows an import left out, with the reason and what the row had, in file order, 100 a page. Kept for 7 days after the import.

FieldTypeRequiredDescription
idstringyesThe resource's ID.
afterintegerReturn rows after this cursor (nextCursor of the previous page).
limitintegerRows per page, 100 by default, 500 at most.

Segments

POST/api/v1/segments/count·MCP tool: count_segment·Keys: full

Counts the contacts some conditions would pick, without saving anything. Try conditions here before create_segment.

FieldTypeRequiredDescription
match"all" | "any"all: contacts meeting every condition. any: at least one.
conditionsobject[]yesUp to 10 conditions. tag has/has_not; city/country/source is/is_not; added within_days/before_days (number); channel active/not_active (email|whatsapp); orders bought_within_days/not_bought_within_days (bought before, but not in the last N days)/count_at_least/spent_at_least (store currency), from a connected store; field.<key> is/contains/is_set/not_set/before/since (YYYY-MM-DD)/at_least/less_than.

List segments

read only

GET/api/v1/segments·MCP tool: list_segments·Keys: full, read

Lists the organization's saved segments, oldest first, with their conditions in plain Arabic and how many contacts each picks now.

No input.

Get a segment

read only

GET/api/v1/segments/{id}·MCP tool: get_segment·Keys: full, read

Gets one saved segment with its conditions and how many contacts it picks now.

FieldTypeRequiredDescription
idstringyesThe resource's ID.

POST/api/v1/segments·MCP tool: create_segment·Keys: full

Saves conditions that pick some contacts, like VIPs in Riyadh or customers not seen in 90 days. Contacts join and leave it as they meet the conditions.

FieldTypeRequiredDescription
namestringyesThe segment's name, e.g. عملاء الرياض المميزون.
match"all" | "any"all: contacts meeting every condition. any: at least one.
conditionsobject[]yesUp to 10 conditions. tag has/has_not; city/country/source is/is_not; added within_days/before_days (number); channel active/not_active (email|whatsapp); orders bought_within_days/not_bought_within_days (bought before, but not in the last N days)/count_at_least/spent_at_least (store currency), from a connected store; field.<key> is/contains/is_set/not_set/before/since (YYYY-MM-DD)/at_least/less_than.

PATCH/api/v1/segments/{id}·MCP tool: update_segment·Keys: full

Renames a segment or replaces its conditions. Send only what changes.

FieldTypeRequiredDescription
idstringyesThe resource's ID.
namestringThe segment's name.
match"all" | "any"all or any.
conditionsobject[]New conditions, replacing the old ones (same rules as create_segment).

Delete a segment

can't be undone

DELETE/api/v1/segments/{id}·MCP tool: delete_segment·Keys: full

Deletes a saved segment. Its contacts stay as they are. It can't be undone.

FieldTypeRequiredDescription
idstringyesThe resource's ID.

Campaigns

GET/api/v1/campaigns/templates·MCP tool: list_campaign_templates·Keys: full, read

Lists the ready-made Arabic campaigns (win-back, new product, offer, Ramadan, Eid, welcome, birthday, review request, blank) a new campaign or automation can start from.

No input.

GET/api/v1/campaigns/occasions·MCP tool: list_occasions·Keys: full, read

Lists the occasions in the next days that shops in the organization's country build campaigns around (Ramadan, the two Eids, national days, White Friday, salary day), with the days left, the template to start from and a tip in Arabic. remind is true when it's time to prepare. Hijri dates follow the Umm al-Qura calendar and can move a day.

FieldTypeRequiredDescription
daysintegerHow far ahead to look, 60 days by default.

GET/api/v1/campaigns·MCP tool: list_campaigns·Keys: full, read

Lists the organization's campaigns, newest first, with status and headline numbers (recipients, sent, opened, clicked).

FieldTypeRequiredDescription
limitintegerHow many, 50 by default.

GET/api/v1/campaigns/{id}·MCP tool: get_campaign·Keys: full, read

Gets a campaign with its content, who it would go to right now, its sender address and anything that blocks sending it.

FieldTypeRequiredDescription
idstringyesThe resource's ID.

POST/api/v1/campaigns·MCP tool: create_campaign·Keys: full

Creates a draft, from a template or blank. Nothing is sent. kind permission_request makes the one-time email that asks pending contacts for consent.

FieldTypeRequiredDescription
namestringInternal name. Default: the template's name.
templatestringA template key from list_campaign_templates. Default: blank.
kind"campaign" | "permission_request"campaign, or permission_request for pending contacts.
brandIdstringThe sending brand. Default: the organization's first brand.
channel"email" | "whatsapp"email (default) or whatsapp.
waTemplateIdstringwhatsapp: an approved template (list_whatsapp_templates).

PATCH/api/v1/campaigns/{id}·MCP tool: update_campaign·Keys: full

Changes a draft's name, subject, preview text, blocks, segment or brand. Send only what changes. With revision, the change is refused if someone else saved in between.

FieldTypeRequiredDescription
idstringyesThe resource's ID.
namestringInternal name.
subjectstringSubject line. Merge tags like {{first_name|عميلنا العزيز}} work.
previewTextstringText inbox apps show after the subject.
contentobjectThe blocks: heading (level 1-3), text (**bold** and line breaks), list (bullet or number items), quote, button (solid or outline), image (full, wide or half width), divider. Most take align: right, center or left; without it the brand's email design decides.
segmentIdstring | nullA segment to send to, or null for every subscribed contact.
brandIdstringThe sending brand.
waTemplateIdstring | nullwhatsapp: the approved template to send.
fallbackEmailbooleanwhatsapp: also email contacts who have no WhatsApp subscription (uses the subject and blocks).
revisionintegerThe revision you started from (get_campaign); refused with revision_conflict if it changed.

POST/api/v1/campaigns/{id}/duplicate·MCP tool: duplicate_campaign·Keys: full

Copies a campaign's subject, preview text, blocks, segment and brand into a new draft.

FieldTypeRequiredDescription
idstringyesThe resource's ID.

DELETE/api/v1/campaigns/{id}·MCP tool: delete_campaign·Keys: full

Deletes a draft. It can't be undone. Campaigns that were sent can't be deleted, so their reports stay.

FieldTypeRequiredDescription
idstringyesThe resource's ID.

GET/api/v1/campaigns/{id}/preview·MCP tool: preview_campaign·Keys: full, read

Renders a campaign as one contact would get it (or a sample reader), with merge tags filled in. Nothing is sent and links aren't tracked.

FieldTypeRequiredDescription
idstringyesThe resource's ID.
contactIdstringA contact to preview as. Default: a sample reader.

Send a test email

can't be undone

POST/api/v1/campaigns/{id}/test·MCP tool: send_test_email·Keys: full

Sends the campaign as saved, subject prefixed with [تجربة], to one address: yours by default. It counts toward the plan's emails. It can't be undone.

FieldTypeRequiredDescription
idstringyesThe resource's ID.
tostringThe address to send the test to. Default: the signed-in user's email (required for API keys).

Send a campaign now

can't be undone

POST/api/v1/campaigns/{id}/send·MCP tool: send_campaign·Keys: full

Sends a draft now to every contact subscribed by email (or its segment's), one email each. It can't be undone: emails can't be recalled. Sending a campaign that is already sending or sent answers conflict, so retrying never sends twice. The content is reviewed first (a hold pauses it with pause_reason content_review). Contacts never emailed before go out in steps (2%, then 5%, then the rest, 30 minutes apart) while bounces and complaints stay low. Contacts who ignored 6 campaigns over 180 days get a 'still want our emails?' message instead.

FieldTypeRequiredDescription
idstringyesThe resource's ID.

Schedule a campaign

can't be undone

POST/api/v1/campaigns/{id}/schedule·MCP tool: schedule_campaign·Keys: full

Schedules a draft to send by itself at a time, between 5 minutes and 60 days from now, after the same checks as send_campaign. At that time it is sent exactly as send_campaign would, with the scheduler's permissions checked again. Once it goes out it can't be undone; until then unschedule_campaign cancels it. Give at with a time zone, or use get_best_send_time for when this organization's customers open most.

FieldTypeRequiredDescription
idstringyesThe resource's ID.
atstringyesWhen to send, ISO 8601 with an offset, e.g. 2026-11-12T20:00:00+03:00.

POST/api/v1/campaigns/{id}/unschedule·MCP tool: unschedule_campaign·Keys: full

Cancels a scheduled send: the campaign goes back to being a draft you can edit, send or schedule again.

FieldTypeRequiredDescription
idstringyesThe resource's ID.

GET/api/v1/campaigns/best-time·MCP tool: get_best_send_time·Keys: full, read

When this organization's customers open most, from people's opens and WhatsApp reads in the last 90 days, by weekday and hour in the organization's time zone. best is null until there are 30 opens to go on.

No input.

POST/api/v1/campaigns/{id}/pause·MCP tool: pause_campaign·Keys: full

Stops a campaign that is sending. Emails already sent stay sent; the rest wait for resume_campaign.

FieldTypeRequiredDescription
idstringyesThe resource's ID.

POST/api/v1/campaigns/{id}/resume·MCP tool: resume_campaign·Keys: full

Sends the rest of a paused campaign. It can't be undone: emails can't be recalled once they go out. Pauses for content review or high bounce/complaint rates can't be resumed this way; they answer conflict.

FieldTypeRequiredDescription
idstringyesThe resource's ID.

POST/api/v1/campaigns/{id}/cancel·MCP tool: cancel_campaign·Keys: full

Stops a sending or paused campaign for good: emails not sent yet are dropped. It can't be undone; emails already sent stay sent.

FieldTypeRequiredDescription
idstringyesThe resource's ID.

GET/api/v1/campaigns/{id}/tips·MCP tool: get_campaign_tips·Keys: full, read

Checks a campaign's content before sending: what would hold it for review (phishing-like requests, prohibited offers, risky links) and tips that help it reach the inbox, in Arabic. Nothing is sent.

FieldTypeRequiredDescription
idstringyesThe resource's ID.

GET/api/v1/campaigns/{id}/checklist·MCP tool: get_campaign_checklist·Keys: full, read

The review before sending a draft, as a checklist: what blocks sending (fail), what is worth fixing (warn: the preview text, no test sent since the last change, content our team would review, inbox tips) and what already passes (subject length, unsubscribe link, size, postal address, sender). Each item says where to fix it. Nothing is sent.

FieldTypeRequiredDescription
idstringyesThe resource's ID.

GET/api/v1/campaigns/{id}/report·MCP tool: get_campaign_report·Keys: full, read

Results of a campaign: recipients, sent, delivered, waiting (later steps or tomorrow's Gmail allowance), failed, skipped, opens (people only; Apple Mail Privacy Protection and scanners counted apart), clicks, bounces, complaints, unsubscribes, consent confirmations, rates, clicks per link, and, with a connected store, the paid orders it brought (bought within 7 days of clicking, or 3 days of opening).

FieldTypeRequiredDescription
idstringyesThe resource's ID.

Automations

GET/api/v1/automations·MCP tool: list_automations·Keys: full, read

Lists the four automations an organization can run (welcome, birthday, win_back, post_purchase), which exist, their state, what they sent, opened, clicked and the revenue they brought, and what stops a draft from starting.

No input.

GET/api/v1/automations/{id}·MCP tool: get_automation·Keys: full, read

Gets one automation with its numbers, what stops it from starting, and the latest 20 people who entered it with what happened to their message.

FieldTypeRequiredDescription
idstringyesThe resource's ID.

POST/api/v1/automations·MCP tool: create_automation·Keys: full

Creates one of the four automations as a draft, with a ready Arabic message from its template; nothing is sent until start_automation. channel whatsapp needs an approved WhatsApp template (waTemplateId); fallbackEmail also emails those without WhatsApp. Edit the message with update_campaign on its campaignId.

FieldTypeRequiredDescription
type"welcome" | "birthday" | "win_back" | "post_purchase"yeswelcome, birthday, win_back or post_purchase.
channel"email" | "whatsapp"email (default) or whatsapp.
waTemplateIdstringFor channel whatsapp: an approved WhatsApp template.
fallbackEmailbooleanFor channel whatsapp: email contacts who aren't on WhatsApp.
brandIdstringThe brand it sends as; the first brand by default.
settingsobjectPer type: welcome {delayMinutes}; birthday {field}; win_back {days, basis orders|field, field}; post_purchase {days}; any {segmentId}.

PATCH/api/v1/automations/{id}·MCP tool: update_automation·Keys: full

Changes when an automation sends (its settings) or its name. Send only what changes; settings are merged with the current ones. Its message is edited with update_campaign while it's a draft or paused.

FieldTypeRequiredDescription
idstringyesThe resource's ID.
namestringA name for the dashboard.
settingsobjectThe settings to change, e.g. { days: 90 } for win_back.

Start an automation

can't be undone

POST/api/v1/automations/{id}/start·MCP tool: start_automation·Keys: full

Turns an automation on: its message is reviewed, then everyone who qualifies from now on gets it automatically, once per occurrence. Messages it sends can't be undone. People are only counted from the moment it starts (a welcome greets new contacts, not old ones); birthday, win_back and post_purchase send between 10:00 and 20:00 in the organization's time zone, and nobody gets more than one marketing message in 20 hours.

FieldTypeRequiredDescription
idstringyesThe resource's ID.

POST/api/v1/automations/{id}/pause·MCP tool: pause_automation·Keys: full

Turns an automation off: nobody new enters it, and its messages not sent yet are dropped (they are time-bound, like a welcome or a birthday). While paused, its message can be edited; start_automation turns it back on.

FieldTypeRequiredDescription
idstringyesThe resource's ID.

Delete an automation

can't be undone

DELETE/api/v1/automations/{id}·MCP tool: delete_automation·Keys: full

Deletes an automation that never sent anything, with its message. It can't be undone. One that sent stays, so its report and revenue stay: pause it instead.

FieldTypeRequiredDescription
idstringyesThe resource's ID.

WhatsApp

GET/api/v1/whatsapp·MCP tool: get_whatsapp·Keys: full, read

Shows the connected WhatsApp number (quality rating, Meta's daily messaging limit), which way of connecting is available here, and the quiet hours for marketing messages.

No input.

POST/api/v1/whatsapp/connect·MCP tool: connect_whatsapp·Keys: full

Connects the business's own WhatsApp number. In production use method embedded_signup with the code, WABA id and phone number id from Meta's Embedded Signup, or manual with an existing token. Locally, method test connects a simulated number.

FieldTypeRequiredDescription
method"test" | "embedded_signup" | "manual"yesHow to connect.
phonestringtest only: the number to simulate, in international format.
codestringembedded_signup: the code from FB.login (valid for 30 seconds).
wabaIdstringThe WhatsApp Business Account id.
phoneNumberIdstringThe phone number id.
accessTokenstringmanual: a business or system-user token with WhatsApp permissions. Stored encrypted.
coexistencebooleanThe number stays on the WhatsApp Business app too (skips registration).

Disconnect WhatsApp

can't be undone

POST/api/v1/whatsapp/disconnect·MCP tool: disconnect_whatsapp·Keys: full

Disconnects the WhatsApp number from Ishtaqna and forgets its token; WhatsApp campaigns that are sending pause. It can't be undone: connecting again means submitting the templates again.

No input.

POST/api/v1/whatsapp/sync·MCP tool: sync_whatsapp·Keys: full

Reads the number's current quality rating and messaging limit, and each template's review status, from Meta. Webhooks do this automatically; this is for checking now.

No input.

PATCH/api/v1/whatsapp/settings·MCP tool: update_whatsapp_settings·Keys: full

Sets the hours, in each recipient's local time, when marketing messages wait instead of going out. Default 22:00 to 09:00. Equal hours turn quiet hours off.

FieldTypeRequiredDescription
quietHoursobjectyesWhen marketing waits.

GET/api/v1/whatsapp/library·MCP tool: list_whatsapp_library·Keys: full, read

Lists Ishtaqna's ready Arabic WhatsApp templates (win-back, new product, offer, Ramadan, Eid, welcome, birthday, review request) that can be submitted to Meta with create_whatsapp_template.

No input.

GET/api/v1/whatsapp/templates·MCP tool: list_whatsapp_templates·Keys: full, read

Lists the templates submitted for the connected number, with their review status at Meta and a preview as a sample customer reads them.

No input.

POST/api/v1/whatsapp/templates·MCP tool: create_whatsapp_template·Keys: full

Checks a template against Meta's format rules and our content rules, then submits it to Meta for review. Write with merge tags ({{first_name|عميلنا العزيز}}, {{city}}, {{brand_name}}) or start from a library key. Marketing templates get an opt-out button. Approval arrives by webhook, usually within minutes.

FieldTypeRequiredDescription
libraryKeystringStart from a ready template (list_whatsapp_library).
brandIdstringThe brand whose name is written into the template. Default: the first brand.
category"MARKETING" | "UTILITY"MARKETING (default) for offers and news; UTILITY only for transactional updates.
bodystringThe message text, up to 1024 characters after merge tags. Default: the library text.
footerTextstring | nullSmall grey line under the message, up to 60 characters.
buttonTextstring | nullLabel of the link button, up to 25 characters.
buttonUrlstring | nullWhere the button goes (https). Clicks are tracked.
optOutbooleanAdd the «إيقاف العروض» opt-out button (default true for marketing).

DELETE/api/v1/whatsapp/templates/{id}·MCP tool: delete_whatsapp_template·Keys: full

Deletes a template here and at Meta. It can't be undone; campaigns that use it can no longer send.

FieldTypeRequiredDescription
idstringyesThe resource's ID.

GET/api/v1/whatsapp/replies·MCP tool: list_whatsapp_replies·Keys: full, read

Lists the latest messages customers sent to the connected number, newest first, marking the ones that were opt-outs. Answer them from the WhatsApp app or WhatsApp Manager.

FieldTypeRequiredDescription
limitintegerHow many (default 50).

Stores

GET/api/v1/stores·MCP tool: get_stores·Keys: full, read

Shows the online stores connected to the organization (Salla, Zid), how many customers and orders came from each, when they last synced, which platforms can be connected here, and the revenue campaigns brought in the last 30 days.

No input.

POST/api/v1/stores/connect·MCP tool: connect_store·Keys: full

Connects a Salla or Zid store: answers authorizeUrl, where the owner installs or approves Ishtaqna for their store; afterwards the store shows on /app/stores to link (claim_store), and its customers and orders are imported. Locally, platform test connects a simulated store at once. Customers are imported with the consent basis you choose per channel; unknown keeps them pending until they agree.

FieldTypeRequiredDescription
platform"salla" | "zid" | "test"yessalla, zid, or test (development only).
consentobjectyesWhy this store's customers may get marketing, per channel. unknown keeps them pending until they agree.
attestbooleanRequired (true) when a consent basis isn't unknown: these customers agreed to receive offers.
brandIdstringThe brand this store belongs to; the first brand by default.

POST/api/v1/stores/claim·MCP tool: claim_store·Keys: full

Links a store that installed or approved Ishtaqna to this organization, then imports its customers and orders. The dashboard offers the installId to the signed-in owner whose email matches the store account's, or who just came back from approving; it works once, for 7 days.

FieldTypeRequiredDescription
installIdstringyesFrom the link opened after installing Ishtaqna in the store.
consentobjectyesWhy this store's customers may get marketing, per channel. unknown keeps them pending until they agree.
attestbooleanRequired (true) when a consent basis isn't unknown.

POST/api/v1/stores/{id}/sync·MCP tool: sync_store·Keys: full

Reads the store's orders since the last sync now (and every customer, the first time). Webhooks bring new orders and customers automatically; this catches anything missed.

FieldTypeRequiredDescription
idstringyesThe resource's ID.

POST/api/v1/stores/{id}/disconnect·MCP tool: disconnect_store·Keys: full

Disconnects a store and forgets its tokens: new orders stop arriving. Contacts and past orders stay, so reports keep their revenue. Connecting again means approving Ishtaqna in the store again.

FieldTypeRequiredDescription
idstringyesThe resource's ID.

GET/api/v1/stores/orders·MCP tool: list_store_orders·Keys: full, read

Lists the orders from connected stores, newest first, 50 a page, with the customer and the campaign credited for each. Filter by campaignId to see what one campaign brought. Pass nextCursor back as cursor for the next page.

FieldTypeRequiredDescription
campaignIdstringOnly orders credited to this campaign.
limitintegerHow many, 50 by default.
cursorintegernextCursor from the previous page.

GET/api/v1/stores/revenue·MCP tool: get_revenue·Keys: full, read

For the last N days: the connected store's paid orders, and the part campaigns brought (bought within 7 days of clicking, or 3 days of opening or reading), with the top campaigns. Null until a store is connected.

FieldTypeRequiredDescription
daysintegerThe period, 30 days by default.

API keys

List API keys

read only

GET/api/v1/api-keys·MCP tool: list_api_keys·Keys: full, read

Lists the organization's API keys, revoked ones included, newest first. The keys themselves are never shown again.

No input.

POST/api/v1/api-keys·MCP tool: create_api_key·Keys: full

Creates an API key and returns it once; only its hash is stored. full can do everything, read can only read, and signups can only add contacts.

FieldTypeRequiredDescription
namestringyesWhat the key is for, e.g. Website signups.
level"full" | "signups" | "read"yesfull, read or signups.

Revoke an API key

can't be undone

DELETE/api/v1/api-keys/{id}·MCP tool: revoke_api_key·Keys: full

Revokes an API key for good: requests with it answer unauthorized from now on. It can't be undone.

FieldTypeRequiredDescription
idstringyesThe resource's ID.

For machines

← Home