Public SDK Endpoints
The REST API behind the Abrova Flags SDKs. Client SDKs ask the service for evaluated values with the public key; server SDKs download the rules with the server secret and evaluate locally.
These are the endpoints the 0.2.0 SDKs call. You only need this page if you call Abrova Flags over HTTP yourself, for example from a platform without an SDK. Base URL: https://flags.abrova.ir. All bodies are JSON.
Who calls what#
Flutter, React Native, Android, iOS, Next.js client
- Public API key only
- POST /public/v2/evaluate
- The service evaluates and returns values for one user
- Rules, segments and variant weights are never returned
Node.js, Next.js server
- API key and server secret
- GET /server/v2/flags
- The SDK downloads the rules and evaluates locally
- Optional POST /server/v2/usage for the Console charts
Authentication#
| Option | Type | Required | Description |
|---|---|---|---|
| x-api-key | Header | Yes | Your project API key (ab_live_xxx) |
| x-server-secret | Header | No | Server secret (ab_srv_xxx). Required on every /server route. Never send it from an app or a browser |
| x-abrova-sdk | Header | No | Platform and version of the caller, for example node/0.2.0. Informational |
| If-None-Match | Header | No | The ETag of your last answer for the same request. The service answers 304 when nothing changed |
A public API key cannot read targeting rules. A /server route called without the server secret answers 403 with code server_secret_required. If the server secret has an IP allow-list, calls from other addresses are refused.
POST /public/v2/evaluate#
Evaluates the flags of the project for one user context and returns the values. Used by all client SDKs.
Request
curl -X POST "https://flags.abrova.ir/public/v2/evaluate" \
-H "x-api-key: ab_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"environment": "production",
"context": {
"userId": "user-123",
"anonymousId": "3f1c2b8e-9d0a-4c51-a1f2-6f0b7e2d9c11",
"attributes": { "plan": "pro", "country": "IR", "beta": true, "age": 31 }
},
"flagKeys": ["new-checkout", "theme"]
}'Request Body
| Option | Type | Required | Description |
|---|---|---|---|
| environment | string | No | Environment key (production, staging, development or your own). Leave out to use the Default values. An unknown or inactive key answers 404 |
| context | object | Yes | Who the flags are evaluated for |
| context.userId | string | No | The signed-in user id, 1 to 256 characters |
| context.anonymousId | string | No | An id for a signed-out install or visitor, 1 to 256 characters. The SDKs always send it |
| context.attributes | object | No | At most 100 attributes. A value is a string (at most 1,024 characters), a number, a boolean, null, or an array of at most 100 of those. No nested objects |
| flagKeys | string[] | No | Only evaluate these flags, at most 500. Leave out to evaluate every flag |
At least one of context.userId and context.anonymousId is required. Attributes named userId or anonymousId are ignored; the context fields are used instead.
Response
{
"environment": "production",
"evaluatedAt": "2026-10-01T12:00:00.000Z",
"flags": {
"new-checkout": {
"key": "new-checkout",
"valueType": "boolean",
"enabled": true,
"value": true,
"variant": null,
"reason": "ON",
"version": 7
},
"theme": {
"key": "theme",
"valueType": "string",
"enabled": true,
"value": "dark",
"variant": "dark",
"reason": "VARIANT",
"version": 3
}
}
}| Field | Type | Description |
|---|---|---|
| environment | string | null | The environment that was evaluated. null for the Default values |
| evaluatedAt | string | Time of the evaluation (ISO 8601) |
| flags | object | One entry per flag, by key. Flags that are off are included with their off value. Keys that do not exist are left out |
| flags.*.valueType | string | boolean, string, number or json |
| flags.*.enabled | boolean | true when the flag is served on for this context |
| flags.*.value | any | The value to use, already of the flag type |
| flags.*.variant | string | null | Key of the served variant, if any |
| flags.*.reason | string | Why: ON, VARIANT, DISABLED, PREREQUISITE_FAILED, NOT_TARGETED, OUTSIDE_ROLLOUT, NO_BUCKETING_ID or ERROR |
| flags.*.version | number | Version of the flag |
The response carries an ETag header. Send it back as If-None-Match with the same body to get 304 Not Modified when the values did not change.
Each call to this endpoint is counted as one flag evaluation, whatever the number of flags it returns, and also when the answer is 304 (100,000 evaluations are free every month; the price above that is set at launch). Cache the values and call again only when the user or the attributes change.
GET /server/v2/flags#
Returns every flag of the project with its rules, for local evaluation. Used by the Node.js and Next.js server SDKs. Requires the server secret. Not counted as evaluations.
Request
curl -X GET "https://flags.abrova.ir/server/v2/flags?environment=production" \ -H "x-api-key: ab_live_xxx" \ -H "x-server-secret: ab_srv_xxx"
Query Parameters
| Option | Type | Required | Description |
|---|---|---|---|
| environment | string | No | Environment key. Leave out to use the Default values. An unknown or inactive key answers 404 |
Response
{
"schemaVersion": 2,
"environment": "production",
"generatedAt": "2026-10-01T12:00:00.000Z",
"flags": [
{
"key": "theme",
"valueType": "string",
"enabled": true,
"rolloutPercentage": 100,
"salt": "a1b2c3d4e5f6",
"rules": {
"operator": "AND",
"rules": [
{ "attribute": "plan", "operator": "in", "value": ["pro", "business"] }
]
},
"variants": [
{ "key": "dark", "value": "dark", "weight": 50 },
{ "key": "light", "value": "light", "weight": 50 }
],
"offValue": "light",
"prerequisites": [{ "flagKey": "new-ui" }],
"version": 3
}
],
"segments": [
{
"key": "beta-testers",
"rules": {
"operator": "OR",
"rules": [
{ "attribute": "email", "operator": "ends_with", "value": "@example.com" }
]
}
}
]
}Environment overrides are already merged into each flag. offValue is the value served when the user does not get the flag. The response carries an ETag header; send it back as If-None-Match to get 304 when nothing changed. The server SDKs do this every 30 seconds.
Evaluating these rules yourself means implementing the whole evaluation flow, including bucketing. The Node.js and Next.js server SDKs already do it and give the same results as the service.
POST /server/v2/usage#
Optional. Reports how often a server evaluated each flag, so the evaluation charts in Abrova Console include server-side evaluations. The server SDKs send it when sendUsage is true. Requires the server secret. Not billed, and it contains no user ids.
Request
curl -X POST "https://flags.abrova.ir/server/v2/usage" \
-H "x-api-key: ab_live_xxx" \
-H "x-server-secret: ab_srv_xxx" \
-H "Content-Type: application/json" \
-d '{
"environment": "production",
"periodStart": "2026-10-01T12:00:00.000Z",
"periodEnd": "2026-10-01T12:01:00.000Z",
"counts": [
{ "flagKey": "theme", "variant": "dark", "enabled": true, "count": 1520 },
{ "flagKey": "theme", "variant": null, "enabled": false, "count": 40 }
]
}'Request Body
| Option | Type | Required | Description |
|---|---|---|---|
| environment | string | No | Environment key the counts belong to |
| periodStart | string | Yes | Start of the period (ISO 8601) |
| periodEnd | string | Yes | End of the period. Not before periodStart, at most one hour after it |
| counts | array | Yes | At most 1,000 entries |
| counts[].flagKey | string | Yes | The flag key. Unknown keys are ignored |
| counts[].variant | string | null | No | The served variant key |
| counts[].enabled | boolean | Yes | Whether the flag was served on |
| counts[].count | number | Yes | Whole number from 1 to 10,000,000 |
Response
202 Accepted
{ "accepted": 2 }Errors#
Errors are JSON. Act on the HTTP status; code and message are for your logs.
{
"statusCode": 403,
"error": "Forbidden",
"code": "server_secret_required",
"message": "This route returns targeting rules and needs the server secret (x-server-secret)."
}| Status | When | What to do |
|---|---|---|
| 304 | If-None-Match matched | Keep the values or rules you have |
| 400 | Invalid body (code invalid_request): no user id and no anonymous id, too many attributes, a nested attribute | Fix the request. Do not send the same body again |
| 401 | Missing or invalid API key, invalid server secret, or Flags is not set up for the project | Check the keys. Do not retry automatically |
| 403 | A /server route without the server secret, or a caller outside the server secret IP allow-list | Do not retry automatically |
| 404 | The environment does not exist or is inactive (code environment_not_found) | Check the environment key. Do not retry automatically |
| 429 | Rate limited | Wait the number of seconds in the Retry-After header, then retry with a growing delay |
| 5xx | Server error | Keep the last values and retry with a growing delay |
Requests are rate limited per key and per address. Server-secret calls have higher limits than public-key calls. Do not hard-code the limits: follow Retry-After.
Older endpoints (upgrading from 0.1.x)#
Two endpoints from the 0.1.x SDKs still answer. Use the endpoints above for new code.
| Endpoint | Status |
|---|---|
| POST /public/flag-evaluation | Deprecated. Evaluates one flag for one user with the public key. Body: flagKey and userId (required), userAttributes and environment (optional). Answers flagKey, enabled, value and, when a variant was served, variant. An unknown flag answers enabled false with reason flag_not_found. Counted as one evaluation per call. Use POST /public/v2/evaluate instead |
| GET /public/flags | Deprecated; needs the server secret. Use `GET /server/v2/flags`. |
The client SDKs call the service on start, when the user context changes and when the app or tab comes back after 15 minutes. The server SDKs never call the service per evaluation. If you call the API yourself, follow the same pattern.