REST API
Use the REST API directly when you need server-side control, CI/CD integration, or a language without a native SDK.
/v1 that the SDKs use
under the hood, authenticated with an environment API key. The
management API for creating projects, flags, environments,
segments, tokens, and webhooks lives under /api/v2, uses access
tokens, and is rendered interactively at /api. You can
also grab the raw OpenAPI 3.0 spec to
generate a client.
Base URL
https://api.shipsilently.com Authentication
Every /v1 request requires an X-API-Key header
carrying an environment-scoped key (sk_live_…). The key
determines which environment's flags you get, there is no environment
parameter.
X-API-Key: sk_live_xxx Referer headers, so a request carrying a key in the URL is
rejected. The one exception is the SSE ticket flow below, which uses a
single-use, 60-second credential instead of the standing key.
SSE tickets
Browser EventSource can't set custom headers, so for
GET /v1/stream exchange your key for a single-use ticket and put
that on the URL:
# 1. mint a ticket (header-authenticated)
curl -X POST https://api.shipsilently.com/v1/stream/ticket \
-H "X-API-Key: sk_live_xxx"
# → { "ticket": "…", "expiresIn": 60 }
# 2. open the stream with the ticket (single-use)
new EventSource("https://api.shipsilently.com/v1/stream?ticket=…") Endpoints
| Method | Path | Purpose |
|---|---|---|
POST | /v1/evaluate | Evaluate one flag. |
POST | /v1/evaluate/batch | Evaluate every flag for a context. |
GET | /v1/blob | Fetch the whole flag configuration for local evaluation. |
POST | /v1/stream/ticket | Mint a single-use SSE ticket. |
GET | /v1/stream | Server-Sent Events stream of flag changes. |
POST | /v1/analytics/events | Submit buffered evaluation analytics. |
POST /v1/evaluate
Evaluate a single flag for a user context.
curl -X POST https://api.shipsilently.com/v1/evaluate \
-H "X-API-Key: $SHIPSILENTLY_KEY" \
-H "Content-Type: application/json" \
-d '{
"flagKey": "new-checkout-v2",
"context": { "userId": "u_123", "plan": "pro" }
}' const res = await fetch('https://api.shipsilently.com/v1/evaluate', {
method: 'POST',
headers: {
'X-API-Key': process.env.SHIPSILENTLY_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
flagKey: 'new-checkout-v2',
context: { userId: 'u_123', plan: 'pro' },
}),
});
const result = await res.json(); import os, requests
res = requests.post(
"https://api.shipsilently.com/v1/evaluate",
headers={"X-API-Key": os.environ["SHIPSILENTLY_KEY"]},
json={
"flagKey": "new-checkout-v2",
"context": {"userId": "u_123", "plan": "pro"},
},
)
result = res.json() body := `{"flagKey":"new-checkout-v2","context":{"userId":"u_123"}}`
req, _ := http.NewRequest("POST",
"https://api.shipsilently.com/v1/evaluate",
strings.NewReader(body))
req.Header.Set("X-API-Key", os.Getenv("SHIPSILENTLY_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close() Response
{
"flagKey": "new-checkout-v2",
"value": true,
"reason": "rollout",
"ruleId": "b1f2c3d4-0000-4000-8000-000000000000"
} ruleId is present only when a rule decided the result, that is,
on rule_match and rollout. See
evaluation reasons.
404, not a result. The
endpoint answers {"error": "flag_not_found", "flagKey": "…"}
rather than returning a body with a default value, the server has no way to
know what your default is. The SDKs convert this into a
flag_not_found result carrying the default you passed.
POST /v1/evaluate/batch
Returns every flag for a context in one call.
curl -X POST https://api.shipsilently.com/v1/evaluate/batch \
-H "X-API-Key: $SHIPSILENTLY_KEY" \
-H "Content-Type: application/json" \
-d '{ "context": { "userId": "u_123" } }' {
"flags": {
"new-checkout-v2": { "flagKey": "new-checkout-v2", "value": true, "reason": "rollout", "ruleId": "…" },
"promo-banner": { "flagKey": "promo-banner", "value": false, "reason": "default" }
}
} GET /v1/blob
Returns the environment's entire flag and segment configuration in one document. This is what powers local evaluation, fetch it once, evaluate in-process, and skip the network entirely on the hot path.
curl https://api.shipsilently.com/v1/blob \
-H "X-API-Key: $SHIPSILENTLY_KEY"
Responses carry an ETag. Send it back as
If-None-Match and a steady-state poll costs a 304
with no body. Rather than implement this yourself, consider the
Edge SDK, which does exactly this.
GET /v1/stream
Server-Sent Events stream. Emits a payload whenever flags change and sends a heartbeat every 30 seconds so proxies don't drop the connection, and so clients can detect a dead link.
curl -N https://api.shipsilently.com/v1/stream \
-H "X-API-Key: $SHIPSILENTLY_KEY"
Streaming is a paid feature. On a plan without it, both this endpoint and
the ticket endpoint return 402, which clients should treat as
"fall back to polling", not as an error.
Errors
| Status | Meaning |
|---|---|
400 | Malformed body, or an API key supplied in the query string. |
401 | Missing, invalid, or expired API key. |
402 | The plan doesn't include this feature (streaming). Fall back to polling. |
403 | The key doesn't have permission for this resource. |
404 | No flag exists with that key in this environment. |
429 | Rate limited. The Retry-After header tells you when to retry. |
For how the SDKs respond to each of these, see Resilience.
Management API (v2)
Creating and updating flags is a separate surface under
/api/v2, authenticated with an access token in the
Authorization header, sent raw, with no Bearer
prefix:
Authorization: <your-access-token>
Collections return an items array, paginated collections include
_links cursors, and partial updates use PATCH with
JSON Patch documents. The full reference is at /api.