---
title: "Who this key is: permissions, workspace and limits"
method: GET
path: "/v1/whoami"
tags: ["Workspace"]
---

# Who this key is: permissions, workspace and limits

`GET /v1/whoami`

## Request

- Base URL: `https://api.trayo.ai`
- URL: `https://api.trayo.ai/v1/whoami`
- Auth: HTTP bearer

**Required permissions:** None beyond a valid API key.

**Make this your first call.** It confirms the key works and returns the workspace and user, granted and effective key scopes, key expiry, data-access restrictions, workspace request budgets and lookup capabilities. Compare `key.effectiveScopes` with each operation’s `x-trayo-scope-requirements`.

Read-only and free. This call starts no searches and changes no records. A 401 here is the key itself — no other route will accept it either.

`canInitiate` describes workspace lifecycle only; it does not authorize this key. Lookup `available` combines scope, lifecycle, plan and current quota checks for new lookups. `blockedBy` explains missing authority or limits. These are snapshots: workspace request pacing, concurrent work and request-specific checks still apply.

## Response `200`

- WhoamiDto
  - `workspace` object, required
    - `id` string, uuid, required: The workspace this key acts in.
    - `name` string, required
    - `plan` 'FREE' | 'PRO' | 'GROWTH' | 'ENTERPRISE', required
    - `lifecycle` 'trial_active' | 'trial_grace' | 'nurturing' | 'purged' | 'active', required: `active` and `trial_active` can initiate metered work; the others are read-only. `canInitiate` is this, as a boolean.
  - `actingAs` object, required: The user your key acts as. Accounts and lists you create belong to this user and show up under them in the Trayo app.
    - `userId` string, uuid, required
    - `email` string, nullable, required
    - `name` string, nullable, required
  - `key` object, required
    - `grantedScopes` string[], required: Persisted key grants; not plan eligibility.
    - `effectiveScopes` string[], required: Valid grants allowed by the owner’s current workspace permissions.
    - `expiresAt` string, date-time, nullable, required: Key expiry; null means no scheduled expiry.
    - `label` string, nullable, required: The label the key was issued with, for telling keys apart. `null` when it has none.
  - `restrictions` object, required
    - `dataAccess` object, required
      - `workspaceId` string, uuid, required: Workspace data stays within this tenant; endpoint-specific checks still apply.
      - `lists` 'created_by_key_owner', required: List operations access only lists created by actingAs.userId.
    - `rateLimits` object, required: Effective workspace request budgets per rolling 60 seconds, shared by REST, MCP, all API keys and OAuth users. Daily and total usage quotas are disabled for now; support can adjust workspace throughput. `defaultPerMinute` and `expensivePerMinute` are separate buckets and a call spends one of them; each MCP tool names the bucket it spends in its description and `_meta["trayo/rateLimitBucket"]`.
      - `defaultPerMinute` integer, required
      - `expensivePerMinute` integer, required
  - `capabilities` object, required
    - `emailLookup` object, required
      - `scopeAuthorized` boolean, required: Whether this key and its owner currently authorize this lookup field.
      - `available` boolean, required: Passes scope, lifecycle and plan eligibility checks for a new lookup. Public REST/MCP usage quotas and the daily usage limit (`429 quota_daily_limit`) are disabled for now. Workspace request pacing, validation and per-network transport protection still apply.
      - `blockedBy` string[], required: Which of these reasons block the field right now; an empty array means none of them do. Public REST/MCP lookup quotas and the daily usage limit (`429 quota_daily_limit`) are disabled for now. Workspace request pacing, row validation and per-network transport protection are checked when the lookup runs and do not appear in this list.
    - `phoneLookup` object, required
      - `scopeAuthorized` boolean, required: Whether this key and its owner currently authorize this lookup field.
      - `available` boolean, required: Passes scope, lifecycle and plan eligibility checks for a new lookup. Public REST/MCP usage quotas and the daily usage limit (`429 quota_daily_limit`) are disabled for now. Workspace request pacing, validation and per-network transport protection still apply.
      - `blockedBy` string[], required: Which of these reasons block the field right now; an empty array means none of them do. Public REST/MCP lookup quotas and the daily usage limit (`429 quota_daily_limit`) are disabled for now. Workspace request pacing, row validation and per-network transport protection are checked when the lookup runs and do not appear in this list.
  - `canInitiate` boolean, required: Workspace lifecycle permission to start metered work. This does not grant the key any scope or confirm plan entitlements, available quota or request eligibility.
  - `lookups` object, required: Contact-lookup usage and any active allowances. `POST /v1/people/{id}/enrich`, `POST /v1/people/enrich/batch` and `POST /v1/contacts/lookup` all draw on these, one per field LOOKED UP — the draw happens when a lookup starts, whether or not it goes on to find a value. Usage is recorded even when `limit` and `remaining` are `null`, meaning no lookup cap applies. When a numeric allowance applies, budget on the people you send, not the addresses you expect back. A field that never starts a lookup (a value already on the record, one already running, the cooldown after a miss) costs nothing. When a numeric allowance applies, `402 lookup_limit_reached` is what exceeding one looks like.
    - `email` object, required
      - `enabled` boolean, required: Whether the plan allows this kind of lookup at all. `false` means every lookup is refused.
      - `limit` integer, nullable, required: Effective allowance for this field. `null` means unlimited. Public REST/MCP lookup quotas, workspace quota and the daily usage limit (`429 quota_daily_limit`, a rolling 24-hour ceiling) are disabled for now. Scope, lifecycle and request validation still apply.
      - `used` integer, required: Recorded lookup usage, including lookups still in flight, even when no cap applies. Read `remaining` rather than subtracting this from `limit`.
      - `remaining` integer, nullable, required: Lookups left against an enabled allowance: `limit - used`, floored at 0. `null` when `limit` is `null`; public REST/MCP usage allowances are unlimited for now. The rolling 24-hour daily usage limit (`429 quota_daily_limit`) is disabled too. This is a snapshot that includes lookups still in flight; concurrent work changes `used`. Scope, lifecycle, per-network protection and row validation can still refuse a call. Handle the actual refusal code and, for a stopped MCP batch, `stoppedBy.retry`.
      - `period` 'lifetime', required: The usage counting period. `lifetime`: recorded lookups do not reset. When a numeric allowance applies, it has no scheduled reset; `null` limits remain uncapped.
    - `phone` object, required
      - `enabled` boolean, required: Whether the plan allows this kind of lookup at all. `false` means every lookup is refused.
      - `limit` integer, nullable, required: Effective allowance for this field. `null` means unlimited. Public REST/MCP lookup quotas, workspace quota and the daily usage limit (`429 quota_daily_limit`, a rolling 24-hour ceiling) are disabled for now. Scope, lifecycle and request validation still apply.
      - `used` integer, required: Recorded lookup usage, including lookups still in flight, even when no cap applies. Read `remaining` rather than subtracting this from `limit`.
      - `remaining` integer, nullable, required: Lookups left against an enabled allowance: `limit - used`, floored at 0. `null` when `limit` is `null`; public REST/MCP usage allowances are unlimited for now. The rolling 24-hour daily usage limit (`429 quota_daily_limit`) is disabled too. This is a snapshot that includes lookups still in flight; concurrent work changes `used`. Scope, lifecycle, per-network protection and row validation can still refuse a call. Handle the actual refusal code and, for a stopped MCP batch, `stoppedBy.retry`.
      - `period` 'lifetime', required: The usage counting period. `lifetime`: recorded lookups do not reset. When a numeric allowance applies, it has no scheduled reset; `null` limits remain uncapped.

