API Reference

REST API

Use the REST API directly when you need server-side control, CI/CD integration, or a language without a native SDK.

Two different APIs. This page covers the lightweight evaluation endpoints under /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.

Authorization
X-API-Key: sk_live_xxx
Keys are never accepted in the query string. Query strings leak into access logs, proxy caches, browser history and 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:

SSE ticket
# 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

MethodPathPurpose
POST/v1/evaluateEvaluate one flag.
POST/v1/evaluate/batchEvaluate every flag for a context.
GET/v1/blobFetch the whole flag configuration for local evaluation.
POST/v1/stream/ticketMint a single-use SSE ticket.
GET/v1/streamServer-Sent Events stream of flag changes.
POST/v1/analytics/eventsSubmit 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.

An unknown flag is a 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
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
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
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

StatusMeaning
400Malformed body, or an API key supplied in the query string.
401Missing, invalid, or expired API key.
402The plan doesn't include this feature (streaming). Fall back to polling.
403The key doesn't have permission for this resource.
404No flag exists with that key in this environment.
429Rate 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
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.