Graine AI
API referenceCampaigns

Create a campaign

Create a campaign — and, with contacts, start dialling in the same call.

Create a campaign — and, with contacts, start dialling in the same call.

This is the single endpoint to drive from an external system. One request creates the campaign, loads the contacts, applies transliteration and starts dialling. Nothing else has to be called: the response carries batch_id and total_contacts alongside the campaign.

Contacts arrive one of two ways, dispatched on Content-Type:

application/json — send agent_id, name and a contacts array. Each contact carries to_number (or the alias phone_number) and an optional variables object of prompt variables for that recipient.

multipart/form-data — send name, agent_id and file, a UTF-8 CSV with one header row. The phone column is the first header named to_number, phone_number, phone, mobile, number or msisdn (case-insensitive). Every other column becomes a per-contact variable under its header name, and a column named name is also mirrored to callee_name — the key the agent's prompt and the dashboard read. The structured settings (working_hours, retry_policy, follow_up_policy, transliteration, default_call_variables, metadata) travel as JSON object strings in their own form fields, because multipart has no types.

Transliteration rewrites uploaded columns into the script the agent speaks — a list of Latin-script names read by a Hindi agent, say. It is set once here and every later upload, retry and follow-up on this campaign inherits it.

Limits. At most 50,000 contacts per request and 20MB per CSV. Every number must contain 7 to 15 digits; bad rows come back together in one 422 naming up to 20 line numbers (the header is line 1), so a file is fixed in one pass.

Omit contacts entirely and nothing dials: you get the policy on its own. Send another request with the same settings when the next list is ready.

A campaign holds the agent, the caller IDs, the timezone, the working hours, the retry policy and the follow-up policy.

Two settings are worth getting right first time:

  • timezone and working_hours decide when dialling is legal. With working_hours_enforced: true, a day you leave out is a day nothing dials, and a retry that lands outside the window is pushed to the next open one rather than placed at 4am.
  • retry_policy.max_retries counts attempts AFTER the first, per contact.

The agent is verified before anything is written: a mistyped agent_id is a 404 here rather than a campaign that fails all 3,000 of its contacts at dispatch time.

Rate limited against the call-creation bucket, at 600 requests per minute.

Rate limit: 600 requests per minute per organization (bucket calls.create). Exceeding it returns 429 with Retry-After; the X-RateLimit-* response headers report your remaining allowance on every call.

POST
/v2/campaigns
/v2/campaigns

The Authorization access token

Authorization

Authorization
Required
Bearer <token>

Your Graine API key. Create one in the dashboard under Developers, or via POST /v2/api-keys. Send it as Authorization: Bearer <key>.

In: header

Request Body

application/jsonRequired

name
Required
Name

Human label, shown wherever the campaign appears.

Minimum length: 1Maximum length: 200

contactsContacts

Contacts to start dialling immediately. Supply these and this ONE call creates the campaign, creates its first batch and begins dialling — there is and nothing else needs calling afterwards. Omit them to create the policy now and add contacts later.

Each entry needs phone_number in E.164; any other keys become that contact's call variables, so {name} in the agent's prompt is filled per person.

agent_id
Required
Agent Id

Agent that places every call. Must exist in your organisation.

Minimum length: 1

phone_numbersPhone Numbers

Caller IDs to dial from. Each must be registered to your organisation. OMIT to use every number registered to your organisation — which is what an external integration usually wants, since it would otherwise have to know your number inventory to place a single call.

phone_number_strategyPhone Number Strategy

round_robin | random | least_loaded.

Default: "round_robin"

timezoneTimezone

IANA timezone the working hours and the campaign window are evaluated in.

Default: "UTC"

working_hours_enforcedWorking Hours Enforced

Set true to confine dialling to working_hours. False dials whenever the window is open.

Default: false

working_hoursWorking Hours

Keyed by lowercase day name: monday … sunday. Days you omit are closed when enforcement is on.

max_concurrent_callsMax Concurrent Calls

Ceiling on this campaign's SIMULTANEOUS calls. Omit for no campaign ceiling. It can only LOWER what the organisation's limit and the fair share across active campaigns already allow — raising it past the org limit has no effect.

Minimum: 1Maximum: 10000

retry_policyRetry Policy

Defaults to 3 retries, fixed 15-minute delay, working hours respected.

follow_up_policyFollow Up Policy

Defaults to disabled.

transliterationTransliteration

Defaults to disabled.

start_timeStart Time

ISO-8601 with a timezone offset. Nothing dials before it. Omit to allow dialling immediately.

end_timeEnd Time

ISO-8601 with a timezone offset. Nothing dials after it and the campaign expires.

default_call_variablesDefault Call Variables

Prompt variables for every call, overridable per contact.

default_call_contextDefault Call Context

Free-form context for every call.

