List campaigns
package main
import ( "fmt" "net/http" "io")
func main() {
url := "https://api.grona.ai/v1/campaigns"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close() body, _ := io.ReadAll(res.Body)
fmt.Println(res) fmt.Println(string(body))
}OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder() .url("https://api.grona.ai/v1/campaigns") .get() .addHeader("Authorization", "Bearer <token>") .build();
Response response = client.newCall(request).execute();const url = 'https://api.grona.ai/v1/campaigns';const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}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.
Authorizations
Section titled “Authorizations”Responses
Section titled “Responses”OK.
object
object
Grona’s campaign id.
live runs the campaign. paused stops it. New campaigns start paused.
The website the campaign runs on, resolved from its url.
Whether the Grona snippet was found on the campaign’s website.
object
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.
ISO 8601 in UTC, with millisecond precision.
ISO 8601 in UTC, with millisecond precision.
general creates one variation. abTest creates two, Control and Test.
specific runs on the one page in url. multiple runs on every page matched by
slug, condition and list.
Only for multiple campaigns. The sample page, stored without its query string or fragment.
Only for multiple campaigns. The sample page’s path segment whose position is compared.
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.
Only for multiple campaigns. The segments compared against.
Keyed by variation id. On update, every variation must appear and the values must total 100.
object
In creation order, so Control comes first in an A/B test. No editor links.
object
The variation id. Use it for editor links and in trafficSplit on update.
Set by Grona. null when the campaign refers to a variation that no longer exists.
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.
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.
object
The workspace resolved from the secret key.
The number of campaigns returned. Reserved for pagination in a later release.
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.
object
object
Stable and machine-readable. Branch on this, and on the HTTP status for codes you do not know.
Human-readable. The wording can change, so do not parse it.
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.
Headers
Section titled “Headers”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.
object
object
Stable and machine-readable. Branch on this, and on the HTTP status for codes you do not know.
Human-readable. The wording can change, so do not parse it.
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.
Headers
Section titled “Headers”How long to wait before retrying, in seconds or as an HTTP date.