Developer documentation
ShareAnyData stores your configuration and feature flags as versioned JSON and serves the published version from Cloudflare's edge. There are two surfaces: a Read API your apps call to fetch live config, and an Authoring API behind the dashboard for creating and publishing it.
Base URL for the API: https://api.shareanydata.com
Concepts
- Project — a workspace. Its
projectKeyis the public slug in every read URL. - Environment — a named stage within a project (e.g.
Dev,Stage,Prod). Configs are promoted between environments. - Config — a named JSON document in an environment (e.g.
feature-flags). - Version — an immutable snapshot of a config's content. Saving creates a new version; publishing makes a version live.
- Template — an optional JSON shape a config is checked against (missing/extra keys).
- Read key (
sad_read_…) — ships in your apps; can only read published configs. Server key (sad_server_…) — for your own automation.
Quickstart
- Create a free account and a project (you get one free project + one published config).
- Create a config, edit its JSON, and click Publish.
- Create a read key under the project's API keys.
- Fetch it from anywhere:
curl -H "Authorization: Bearer sad_read_•••" \
https://api.shareanydata.com/v1/acme/Prod/feature-flags
# → 200
# {"checkout_v2": true, "max_items": 25}
API keys
Create keys in the dashboard under a project → API keys. Present a read key one of two ways:
Authorization: Bearer sad_read_••• # preferred
?key=sad_read_••• # for header-less clients (e.g. <img>/<script>)
Read keys are read-only and safe to ship in client apps. You can scope a read key to a single environment and revoke any key instantly. Never ship a server key.
Read API — get a config
GET /v1/{projectKey}/{env}/{configKey}
Returns the live published JSON for that config, or 404 not_published if it has never been published.
GET /v1/acme/Prod/feature-flags
Authorization: Bearer sad_read_•••
200 OK
ETag: "v_8x2k…"
Cache-Control: public, max-age=300, stale-while-revalidate=86400
Content-Type: application/json
{ "checkout_v2": true, "max_items": 25 }
HEAD /v1/{projectKey}/{env}/{configKey} returns the headers (including the ETag) with no body — a cheap way to poll for changes.
Read API — get all configs in an environment
GET /v1/{projectKey}/{env} returns every live config in one request — handy at app start.
GET /v1/acme/Prod
Authorization: Bearer sad_read_•••
200 OK
{ "configs": { "feature-flags": { … }, "pricing": { … } } }
Caching & ETags
The ETag is the published version's id, so it changes only when you publish a new version. Send it back with If-None-Match to get a tiny 304 instead of the full body:
GET /v1/acme/Prod/feature-flags
If-None-Match: "v_8x2k…"
304 Not Modified # no body — your client keeps its cached value
Responses are cached at Cloudflare's edge (max-age=300, stale-while-revalidate=86400). Publishing a new version refreshes the edge cache so reads pick it up quickly. The SDKs handle ETag caching and conditional requests for you.
SDK — Web / JavaScript / TypeScript
npm i @shareanydata/web
import { createClient } from "@shareanydata/web";
const sad = createClient({
sdkKey: "sad_read_•••",
projectKey: "acme",
env: "Prod",
pollIntervalMs: 30000, // optional: detect new publishes
});
const flags = await sad.get("feature-flags");
if (flags.checkout_v2) renderNewCheckout();
sad.on("feature-flags", (next) => applyFlags(next)); // fires on change
const all = await sad.getAll(); // every config in the env
Isomorphic (browser + Node 18+). Caches by ETag, sends conditional requests, serves the last value when offline, and zero dependencies.
SDK — iOS / Swift
// Swift Package Manager
.package(url: "https://github.com/shareanydata/sdk-ios", from: "0.1.0")
import ShareAnyData
let sad = ShareAnyData(sdkKey: "sad_read_•••", projectKey: "acme", env: "Prod",
options: .init(pollInterval: 30))
struct Flags: Decodable { let checkout_v2: Bool; let max_items: Int }
let flags: Flags = try await sad.get("feature-flags")
sad.observe("feature-flags") { (f: Flags) in apply(f) }
SDK — Android / Kotlin
// build.gradle.kts
implementation("com.shareanydata:sdk:0.1.0")
val sad = ShareAnyData(context, sdkKey = "sad_read_•••",
projectKey = "acme", env = "Prod", pollIntervalMs = 30000)
val flags = sad.get("feature-flags") // suspend → JSONObject
if (flags.getBoolean("checkout_v2")) showNewCheckout()
sad.observe("feature-flags") { next -> applyFlags(next) }
Authoring API — overview & auth
The dashboard talks to the authoring API on the same origin under /api/…. Requests are authenticated with the bearer token you receive when you sign in (Google or Apple), and gated by your plan. Use it to script project setup or CI publishing.
Authorization: Bearer <your session JWT>
Content-Type: application/json
Authoring API — endpoints
| GET | /api/auth/me | Current user + entitlement. |
| GET | /api/projects | List your projects. |
| POST | /api/projects | Create a project { name } (seeds Template/Dev/Stage/Cert/Prod). |
| GET | /api/projects/:key | Project + environments. |
| POST | /api/projects/:key/envs | Add an environment (max 6). |
| POST | /api/projects/:key/envs/:env/configs | Create a config { configKey, content? }. |
| POST | …/configs/:cfg/versions | Save a new version { content }. |
| GET | …/configs/:cfg/diff | Missing/extra keys vs the linked template. |
| POST | …/configs/:cfg/promote | Promote to another env { targetEnv }. |
| POST | …/configs/:cfg/publish | Publish the head (or { versionId }). |
| POST | …/configs/:cfg/unpublish | Take a config offline. |
| POST | /api/projects/:key/keys | Create a key { kind: "read"|"server", label?, envScope? }. |
| GET | /api/projects/:key/export | Export the whole project as JSON. |
Errors
401 | missing_read_key / unauthenticated — no or invalid credential. |
402 | quota_exceeded / project_limit — plan limit reached; upgrade. |
403 | Key doesn't match the project, or is out of its environment scope. |
404 | not_published / *_not_found — nothing live at that path. |
All error bodies are JSON: { "error": "…" } (plus context fields where useful).