Skip to content
The Public API is available on request. Email support@grona.ai to get access.

List campaigns

GET
/v1/campaigns
curl --request GET \
--url https://api.grona.ai/v1/campaigns \
--header 'Authorization: Bearer <token>'

Returns every campaign in the workspace. v1 has no pagination: the whole list comes back in one response, and meta.count is the number of campaigns returned. The field is reserved so pagination can be added later without breaking clients.

The order of the list is not specified. Sort on your side if you need an order. Variations are listed without editor links; request one with create-edit-link.

OK.

Media typeapplication/json
object
data
required
Array
object
id
required

Grona’s campaign id.

string
externalId
required
One of:

Your own id for a campaign: 1 to 128 letters, digits, _, ., : or -. Unique per workspace. A UUID, an ObjectId or a plain number are all valid.

string
/^[A-Za-z0-9_.:-]{1,128}$/
name
required
string
status
required

live runs the campaign. paused stops it. New campaigns start paused.

string
Allowed values: live paused
domain
required

The website the campaign runs on, resolved from its url.

string | null
script
required

Whether the Grona snippet was found on the campaign’s website.

object
status
required

checking: a check is running. present: the snippet was found. absent: the snippet was not found, or Grona could not load the page. unknown: no check is running and none has completed. New campaigns read checking, not unknown.

string
Allowed values: checking present absent unknown
checkedAt
required
One of:

ISO 8601 in UTC, with millisecond precision.

string format: date-time
createdAt
required

ISO 8601 in UTC, with millisecond precision.

string format: date-time
updatedAt
required

ISO 8601 in UTC, with millisecond precision.

string format: date-time
type
required

general creates one variation. abTest creates two, Control and Test.

string
Allowed values: general abTest
pageTargeting
required

specific runs on the one page in url. multiple runs on every page matched by slug, condition and list.

string
Allowed values: specific multiple
segmentId
required
One of:

The name of a variable your page sets on window: letters, digits and _, not starting with a digit, at most 128 characters. Names the browser already defines, such as name, location, status, top, parent and history, are rejected.

string
/^[A-Za-z_][A-Za-z0-9_]{0,127}$/
segmentValue
required
One of:

The value the page variable must hold for a visitor to be eligible: 1 to 128 letters, digits, _, ., : or -. Compared as an exact string match.

string
/^[A-Za-z0-9_.:-]{1,128}$/
url

Only for multiple campaigns. The sample page, stored without its query string or fragment.

string format: uri
slug

Only for multiple campaigns. The sample page’s path segment whose position is compared.

string
condition

Only for multiple campaigns. Grona takes the path segment of each page at the same position as slug in the sample url. included matches pages whose segment there is in list. not_included matches pages whose segment there is not in list, which can be far more pages than you intend, so preview the variation on some of them before you set the campaign live. A page with no segment at that position never matches.

string
Allowed values: included not_included
list

Only for multiple campaigns. The segments compared against.

Array<string>
>= 1 items <= 50 items
trafficSplit

Keyed by variation id. On update, every variation must appear and the values must total 100.

object
key
additional properties
integer
<= 100
variations
required

In creation order, so Control comes first in an A/B test. No editor links.

Array<object>
object
id
required

The variation id. Use it for editor links and in trafficSplit on update.

string
name
required

Set by Grona. null when the campaign refers to a variation that no longer exists.

string | null
type
required

control when the variation’s name contains the word “control” in any letter case, variant otherwise. An A/B test reads control, variant. A variation whose name is null reads variant.

string
Allowed values: control variant
previewUrl
required

The campaign’s page with the variation’s saved changes applied, through the gronaPreview parameter. It does not expire. null only when the variation no longer exists.

string | null format: uri
meta
required
object
workspaceId
required

The workspace resolved from the secret key.

string
count
required

The number of campaigns returned. Reserved for pagination in a later release.

integer

Example

{
"data": [
{
"id": "671a3c90e4b0a1f2c3d40011",
"externalId": "CMP-1024",
"name": "Free delivery bar on the home page",
"status": "live",
"domain": "https://shop.example.com",
"script": {
"status": "present",
"checkedAt": "2026-10-20T09:30:19.884Z"
},
"createdAt": "2026-10-20T09:30:12.418Z",
"updatedAt": "2026-10-20T11:05:37.290Z",
"type": "general",
"pageTargeting": "specific",
"segmentId": "audience_id",
"segmentValue": "returning_buyer",
"variations": [
{
"id": "671a3c90e4b0a1f2c3d40012",
"name": "Free delivery bar on the home page - Variation",
"type": "variant",
"previewUrl": "https://shop.example.com/?gronaPreview=671a3c90e4b0a1f2c3d40012"
}
]
},
{
"id": "671a3d52e4b0a1f2c3d40021",
"externalId": null,
"name": "Breakfast banner on dairy and bakery pages",
"status": "paused",
"domain": "https://shop.example.com",
"script": {
"status": "present",
"checkedAt": "2026-10-20T10:02:51.016Z"
},
"createdAt": "2026-10-20T10:02:44.105Z",
"updatedAt": "2026-10-20T10:02:44.391Z",
"type": "abTest",
"pageTargeting": "multiple",
"segmentId": null,
"segmentValue": null,
"url": "https://shop.example.com/dairy/whole-milk",
"slug": "dairy",
"condition": "included",
"list": [
"dairy",
"bakery"
],
"trafficSplit": {
"671a3d52e4b0a1f2c3d40022": 60,
"671a3d52e4b0a1f2c3d40023": 40
},
"variations": [
{
"id": "671a3d52e4b0a1f2c3d40022",
"name": "Control",
"type": "control",
"previewUrl": "https://shop.example.com/dairy/whole-milk?gronaPreview=671a3d52e4b0a1f2c3d40022"
},
{
"id": "671a3d52e4b0a1f2c3d40023",
"name": "Test",
"type": "variant",
"previewUrl": "https://shop.example.com/dairy/whole-milk?gronaPreview=671a3d52e4b0a1f2c3d40023"
}
]
}
],
"meta": {
"workspaceId": "65e8a1c47d2f9b03e6a15c42",
"count": 2
}
}

The secret key is missing, malformed, unknown or revoked. error.code is unauthorized.

Media typeapplication/json
object
error
required
object
code
required

Stable and machine-readable. Branch on this, and on the HTTP status for codes you do not know.

string
Allowed values: invalid_request unauthorized not_found conflict internal_error
message
required

Human-readable. The wording can change, so do not parse it.

string

Example

{
"error": {
"code": "unauthorized",
"message": "Missing or invalid API key."
}
}

Too many requests. Retry with exponential backoff and jitter, and wait at least as long as Retry-After when it is present. Branch on the status code; the body is not guaranteed to use the error envelope.

Retry-After
string

How long to wait before retrying, in seconds or as an HTTP date.

Unexpected server error. error.code is internal_error. Retry with backoff; if it persists, contact support@grona.ai with the time of the request and the endpoint.

Media typeapplication/json
object
error
required
object
code
required

Stable and machine-readable. Branch on this, and on the HTTP status for codes you do not know.

string
Allowed values: invalid_request unauthorized not_found conflict internal_error
message
required

Human-readable. The wording can change, so do not parse it.

string

Example

{
"error": {
"code": "internal_error",
"message": "Something went wrong on our side."
}
}

Temporarily unavailable. Retry with exponential backoff and jitter, and wait at least as long as Retry-After when it is present. Branch on the status code; the body is not guaranteed to use the error envelope.

Retry-After
string

How long to wait before retrying, in seconds or as an HTTP date.