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

Fetch grants for a shopper

POST
/v1/mobile/grants
curl --request POST \
--url https://api.grona.ai/v1/mobile/grants \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "customerId": "cus_7Hq2Lm9x" }'

Returns the currently valid grants for one identified shopper. Call it from your backend, with your secret key, inside the shopper’s authenticated session, then hand the grants to the app. Take customerId from that session, never from a value the app sends.

Each grant is a compact JWS signed by Grona. It expires at most 24 hours after it is issued, so do not cache grants past their exp or share them between shoppers. An empty list means no grant applies to this shopper right now.

Repeat calls for the same customer return that customer’s currently valid grants. Grona does not mint a new grant for a campaign while an unexpired one exists for that customer, so retries and refetches cannot multiply the grants a shopper can redeem. The call is safe to retry.

Grona has no endpoint to redeem a grant. Your order backend verifies each grant itself at order time and enforces single use per grant jti. If an offer must apply once per customer rather than once per grant, your order backend enforces that too. See Mobile grants API.

Media typeapplication/json
object
customerId
required

The opaque customer id your app passes to the SDK’s identify, taken from the shopper’s authenticated session: 1 to 128 letters, digits, ., _, : or -, the same rule the SDK applies to identify. Never an email, a phone number or a national id. It becomes the grant’s sub claim.

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

Example

{
"customerId": "cus_7Hq2Lm9x"
}

The shopper’s grants. Empty when none apply.

Media typeapplication/json
object
data
required
object
grants
required

The customer’s currently valid grants, at most 50: compact JWS tokens with typ grona-grant+jwt, each at most 4 KB. Grona mints no new grant for a campaign while an unexpired one exists for the customer.

Array<string>
<= 50 items
meta
required
object
workspaceId
required

The workspace resolved from the secret key.

string

Example

{
"data": {
"grants": [
"eyJhbGciOiJFUzI1NiIs..."
]
},
"meta": {
"workspaceId": "65e8a1c47d2f9b03e6a15c42"
}
}

customerId is missing, or is not 1 to 128 letters, digits, ., _, : or -. 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": "`customerId` must be 1 to 128 letters, digits, `.`, `_`, `:` or `-`."
}
}

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.