metadataMetadata

Anything you want stored alongside.

Query Parameters

organization_idOrganization Id

Must match the organisation the API key belongs to.

Response Body

201

The created campaign, at status 'created'.

batch_idBatch Id | null

The batch created from contacts on this request, if any were supplied. Null on every other read — a campaign can hold many batches; list them with GET /v2/campaigns/{campaign_id}/batches.

total_contactsTotal Contacts

Contacts across every batch.

Default: 0

campaign_id
Required
Campaign Id

Campaign identifier.

organization_idOrganization Id | null

The organisation this campaign belongs to. Always your own.

name
Required
Name | null

Human label for the campaign.

agent_id
Required
Agent Id | null

Agent that places every call.

phone_numbersPhone Numbers

Caller IDs this campaign dials from.

@minItems 0

@minItems 0

@minItems 0

@minItems 0

@minItems 0

phone_number_strategyPhone Number Strategy

round_robin | random | least_loaded.

Default: "round_robin"

phone_number_indexPhone Number Index

Round-robin cursor: the position the next call starts from.

Default: 0

timezoneTimezone

IANA timezone the working hours and the window are evaluated in.

Default: "UTC"

working_hours_enforcedWorking Hours Enforced

False dials around the clock, subject only to start_time/end_time.

Default: false

working_hoursWorking Hours

Per-day dialling windows, keyed by lowercase day name.

max_concurrent_callsMax Concurrent Calls | null

Ceiling on this campaign's SIMULTANEOUS calls. Omit for no campaign ceiling. It can only LOWER what the organisation's limit and the fair share across active campaigns already allow — raising it past the org limit has no effect.

retry_policy
Required
Retry Policy

What happens after a busy or a no-answer.

follow_up_policy
Required
Follow Up Policy

Callbacks after the first cycle ends.

transliteration
Required
Transliteration

Name-script rewriting inherited by every batch.

start_timeStart Time | null

ISO-8601 UTC. Nothing dials before this.

end_timeEnd Time | null

ISO-8601 UTC. Nothing dials after this; the campaign then expires.

default_call_variablesDefault Call Variables

Prompt variables applied to every call, overridable per contact.

default_call_contextDefault Call Context

Free-form context applied to every call.

status
Required
Status

created | active | paused | completed | cancelled | expired | archived. A campaign is born 'created' and becomes 'active' when its first batch dials. 'archived' is what DELETE leaves behind; 'cancelled' is what the cancel action does.

metadataMetadata

Whatever you stored on the campaign.

total_batchesTotal Batches

Batches created under this campaign.

Default: 0

active_batchesActive Batches

Batches not yet finished.

Default: 0

completed_contactsCompleted Contacts

Contacts that reached a completed call, counted ONCE regardless of how many attempts it took. Use this for progress, not completed_calls.

Default: 0

completed_callsCompleted Calls

Calls that completed, retries included.

Default: 0

failed_callsFailed Calls

Calls that failed.

Default: 0

busy_callsBusy Calls

Calls that hit a busy line.

Default: 0

no_answer_callsNo Answer Calls

Calls that rang out.

Default: 0

voicemail_callsVoicemail Calls

Calls answered by a voicemail box. A SUBSET of no_answer_calls, never a peer of it — do not add it to the others to form a total.

Default: 0

in_progress_callsIn Progress Calls

Calls live right now.

Default: 0

follow_up_callsFollow Up Calls

Follow-up calls placed.

Default: 0

follow_up_exhaustedFollow Up Exhausted

Contacts whose follow-up attempts ran out.

Default: 0

created_atCreated At | null

ISO-8601 UTC.

updated_atUpdated At | null

ISO-8601 UTC.

400

1000 — malformed or contradictory input: an unknown timezone, a working-hours window that is not 24-hour HH:MM, an end_time at or before start_time, or an empty patch body. 1002 — a required parameter is missing.

error
Required
Error

Stable integer code from the /v2 error table. Branch on this.

message
Required
Message

One human-readable sentence. Wording may change; the code will not.

401

1100 — missing, unknown or inactive API key. 1101 — a browser session token was presented.

error
Required
Error

Stable integer code from the /v2 error table. Branch on this.

message
Required
Message

One human-readable sentence. Wording may change; the code will not.

403

1102 — the key is valid but lacks the scope this endpoint requires, or names another organisation. GET /v2/scopes reports which scopes the key holds.

error
Required
Error

Stable integer code from the /v2 error table. Branch on this.

message
Required
Message

One human-readable sentence. Wording may change; the code will not.

404

1200 — no such campaign, or no such agent, in this organisation. A campaign belonging to another organisation answers 404, never 403, so ids cannot be probed.

error
Required
Error

Stable integer code from the /v2 error table. Branch on this.

message
Required
Message