## Other responses

- `401`: Missing, malformed, or incorrect bearer token. Every way authentication can fail returns the identical body, so do not try to infer which one it was, and do not repeat the call with the same key. `unauthorized` (`retry: do_not_retry`) — Missing, malformed, or incorrect bearer token. Identical body for every cause.
- `403`: The key grant and its owner's current workspace role must both include every scope required by the operation. `insufficient_scope` (`retry: do_not_retry`) — Scope denial details distinguish missing_key_scope (use a key granting every missing scope), owner_permission_limit (the owner needs the missing workspace permissions), and invalid_key_scope (replace the key with supported scopes). Membership and undeclared-route denials remain generic.
- `429`: Every route is rate limited per workspace across REST, MCP, all keys and OAuth users, `GET /v1/whoami` included, so a balance poll competes with the work it is watching. For `rate_limited`, wait `Retry-After` seconds (also `details.retryAfterSeconds`), then send the same request once; do not retry sooner. `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` describe the workspace's bucket, and they come back on successful responses too: read them and slow down BEFORE you are refused. A burst from one network can also be refused before the key is checked; that 429 carries `Retry-After` and no `RateLimit-*` headers. `rate_limited` (`retry: retry_later`) — The workspace or source network exhausted its request budget for one bucket. `details.retryAfterSeconds` (and `Retry-After` on REST) is the wait: wait that long, then retry the same request once. Do not poll or retry sooner; an early retry is refused again. If you cannot wait, stop and tell the user when to retry; do not fill the wait with other calls. `details.limit` and `details.bucket` name the exhausted budget. When present, RateLimit-* headers describe the active budget and reset.
- `500`: Something failed on our side. Nothing about the request needs changing. `internal_error` (`retry: retry_later`) — Something failed on our side. `message` never describes it — quote `requestId` when reporting. Retry with exponential backoff and GIVE UP after a few attempts: `retry_later` here does not mean the failure is known to be transient, only that nothing about your request needs changing. A request that fails this way repeatedly will not start working.

---

[API](https://skmtc.dev/trayo/apis/trayo-api.md) · [All operations](https://skmtc.dev/trayo/apis/trayo-api/llms.txt) · [OpenAPI document](https://skmtc.dev/trayo/apis/trayo-api/revisions/0af452dce6de?raw)
