Docs
grovs.io Start for free
Examples for

Server push

Send a push to your users, a list of devices or a segment, then read its delivery report. Campaigns built in the dashboard are covered in Push campaigns.

These endpoints live on the SDK host: https://sdk.sqd.link on grovs Cloud, https://sdk.<SERVER_HOST> when you self-host. Send your project key in the PROJECT-KEY header. Send production or test in the ENVIRONMENT header.

Availability: an active paid plan on grovs Cloud. Always on for self-hosted Enterprise. Segment audiences need ClickHouse.

Rate limit: each project has a budget per minute of 600 campaigns, 10 segment campaigns and 100,000 list recipients. A refused request creates nothing and uses no budget.

IDs: campaign and segment IDs are hashids, such as k9Qx2. link_id is the numeric link ID from Get link details.

The push campaign object

Attributes
idstring

Hashid of the campaign.

namestring

Internal label, up to 255 characters. Defaults to the title.

titlestring

Notification title, up to 255 characters.

bodystring

Notification text, up to 1000 characters. The whole APNs payload must fit in 4096 bytes, so a long body with a long image_url or data is refused with 422.

image_urlstring | null

An https:// image URL, up to 2048 characters. Use PNG, JPEG or GIF. iOS needs a Notification Service Extension in your app to show it.

imagestring | null

Read-only. The image the push carries.

soundstring | null

default or the name of a sound file bundled in your app, up to 64 characters.

link_idinteger | null

A grovs link of this project. Its URL travels in the payload as grovs_link.

dataobject

Custom keys delivered in the payload, at most 2048 bytes as JSON. grovs_campaign_id, grovs_link and grovs_image are overwritten. aps, from, notification, message_type and keys starting with google, gcm or __rpush_ are refused.

sourceenum

api for campaigns sent through this API.

statusenum

scheduled, evaluating, sending, sent, failed or cancelled.

audienceobject

type, recipients_count, segment_id, segment_definition_hash and recipients.

scheduled_atstring | null

When the campaign goes out, ISO 8601.

local_timeboolean

Deliver at the wall-clock time of scheduled_at in each device's own timezone.

timezonestring

IANA zone the wall-clock is read in, and the fallback for devices with no timezone. Defaults to UTC.

reportobject

Delivery counters. See below.

evaluated_atstring | null

When the audience was frozen.

started_atstring | null

When sending started.

finished_atstring | null

When every device had been handed off, or the campaign was cancelled.

failure_reasonstring | null

For a failed campaign: segment_busy, segment_invalid, query_too_heavy or evaluation_error.

created_atstring

ISO 8601.

updated_atstring

ISO 8601.

report
targetedinteger

Every resolved device, held people excluded.

no_channelinteger

Devices with no push token, or on web or desktop. Skipped.

not_foundinteger

Recipients that matched no visitor, malformed UUIDs included.

queuedinteger

Handed to the push process.

deliveredinteger

Accepted by APNs or FCM. Not the same as shown on screen.

failedinteger

Rejected or not sent. error_counts gives the reasons.

openedinteger

Devices whose SDK reported a tap, once per device.

cancelledinteger

Devices dropped by a cancel before they were handed off.

heldinteger

People kept back as a control. Always 0 for server pushes.

waitinginteger

People waiting for an A/B winner. Always 0 for server pushes.

error_countsobject

Failures by reason. APNs reasons keep their name (Unregistered, BadDeviceToken). FCM failures are keyed by HTTP status (404). capped counts pushes over the project's daily cap; no_app and no_token count devices that could not be sent to.

audienceobject

persons, devices and incomplete for a segment audience. incomplete is true when a segment window reached past your data retention.

Per-device rows are kept for 30 days. The counters stay.

The object also has A/B test and goal fields. They are null for server pushes.

JSON
The push campaign object
JSON
{
  "id": "k9Qx2",
  "name": "Flu shot reminder",
  "title": "Flu season is here",
  "body": "Book your flu shot in two taps.",
  "image_url": "https://cdn.medly.health/push/flu.png",
  "image": "https://cdn.medly.health/push/flu.png",
  "sound": "default",
  "link_id": 4821,
  "data": { "screen": "book", "service": "flu_shot" },
  "source": "api",
  "status": "sent",
  "scheduled_at": null,
  "local_time": false,
  "timezone": "UTC",
  "evaluated_at": "2026-10-22T09:12:05.000Z",
  "started_at": "2026-10-22T09:12:04.000Z",
  "finished_at": "2026-10-22T09:20:41.000Z",
  "failure_reason": null,
  "created_at": "2026-10-21T15:02:11.000Z",
  "updated_at": "2026-10-22T09:20:41.000Z",
  "control_group": null,
  "conversion": null,
  "variants": null,
  "test_share": null,
  "winner_after_minutes": null,
  "winner_by": null,
  "winner_due_at": null,
  "winner": null,
  "audience": {
    "type": "segment",
    "recipients_count": 0,
    "segment_id": "s9Kq1",
    "segment_definition_hash": "9c41e0b7…",
    "recipients": []
  },
  "report": {
    "targeted": 41230,
    "no_channel": 1204,
    "not_found": 0,
    "queued": 36020,
    "delivered": 35511,
    "failed": 509,
    "opened": 2210,
    "cancelled": 0,
    "held": 0,
    "waiting": 0,
    "error_counts": { "Unregistered": 474, "404": 35 },
    "audience": { "persons": 38120, "devices": 41230, "incomplete": false }
  }
}