One human-readable sentence. Wording may change; the code will not.

422

1001 — the body or query failed validation; the message names the first offending field.

error
Required
integer

Stable integer code from the error table. Branch on this, not on the message.

message
Required
string

One human-readable sentence. Wording may change; the code will not.

429

1300 — per-organisation rate limit exceeded. Carries Retry-After.

error
Required
Error

Stable integer code from the /v2 error table. Branch on this.

message
Required
Message

One human-readable sentence. Wording may change; the code will not.

503

1501 — the agent service is unreachable or returned a 5xx.

error
Required
Error

Stable integer code from the /v2 error table. Branch on this.

message
Required
Message

One human-readable sentence. Wording may change; the code will not.

504

1502 — the agent service did not respond in time.

error
Required
Error

Stable integer code from the /v2 error table. Branch on this.

message
Required
Message

One human-readable sentence. Wording may change; the code will not.

curl -X POST "https://api.graine.ai/v2/campaigns?organization_id=string" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Slot-2-Uttar-Pradesh",
    "contacts": [
      {
        "phone_number": "+919812345678",
        "callee_name": "Raju"
      },
      {
        "phone_number": "+919812345679",
        "callee_name": "Rohit Verma"
      }
    ],
    "agent_id": "6c6f4cc3-f56c-479d-8422-7f76694daa29",
    "phone_numbers": [
      "+917971442184"
    ],
    "phone_number_strategy": "round_robin",
    "timezone": "Asia/Kolkata",
    "working_hours_enforced": true,
    "working_hours": {
      "monday": {
        "start": "08:00",
        "end": "20:00",
        "enabled": true
      }
    },
    "max_concurrent_calls": 10,
    "start_time": "2027-03-02T11:00:00+05:30",
    "end_time": "2027-03-09T16:30:00+05:30",
    "default_call_variables": {
      "campaign_offer": "monsoon-renewal"
    },
    "default_call_context": {},
    "metadata": {
      "region": "uttar-pradesh",
      "slot": "2"
    }
  }'

The created campaign, at status 'created'.

{
  "batch_id": "fa08ae9d-239f-4a99-8d76-d4dec51311b4",
  "total_contacts": 3000,
  "campaign_id": "31ff3ea9-8bb5-433f-868f-286ac3c31328",
  "organization_id": "string",
  "name": "Slot-2-Uttar-Pradesh",
  "agent_id": "6c6f4cc3-f56c-479d-8422-7f76694daa29",
  "phone_numbers": [
    "+917971442184"
  ],
  "phone_number_strategy": "round_robin",
  "phone_number_index": 0,
  "timezone": "Asia/Kolkata",
  "working_hours_enforced": true,
  "working_hours": {
    "monday": {
      "start": "08:00",
      "end": "20:00",
      "enabled": true
    },
    "saturday": {
      "start": "10:00",
      "end": "16:00",
      "enabled": true
    },
    "sunday": {
      "start": "10:00",
      "end": "16:00",
      "enabled": false
    }
  },
  "max_concurrent_calls": 10,
  "retry_policy": {
    "max_retries": 5,
    "strategy": "fixed_delay",
    "cooldown_minutes": 45,
    "base_delay_minutes": 15,
    "max_delay_minutes": 240,
    "respect_working_hours": true
  },
  "follow_up_policy": {
    "enabled": false,
    "max_attempts": 2,
    "cooldown_minutes": 1440,
    "triggers": [
      "after_retries_exhausted"
    ],
    "next_working_day_only": true,
    "follow_up_agent_id": "6c6f4cc3-f56c-479d-8422-7f76694daa29",
    "follow_up_prompt": "You spoke to this person yesterday. Open by referring to that call.",
    "follow_up_extra_logic": {}
  },
  "transliteration": {
    "enabled": true,
    "fields": [
      "callee_name"
    ],
    "target_language_code": "hi-IN",
    "source_language_code": "en-IN",
    "keep_original": true
  },
  "start_time": "2026-08-27T05:30:00.000Z",
  "end_time": "2026-08-31T11:00:00.000Z",
  "default_call_variables": {
    "campaign_offer": "monsoon-renewal"
  },
  "default_call_context": {},
  "status": "paused",
  "metadata": {
    "region": "uttar-pradesh",
    "slot": "2"
  },
  "total_batches": 1,
  "active_batches": 1,
  "completed_contacts": 412,
  "completed_calls": 451,
  "failed_calls": 88,
  "busy_calls": 37,
  "no_answer_calls": 120,
  "voicemail_calls": 14,
  "in_progress_calls": 3,
  "follow_up_calls": 0,
  "follow_up_exhausted": 0,
  "created_at": "2026-08-26T10:48:18.239Z",
  "updated_at": "2026-08-26T10:51:46.003Z"
}