Skip to Content

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 /evaluate

Headers:

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-demo

The 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! Bool

The 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/batch manually 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

ScenarioRecommendation
Language has an official GoGreen SDKUse the SDK
Language has no SDK but has an HTTP clientUse the API directly
Embedded system or constrained environmentUse the API directly
Serverless / short-lived processUse the API (no streaming needed)
Need real-time streaming updatesUse an SDK or build SSE support
Evaluating flags in a CLI tool or scriptUse 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.