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

Check an editor link and refresh it if expired

POST
/v1/variations/edit-link/validate
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.

Media typeapplication/json
object
editUrl
required

The whole link as Grona issued it, not the token inside it.

string format: uri

Example

{
"editUrl": "https://api.grona.ai/v1/editor/launch?token=eyJhbGciOi..."
}

The link to open, either the one you sent or a new one.

Media typeapplication/json
object
data
required
object
valid
required

Whether the link you sent had not yet expired. Informational, for your logs.

boolean
refreshed
required

Whether Grona issued a new link.

boolean
editUrl
required

The link to open. The one you sent, byte for byte, or its replacement.

string format: uri
variationId
required

The variation the returned link opens.

string
generatedAt
required

ISO 8601 in UTC, with millisecond precision.

string format: date-time
expiresAt
required

ISO 8601 in UTC, with millisecond precision.

string format: date-time
meta
required
object
workspaceId
required

The workspace resolved from the secret key.

string

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"
}
}

editUrl is missing or is not a Grona editor link. error.code is invalid_request.

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": "invalid_request",
"message": "`editUrl` is not a Grona editor link."
}
}

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."
}
}

No variation with this id exists in the key’s workspace, for example because its campaign was deleted. error.code is not_found.

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": "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.

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.