Check an editor link and refresh it if expired
package main
import ( "fmt" "strings" "net/http" "io")
func main() {
url := "https://api.grona.ai/v1/variations/edit-link/validate"
payload := strings.NewReader("{ \"editUrl\": \"https://api.grona.ai/v1/editor/launch?token=eyJhbGciOi...\" }")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>") req.Header.Add("Content-Type", "application/json")
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();
MediaType mediaType = MediaType.parse("application/json");RequestBody body = RequestBody.create(mediaType, "{ \"editUrl\": \"https://api.grona.ai/v1/editor/launch?token=eyJhbGciOi...\" }");Request request = new Request.Builder() .url("https://api.grona.ai/v1/variations/edit-link/validate") .post(body) .addHeader("Authorization", "Bearer <token>") .addHeader("Content-Type", "application/json") .build();
Response response = client.newCall(request).execute();const url = 'https://api.grona.ai/v1/variations/edit-link/validate';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"editUrl":"https://api.grona.ai/v1/editor/launch?token=eyJhbGciOi..."}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://api.grona.ai/v1/variations/edit-link/validate \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "editUrl": "https://api.grona.ai/v1/editor/launch?token=eyJhbGciOi..." }'Send an editor link you are holding. If it has not expired, it comes back unchanged with
its original generatedAt and expiresAt. If it has expired, the response carries a new
link with a full 15-minute lifetime. Either way the status is 200, and data.editUrl is
the link to open, so you do not need to branch on valid.
A value that is not a Grona editor link returns 400. A link for a variation that no
longer exists returns 404.
Authorizations
Section titled “Authorizations”Request Bodyrequired
Section titled “Request Bodyrequired”object
The whole link as Grona issued it, not the token inside it.
Example
{ "editUrl": "https://api.grona.ai/v1/editor/launch?token=eyJhbGciOi..."}Responses
Section titled “Responses”The link to open, either the one you sent or a new one.
object
object
Whether the link you sent had not yet expired. Informational, for your logs.
Whether Grona issued a new link.
The link to open. The one you sent, byte for byte, or its replacement.
The variation the returned link opens.
ISO 8601 in UTC, with millisecond precision.
ISO 8601 in UTC, with millisecond precision.
object
The workspace resolved from the secret key.
Examples
Not expired, returned unchanged
{ "data": { "valid": true, "refreshed": false, "editUrl": "https://api.grona.ai/v1/editor/launch?token=eyJhbGciOi...", "variationId": "671a3c90e4b0a1f2c3d40012", "generatedAt": "2026-10-20T11:12:05.614Z", "expiresAt": "2026-10-20T11:27:05.614Z" }, "meta": { "workspaceId": "65e8a1c47d2f9b03e6a15c42" }}Expired, replaced with a new link
{ "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" }}editUrl is missing or is not a Grona editor link. error.code is invalid_request.
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": "invalid_request", "message": "`editUrl` is not a Grona editor link." }}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." }}No variation with this id exists in the key’s workspace, for example because its
campaign was deleted. error.code is not_found.
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": "not_found", "message": "Variation not found." }}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.