Layers
Partner APIGetting started

Your first request

Call `/v1/whoami`, interpret every field, and know your key is healthy before you build anything else.

View as Markdown

Every Partner API client makes the same first call: GET /v1/whoami. It's the cheapest endpoint on the surface and it tells you four things at once — that your key authenticates, what org it's bound to, what tier it's on, and whether the kill switch is on. Run it once in your bootstrap and again in CI. If it returns 200 with the shape below, the rest of the API is yours to use.

The call

curl -i https://api.layers.com/v1/whoami \
  -H "X-Api-Key: $LAYERS_API_KEY"
whoami.ts
const res = await fetch("https://api.layers.com/v1/whoami", {
  headers: { "X-Api-Key": process.env.LAYERS_API_KEY! },
});

if (!res.ok) {
  throw new Error(`whoami failed: ${res.status} ${await res.text()}`);
}

const me = await res.json();
console.log(me);
whoami.py
import os, requests

res = requests.get(
    "https://api.layers.com/v1/whoami",
    headers={"X-Api-Key": os.environ["LAYERS_API_KEY"]},
)
res.raise_for_status()
me = res.json()
print(me)

The response

A healthy key returns 200 OK:

{
  "organizationId": "2481fa5c-a404-44ed-a561-565392499abc",
  "workspaceId": "2481fa5c-a404-44ed-a561-565392499abc",
  "organizationName": "Acme Growth",
  "scopes": [],
  "rateLimitTier": "standard",
  "killSwitch": false,
  "apiAccessRevoked": false,
  "apiKeyId": "c2037bb9-354d-4662-96b7-97a28ad6b6e1",
  "creditBalance": 6000
}

Every field is load-bearing.

organizationId and workspaceId

The UUIDs your key is pinned to. Every project, content container, social account, and metric you can touch hangs off organizationId. workspaceId will diverge from organizationId in a future workspace split — today they resolve to the same value for partner keys. Persist organizationId in your config; you'll want it in log lines when filing a support ticket.

organizationName

Human-readable label for the org. Safe to render in a "you are authenticated as" UI element.

scopes

Present for forward-compat. Partner keys today carry org-level access and the returned array is empty. A future release will populate granular scopes — don't key behavior off scopes.length until that ships.

rateLimitTier

standard by default. Partner-tier keys (pilot, partner) are provisioned by Layers for enterprise partners. The tier sets per-endpoint-class rpm limits; if you're hitting 429 RATE_LIMITED in normal operation, this field is the first thing to check. Full tier table lives in Rate limits.

killSwitch and apiAccessRevoked

Two "stop the world" signals.

  • killSwitch: true → this specific key is disabled. Clearable by Layers without re-issuing.
  • apiAccessRevoked: true → the whole org lost API access (billing lapse, policy violation). Cleared when the underlying issue is resolved.

Either one means every other call returns 503 KILL_SWITCH until cleared. Treat both as hard stops — no retry, no exponential back-off. Stop your worker, page your Layers contact, find out why.

If either killSwitch or apiAccessRevoked is true, do not retry the call you were about to make. The switch is binary and the only recovery is human. Log the incident and stop.

apiKeyId

Safe to log. It's the public identifier that Layers uses for rate-limit attribution and audit events. You'll see it echoed in every partner_audit log entry.

What can go wrong

A small set of failure modes accounts for nearly every onboarding ticket. If /v1/whoami doesn't return 200, you'll see one of these:

Statuserror.codeWhat happened
401UNAUTHENTICATEDHeader missing, malformed, or the secret doesn't match. Check X-Api-Key: <key> is set and the key is the full string (not just key_id).
401UNAUTHENTICATED (message: "API key has been revoked.")The key was revoked. Create a new one with your Layers contact.
402BILLING_EXHAUSTEDYour plan doesn't include partner API access. error.details.minTier names the tier you need.
503KILL_SWITCHPer-key or org-wide kill switch is engaged. See Authentication → kill switch.
5xxINTERNALBad day on our side. Include X-Request-Id when you page us.

Every error response carries the same shape — { error: { code, message, requestId, details? } }. Branch on code, never on message. Full vocabulary in Errors.

A boot-time sanity check

Run whoami at process startup and refuse to serve traffic until it returns clean. The pattern below catches every one of the failure modes above before your first real request hits the wire.

boot-check.sh
#!/usr/bin/env bash
set -euo pipefail

response=$(curl -sS -w "\n%{http_code}" \
  https://api.layers.com/v1/whoami \
  -H "X-Api-Key: $LAYERS_API_KEY")

body=$(echo "$response" | sed '$d')
status=$(echo "$response" | tail -n1)

if [ "$status" != "200" ]; then
  echo "whoami failed: HTTP $status"
  echo "$body"
  exit 1
fi

if echo "$body" | jq -e '.killSwitch == true or .apiAccessRevoked == true' > /dev/null; then
  echo "access revoked or kill switch engaged — refusing to start"
  exit 1
fi

echo "key healthy: $(echo "$body" | jq -r '.organizationId')"
boot-check.ts
export async function assertKeyHealthy() {
  const res = await fetch("https://api.layers.com/v1/whoami", {
    headers: { "X-Api-Key": process.env.LAYERS_API_KEY! },
  });

  if (!res.ok) {
    const body = await res.text();
    throw new Error(`whoami ${res.status}: ${body}`);
  }

  const me = await res.json();

  if (me.killSwitch || me.apiAccessRevoked) {
    throw new Error(
      `api access blocked: killSwitch=${me.killSwitch} apiAccessRevoked=${me.apiAccessRevoked}`,
    );
  }

  console.log(
    `key healthy — org=${me.organizationId} name=${me.organizationName} tier=${me.rateLimitTier}`,
  );
  return me;
}
boot_check.py
import os, requests

def assert_key_healthy() -> dict:
    res = requests.get(
        "https://api.layers.com/v1/whoami",
        headers={"X-Api-Key": os.environ["LAYERS_API_KEY"]},
        timeout=10,
    )
    res.raise_for_status()
    me = res.json()

    if me.get("killSwitch") or me.get("apiAccessRevoked"):
        raise RuntimeError(
            f"api access blocked: killSwitch={me.get('killSwitch')} "
            f"apiAccessRevoked={me.get('apiAccessRevoked')}"
        )

    print(
        f"key healthy — org={me['organizationId']} "
        f"name={me['organizationName']} tier={me['rateLimitTier']}"
    )
    return me

Wire this into your service's health check or your CI smoke suite. It's a single network round trip and it will catch a misconfigured secret faster than any downstream 403 ever will.

What's next

On this page