Fetch grants for a shopper
package main
import ( "fmt" "strings" "net/http" "io")
func main() {
url := "https://api.grona.ai/v1/mobile/grants"
payload := strings.NewReader("{ \"customerId\": \"cus_7Hq2Lm9x\" }")
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, "{ \"customerId\": \"cus_7Hq2Lm9x\" }");Request request = new Request.Builder() .url("https://api.grona.ai/v1/mobile/grants") .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/mobile/grants';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"customerId":"cus_7Hq2Lm9x"}'};
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/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.
Authorizations
Section titled “Authorizations”Request Bodyrequired
Section titled “Request Bodyrequired”object
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.
Example
{ "customerId": "cus_7Hq2Lm9x"}Responses
Section titled “Responses”The shopper’s grants. Empty when none apply.
object
object
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.
object
The workspace resolved from the secret key.
Example
{ "data": { "grants": [ "eyJhbGciOiJFUzI1NiIs..." ] }, "meta": { "workspaceId": "65e8a1c47d2f9b03e6a15c42" }}customerId is missing, or is not 1 to 128 letters, digits, ., _, : or -.
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": "`customerId` must be 1 to 128 letters, digits, `.`, `_`, `:` or `-`." }}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." }}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.