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

Mobile grants API

A grant lets one identified shopper see one money-bearing offer in your app, such as 10% off this order. Grona signs each grant, the Grona SDK shows an offer only when it holds a matching grant, and your order backend checks the grant again before it applies the treatment. This page covers the one call your backend makes to fetch grants, where it fits, and how to handle failures and caching. The mobile grants API is available on request.

  1. The app identifies the shopper. It passes an opaque customer id, such as cus_7Hq2Lm9x, to the SDK’s identify. See Shoppers and identity.

  2. The SDK asks the app for grants. It does this on identify, then again shortly before the nearest grant expires, and at least once an hour, through the grant source your app provides. Your app passes the request to your backend, inside the shopper’s signed-in session.

  3. Your backend calls Grona. It sends POST /v1/mobile/grants with your secret key and the customer id from the shopper’s session.

  4. Your backend returns the grants to the app. The SDK verifies each one and shows an offer only when a grant matches its campaign, variant, treatment type and value.

  5. Your order backend verifies the grant at order time. It checks the signature and claims, enforces single use and applies the treatment. See Grants and order verification.

The request is server to server, authenticated with your workspace secret key like every other Public API call. The body has one field, the shopper’s customer id.

Terminal window
curl https://api.grona.ai/v1/mobile/grants \
-H "Authorization: Bearer $GRONA_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"customerId": "cus_7Hq2Lm9x"}'

The response lists the shopper’s grants:

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

An empty grants list is a normal answer: no offer applies to this shopper right now. Return the list to the app as it is.

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. That makes the call safe to retry.

Your order backend still enforces single use for each grant, keyed on its jti claim. Once a grant expires, a later call can return a new grant for the same campaign, with a new jti. If an offer must apply once per customer rather than once per grant, your order backend enforces that too.

customerId is the same opaque id your app passes to the SDK’s identify. It becomes the grant’s sub claim, and both the SDK and your order backend check it against the shopper. Never use an email address, a phone number or a national id.

Each grant is a compact JWS signed by Grona, at most 4 KB, that names one shopper, one campaign and variant, and one treatment. It expires at most 24 hours after it is issued. Offers and grants explains the header and every claim.

Status When What to do
400 invalid_request customerId is missing, or is not 1 to 128 letters, digits, ., _, : or -. Send the customer id from the shopper’s session. The SDK’s identify accepts the same characters.
401 unauthorized The secret key is missing, malformed, unknown or revoked. See Authentication.
429, 503 Too many requests, or Grona is briefly unavailable. Report a failed fetch to the app. The SDK retries with backoff.
500 internal_error Something failed on Grona’s side. Report a failed fetch to the app. The SDK retries with backoff.

When the call to Grona fails or times out, tell the app the fetch failed rather than returning an empty list. The app’s grant source then reports a failure (on Android, fetchGrants returns null), and the SDK retries with backoff. An empty list means the fetch succeeded and no grant applies, so the SDK does not retry straight away. Either way the shopper sees no offer until a grant arrives: the campaign gives way to the next one in that placement and the app keeps working. The SDK waits at most 10 seconds for a grant fetch, so keep your route fast.

Grants are short-lived: each one expires at most 24 hours after it is issued. The SDK keeps them in memory only, and refetches shortly before the nearest one expires and at least once an hour.

  • You do not need to cache grants. Fetching them on each request from the app is the expected pattern: repeat calls return the customer’s currently valid grants, not new ones.
  • If you do cache, key the cache by customer id, never share entries between shoppers, and drop an entry no later than the earliest exp among its grants.
  • Clear any cached grants when the shopper signs out.
  • No redeem endpoint. Your order backend verifies each grant itself and enforces single use, keyed on the grant’s jti claim and never on the token string. See Grants and order verification.
  • No key-download endpoint. There is no API for fetching signing keys. See Rotate signing keys.