Send a push from your server

POST/api/v1/sdk/server/push

Creates and sends a campaign in one call. Use it for transactional pushes such as "your test results are ready".

The campaign has source: "api". It takes no variants, goal or control group. The project-wide control group does not hold it back.

The response is 202 with status evaluating, or scheduled when you pass scheduled_at. A background job resolves the audience within a couple of seconds. Poll Retrieve a server push for progress.

AuthProject keyAuthentication ›
Headers
PROJECT-KEYstringRequired

Your project key.

ENVIRONMENTstringRequired

production or test.

Body
titlestringRequired

Up to 255 characters.

bodystringRequired

Up to 1000 characters.

audienceobjectRequired

Who gets the push. One of:

  • { "type": "identifiers", "recipients": [...] }: the user IDs your app set through the SDK.
  • { "type": "visitors", "recipients": [...] }: visitor UUIDs.
  • { "type": "segment", "segment_id": "…" }: an active segment.

Lists hold 1 to 10,000 non-empty strings of at most 256 bytes. An identifier reaches at most 50 of its devices.

namestring

Defaults to the title.

image_urlstring

https:// only. The server API takes no file upload.

soundstring

default or a bundled sound file name.

link_idinteger

A link of this project.

dataobject

Custom payload keys, at most 2048 bytes as JSON.

scheduled_atstring

ISO 8601, in the future and at most 30 days away.

local_timeboolean

Deliver at the wall-clock time of scheduled_at in each device's timezone. Needs scheduled_at.

timezonestring

IANA zone name. Defaults to UTC.

Returns 202 with a campaign object.

Errors

400Missing PROJECT-KEY, or ENVIRONMENT is not production or test.
403Invalid credentials, or enterprise_required: the plan does not include the server push API.
404Push campaigns are not available.
422Validation failed. Also Link not found, Segment not found, data must be an object.
429rate_limited: the per-minute budget is used up.
Request · cURL
curl -X POST "https://sdk.sqd.link/api/v1/sdk/server/push" \
  -H "PROJECT-KEY: $GROVS_PROJECT_KEY" \
  -H "ENVIRONMENT: production" \
  -H "Content-Type: application/json" \
  -d '{
  "title": "Your test results are ready",
  "body": "Tap to see them in Medly.",
  "link_id": 5120,
  "data": { "screen": "results", "result_id": "r_88213" },
  "audience": {
    "type": "identifiers",
    "recipients": ["member-4471"]
  }
}'
JSON
{
  "campaign": {
    "id": "pR4tz",
    "name": "Your test results are ready",
    "title": "Your test results are ready",
    "body": "Tap to see them in Medly.",
    "source": "api",
    "status": "evaluating",
    "link_id": 5120,
    "data": { "screen": "results", "result_id": "r_88213" },
    "audience": {
      "type": "identifiers",
      "recipients_count": 1,
      "segment_id": null,
      "segment_definition_hash": null,
      "recipients": ["member-4471"]
    },
    "report": { "targeted": 0, "queued": 0, "delivered": 0, "…": "…" },
    "…": "…"
  }
}

Retrieve a server push

GET/api/v1/sdk/server/push/{campaign_id}

Returns a campaign of the project with its live report.

AuthProject keyAuthentication ›
Headers
PROJECT-KEYstringRequired

Your project key.

ENVIRONMENTstringRequired

production or test.

Path
campaign_idstringRequired

The id from the send response.

Returns 200 with a campaign object and its live report.

Errors

400Missing PROJECT-KEY, or ENVIRONMENT is not production or test.
403Invalid credentials, or enterprise_required.
404No campaign with that ID in this environment.
Request · cURL
curl -X GET "https://sdk.sqd.link/api/v1/sdk/server/push/{campaign_id}" \
  -H "PROJECT-KEY: $GROVS_PROJECT_KEY" \
  -H "ENVIRONMENT: production"
JSON
{
  "campaign": {
    "id": "pR4tz",
    "status": "sent",
    "report": {
      "targeted": 2,
      "no_channel": 0,
      "not_found": 0,
      "queued": 2,
      "delivered": 2,
      "failed": 0,
      "opened": 1,
      "cancelled": 0,
      "held": 0,
      "waiting": 0,
      "error_counts": {},
      "audience": { "persons": null, "devices": null, "incomplete": false }
    },
    "…": "…"
  }
}
Was this page helpful?
Last updated 2026-10-26