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

Visual editor links

An editor link opens one variation of a campaign in the Grona visual editor, in your user’s browser. Use this page when you add an “Edit” button to your product: it covers how long a link lasts, how to get a fresh one, and how to keep links from leaking.

An editor link looks like this:

https://api.grona.ai/v1/editor/launch?token=eyJhbGciOi...
  • The token in the query string is what lets the editor open. Anyone who holds a link that has not expired can open the editor for that variation.
  • The token expires 15 minutes after it is issued. generatedAt and expiresAt in the response are read from the token itself, so they always agree with the link.
  • The link is opened in a browser. Your backend never calls it.
  • Grona does not promise that a link works only once. Treat it as usable by whoever holds it until expiresAt.

The page you edit must meet the visual editor requirements.

  1. Create. Creating a campaign returns an editUrl, generatedAt and expiresAt for every variation. An A/B test returns two links, one for Control and one for Test.

  2. Open. Send the link to the user’s browser while it is fresh. The user edits the variation and saves.

  3. Expire. 15 minutes after generatedAt, the link stops opening the editor. Changes the user already saved are kept.

  4. Refresh. Request a new link with POST /v1/variations/edit-link, or send the link you hold to POST /v1/variations/edit-link/validate and get back a link that works.

List, get and update do not return editor links. When you need one later, request it with the variation id.

Send a variation id from variations[].id on create, list or get:

Terminal window
curl https://api.grona.ai/v1/variations/edit-link \
-H "Authorization: Bearer $GRONA_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"variationId": "671a3c90e4b0a1f2c3d40012"}'
{
"data": {
"editUrl": "https://api.grona.ai/v1/editor/launch?token=eyJhbGciOi...",
"variationId": "671a3c90e4b0a1f2c3d40012",
"previewUrl": "https://shop.example.com/?gronaPreview=671a3c90e4b0a1f2c3d40012",
"generatedAt": "2026-10-20T11:12:05.614Z",
"expiresAt": "2026-10-20T11:27:05.614Z"
},
"meta": { "workspaceId": "65e8a1c47d2f9b03e6a15c42" }
}

generatedAt and expiresAt describe editUrl only. previewUrl does not expire.

If your product keeps a link for a few minutes, for example while a user decides whether to edit, send it to the validate endpoint before you open it. Send the whole link as Grona issued it, not the token inside it:

Terminal window
curl https://api.grona.ai/v1/variations/edit-link/validate \
-H "Authorization: Bearer $GRONA_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"editUrl": "https://api.grona.ai/v1/editor/launch?token=eyJhbGciOi..."}'

For any Grona editor link whose variation still exists, expired or not, the response is 200 and data.editUrl is the link to open:

  • Not expired: the same link comes back byte for byte, with its original generatedAt and expiresAt. valid is true and refreshed is false.
  • Expired: a new link comes back with a full 15-minute lifetime. valid is false and refreshed is true.
{
"data": {
"valid": false,
"refreshed": true,
"editUrl": "https://api.grona.ai/v1/editor/launch?token=eyJhbGciOi...",
"variationId": "671a3c90e4b0a1f2c3d40012",
"generatedAt": "2026-10-20T13:41:09.327Z",
"expiresAt": "2026-10-20T13:56:09.327Z"
},
"meta": { "workspaceId": "65e8a1c47d2f9b03e6a15c42" }
}

Open data.editUrl either way; you do not need to branch on valid. A returned link that was echoed back may have only seconds left, so open it straight away.

The safest pattern is not to hold links at all. Give your “Edit” button a route on your own backend that checks the user, requests a fresh link and redirects the browser to it. The link then exists for a moment and is never stored.

// server.mjs: an Express route that opens the editor (Node 18 or later).
import express from 'express';
import { findCampaignForUser } from './campaigns.mjs'; // your own database lookup
const app = express();
app.get('/campaigns/:campaignId/variations/:variationId/edit', async (req, res) => {
// 1. Check ownership in your own database. req.user is set by your login middleware.
// The campaign must belong to this user, and the variation must belong to the campaign.
const campaign = await findCampaignForUser(req.user, req.params.campaignId);
if (!campaign || !campaign.gronaVariationIds.includes(req.params.variationId)) {
return res.status(404).send('Not found.');
}
// 2. Only then ask Grona for a link to that variation.
const grona = await fetch('https://api.grona.ai/v1/variations/edit-link', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.GRONA_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ variationId: req.params.variationId }),
});
if (!grona.ok) return res.status(502).send('Could not open the editor.');
const { data } = await grona.json();
res.set('Cache-Control', 'no-store');
res.redirect(303, data.editUrl);
});
app.listen(3000);

Your route decides who may edit, so the ownership check in step 1 is not optional. Your secret key covers every campaign in your workspace, including those of all your own customers, and Grona checks only that the variation is in that workspace. It cannot tell your users apart. Without the check, a user who may edit one campaign could put another customer’s variation id in the URL and get a working editor link for it. Never send Grona a variation id taken from a request until you have matched it against the variation ids you saved, at create time, for a campaign the signed-in user owns.

Because the token travels in the query string, a link can end up anywhere a URL is recorded.

  • Open links straight away. Do not store them in your database, a cookie or local storage.
  • Keep them out of your logs, error reports and analytics tools. Redact the token parameter if a URL might be recorded.
  • Do not send them by email or chat, or put them in a support ticket.
  • Do not show them in your UI as text a user could copy and share.
  • Send them only to the user who asked to edit, over HTTPS.
Status When Fix
400 invalid_request variationId is missing or not a valid id, or the editUrl sent to validate is not a Grona editor link. Send a variation id from a Grona response, or the whole link exactly as issued.
404 not_found The variation does not exist in this workspace, for example because its campaign was deleted. Read the campaign again for its current variation ids.

See Errors for authentication errors and retries.