Feature Flags in Any Language: Using the Evaluation API Directly
March 10, 2026
GoGreen ships official SDKs for Go, TypeScript, Python, Java, Rust, and C#. But what if your stack runs on C++, Swift, Kotlin Native, Elixir, Haskell, or an embedded system? You’re not locked out. The Evaluation API is a simple HTTP+JSON contract that any language with an HTTP client can call.
This post walks through building a feature flag client from scratch in C++, but the pattern works identically in any language. If you can POST JSON and parse a response, you can use GoGreen.
The Evaluation API Contract
The entire interface is a single endpoint:
POST /evaluateHeaders:
Content-Type: application/json
Authorization: Bearer <your-server-sdk-key>Request body:
{
"key": "user-42",
"kind": "user",
"custom": {
"email": "alice@example.com",
"plan": "pro",
"country": "US"
}
}Response:
{
"flags": {
"dark-mode": {
"value": true,
"reason": { "kind": "RULE_MATCH" },
"variationId": "on-variation",
"variationName": "On"
},
"rate-limit": {
"value": 1000,
"reason": { "kind": "FALLTHROUGH" },
"variationId": "default-variation",
"variationName": "Default"
}
}
}That’s it. One POST, all your flags back, with evaluation reasons included. No WebSocket, no polling protocol, no binary encoding. Every flag type — boolean, string, number, and JSON — comes back in the same response.
A Complete C++ Example
Here’s a working feature flag client in ~80 lines of C++ using libcurl and nlohmann/json. The full source is in the examples/cpp-api-demo directory.
The HTTP call
#include <curl/curl.h>
#include <nlohmann/json.hpp>
#include <iostream>
#include <string>
using json = nlohmann::json;
static std::string response_body;
static size_t write_cb(char* ptr, size_t size, size_t nmemb, void*) {
response_body.append(ptr, size * nmemb);
return size * nmemb;
}
long post_json(const std::string& url, const std::string& sdk_key,
const std::string& body) {
response_body.clear();
CURL* curl = curl_easy_init();
if (!curl) return -1;
struct curl_slist* headers = nullptr;
headers = curl_slist_append(headers, "Content-Type: application/json");
std::string auth = "Authorization: Bearer " + sdk_key;
headers = curl_slist_append(headers, auth.c_str());
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);
long code = 0;
CURLcode res = curl_easy_perform(curl);
if (res == CURLE_OK)
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
else
code = -1;
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
return code;
}Building the context and parsing flags
int main() {
std::string api_base = "https://app.gogreenflags.com";
std::string sdk_key = std::getenv("GOGREEN_SDK_KEY");
// Build evaluation context
json context = {
{"key", "cpp-demo-user-1"},
{"kind", "user"},
{"custom", {
{"email", "demo@example.com"},
{"plan", "pro"},
{"country", "US"}
}}
};
std::string url = api_base + "/evaluate";
long status = post_json(url, sdk_key, context.dump());
if (status != 200) {
std::cerr << "Request failed: HTTP " << status << "\n";
return 1;
}
json resp = json::parse(response_body);
for (auto& [flag_key, result] : resp["flags"].items()) {
std::cout << flag_key << " = " << result["value"]
<< " (reason: " << result["reason"]["kind"] << ")\n";
}
return 0;
}Build it with CMake:
cd examples/cpp-api-demo
mkdir build && cd build
cmake .. && cmake --build .
GOGREEN_SDK_KEY=your-key ./cpp-api-demoThe output looks like:
dark-mode = true (reason: RULE_MATCH)
rate-limit = 1000 (reason: FALLTHROUGH)
new-checkout = false (reason: OFF)Adapting This to Other Languages
The pattern is identical regardless of language. Here’s the same flow sketched in a few others:
Ruby
require 'net/http'
require 'json'
uri = URI("https://app.gogreenflags.com/evaluate")
req = Net::HTTP::Post.new(uri, {
'Content-Type' => 'application/json',
'Authorization' => "Bearer #{ENV['GOGREEN_SDK_KEY']}"
})
req.body = { key: 'user-42', kind: 'user', custom: { plan: 'pro' } }.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
flags = JSON.parse(res.body)['flags']
puts "dark-mode: #{flags['dark-mode']['value']}"PHP
$ch = curl_init('https://app.gogreenflags.com/evaluate');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . getenv('GOGREEN_SDK_KEY'),
],
CURLOPT_POSTFIELDS => json_encode([
'key' => 'user-42', 'kind' => 'user', 'custom' => ['plan' => 'pro']
]),
]);
$response = json_decode(curl_exec($ch), true);
$darkMode = $response['flags']['dark-mode']['value'];Swift
var request = URLRequest(url: URL(string: "https://app.gogreenflags.com/evaluate")!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("Bearer \(sdkKey)", forHTTPHeaderField: "Authorization")
let context: [String: Any] = ["key": "user-42", "kind": "user", "custom": ["plan": "pro"]]
request.httpBody = try JSONSerialization.data(withJSONObject: context)
let (data, _) = try await URLSession.shared.data(for: request)
let result = try JSONSerialization.jsonObject(with: data) as! [String: Any]
let flags = result["flags"] as! [String: [String: Any]]
let darkMode = flags["dark-mode"]!["value"] as! BoolThe specifics change, but the shape never does: POST context, get flags, read values.
What You Get (and What You Don’t)
Calling the Evaluation API directly gives you:
- All four flag types (boolean, string, number, JSON)
- Full targeting — rules, segments, percentage rollouts, prerequisites
- Evaluation reasons for debugging and observability
- Variation IDs for analytics tracking
What you miss compared to the official SDKs:
- Streaming updates — SDKs receive flag changes in real time via SSE. API-only clients need to re-evaluate on a timer or per-request.
- Local caching — SDKs cache flag state and evaluate locally between polls. API calls add network latency per evaluation.
- Event batching — SDKs automatically batch and flush impression events. API-only clients need to POST to
/events/batchmanually if they want analytics. - OpenFeature integration — The official SDKs include OpenFeature providers for standardized flag evaluation interfaces.
For most backend services, the latency of a single HTTP call per request is negligible — especially if you cache results with a short TTL.
Production Tips
Cache with a TTL. Don’t call /evaluate on every request. Cache the response for 10-60 seconds and re-evaluate when the cache expires. This gives you near-real-time flag changes without per-request latency.
Add a fallback. If the API is unreachable, fall back to sensible defaults. Feature flags should fail safe — a network error shouldn’t crash your application.
long status = post_json(url, sdk_key, body);
if (status != 200) {
// Fall back to safe defaults
bool dark_mode = false;
int rate_limit = 500;
// continue with defaults...
}Track impressions. If you want flag evaluation analytics, POST impression events to /events/batch:
POST /events/batch
{
"events": [
{
"type": "impression",
"flag_key": "dark-mode",
"variation_id": "on-variation",
"user_key": "user-42"
}
]
}Use the evaluation reason. The reason.kind field tells you why a flag resolved the way it did — RULE_MATCH, FALLTHROUGH, OFF, DEFAULT, or PREREQUISITE_FAILED. Log it. It’s invaluable for debugging targeting rules.
When to Use the API vs. an SDK
| Scenario | Recommendation |
|---|---|
| Language has an official GoGreen SDK | Use the SDK |
| Language has no SDK but has an HTTP client | Use the API directly |
| Embedded system or constrained environment | Use the API directly |
| Serverless / short-lived process | Use the API (no streaming needed) |
| Need real-time streaming updates | Use an SDK or build SSE support |
| Evaluating flags in a CLI tool or script | Use the API directly |
The Evaluation API is a first-class interface, not a workaround. Every GoGreen SDK is built on top of it. When you call it directly, you’re using the same contract the SDKs use internally.
Get Started
The complete C++ example lives at examples/cpp-api-demo in the GoGreen repository. Clone it, set your SDK key, and you’ll have feature flags in C++ in under five minutes. Then apply the same pattern to whatever language your stack demands.