# Your first request (/docs/api/getting-started/first-request)



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 [#the-call]

<Tabs items="['curl', 'TypeScript', 'Python']">
  <Tab value="curl">
    ```bash
    curl -i https://api.layers.com/v1/whoami \
      -H "X-Api-Key: $LAYERS_API_KEY"
    ```
  </Tab>

  <Tab value="TypeScript">
    ```ts title="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);
    ```
  </Tab>

  <Tab value="Python">
    ```python title="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)
    ```
  </Tab>
</Tabs>

## The response [#the-response]

A healthy key returns `200 OK`:

```json
{
  "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` [#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` [#organizationname]

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

### `scopes` [#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` [#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](/docs/api/operational/rate-limits).

### `killSwitch` and `apiAccessRevoked` [#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.

<Callout type="warn">
  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.
</Callout>

### `apiKeyId` [#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 [#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:

| Status | `error.code`                                             | What happened                                                                                                                               |
| ------ | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `401`  | `UNAUTHENTICATED`                                        | Header 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`). |
| `401`  | `UNAUTHENTICATED` (message: "API key has been revoked.") | The key was revoked. Create a new one with your Layers contact.                                                                             |
| `402`  | `BILLING_EXHAUSTED`                                      | Your plan doesn't include partner API access. `error.details.minTier` names the tier you need.                                              |
| `503`  | `KILL_SWITCH`                                            | Per-key or org-wide kill switch is engaged. See [Authentication → kill switch](/docs/api/getting-started/authentication#kill-switch).       |
| `5xx`  | `INTERNAL`                                               | Bad 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](/docs/api/operational/errors).

## A boot-time sanity check [#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.

<Tabs items="['curl', 'TypeScript', 'Python']">
  <Tab value="curl">
    ```bash title="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')"
    ```
  </Tab>

  <Tab value="TypeScript">
    ```ts title="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;
    }
    ```
  </Tab>

  <Tab value="Python">
    ```python title="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
    ```
  </Tab>
</Tabs>

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 [#whats-next]

* [Authentication](/docs/api/getting-started/authentication) — rotation, kill switch, rate limits.
* [Common patterns](/docs/api/getting-started/common-patterns) — idempotency, pagination, errors, async jobs.
* [Quickstart](/docs/api/getting-started/quickstart) — six calls end to end, from key check to first ingest job.
