← tanukitrade.com Open WebApp (opens in a new tab) Start 7-day free trial

Account

Your own usage, clients, and status.

3 POST routes read the token that called them. They are not market data. Each one is also an MCP tool, listed in tools/list for every token. Paths and tools are on API overview.

Send the same bearer token as any other route. The client_id in the response is that token’s api_user_id. Putting client_id or api_user_id in the body is 422. A disabled market group does not hide these routes: ENDPOINT_NOT_ENABLED does not apply. enabled_groups tells you which market groups this token may call.

What every call shares

  • Method is POST. Another method is 405 and Allow: POST.
  • An empty body needs no Content-Type. A non-empty body must be application/json. A parameter after ; is fine. Anything else is 422: Content-Type must be application/json.
  • The body is one JSON object, at most 1024 bytes. Over that is 413 PAYLOAD_TOO_LARGE. Market routes use 4 KiB. The proxy still rejects any body over 64 KiB.
  • An array, a string, or broken JSON is 422: Body must be a JSON object. A repeated key is 422: Duplicate field "days". with the key name. That is the same parser as a market route. A nested object is not a separate error: days still has to be an integer, and clients and status still take no fields. The 422 has an error.details array in the same form as a market route (see Errors).
  • A success is ok, data, and meta.request_id. There is no data.items, no cursor, and no response cache. Age is not set.
  • Timestamps are ISO 8601 with an America/New_York offset, to the second: 2026-10-20T12:31:07-04:00. No milliseconds, no Z, no bare wall time. Counts are 0, not null.

MCP tools

ToolRouteArguments
get_account_usagePOST /api/v1/account/usage{"days": 7} or none (30)
get_account_clientsPOST /api/v1/account/clientsnone
get_account_statusPOST /api/v1/account/statusnone

The arguments are the REST body; no arguments is an empty body. The answer is the same envelope in structuredContent, with isError: true on a failure. A tool call uses the same validation, the same 1024-byte cap, and the same account counter as REST: a REST call and a tool call in one minute count twice. get_account_status is the connection test; there is no separate ping. The arguments never carry the token.

Two limits

After a good auth, the shared user budget still counts. The default is 120 requests per client id per ET minute, shared with market REST and MCP. The response field rate_limit.per_minute is that number. The RateLimit-* headers are that window too. Over it, the code is RATE_LIMIT_EXCEEDED, and the account counter does not move. Headers and the other shared codes are on Rate limits and Errors.

These 3 routes then have their own counter: 30 requests per client id per ET minute (ACCOUNT_RATE_LIMIT). Over it, the HTTP status is 429 and the code is ACCOUNT_RATE_LIMITED. Retry-After is whole seconds until the next America/New_York minute, at least 1. meta.rate_limit.scope is account, remaining is 0, and reset_at is that next minute.

429 application/json
{
  "ok": false,
  "error": {
    "code": "ACCOUNT_RATE_LIMITED",
    "message": "Account endpoint rate limit exceeded.",
    "request_id": "req_example_01"
  },
  "meta": {
    "rate_limit": {
      "scope": "account",
      "limit": 30,
      "remaining": 0,
      "reset_at": "2026-10-20T12:32:00-04:00"
    }
  }
}

A 422 or a 413 that reaches the handler has already taken one user attempt and one account-limit slot. Neither is usage. Auth failures never reach the account counter.

1 / 3

Usage

POST/api/v1/account/usageMCP get_account_usage

How many calls this token made, split by ET day and by operation. The window is days ET calendar days, ending today, including today. Missing days are filled with zeros.

NameTypeRequiredValues
daysintegerNo1 to 90. Omit it for 30. A boolean, a fraction, or a string is 422: days must be an integer from 1 to 90.

Any other field is 422. The message names the first unknown field in alphabetical order, cut at 40 characters: Unknown field: client_id.

curl
curl --request POST \
  --url "${TANUKI_BASE_URL}/api/v1/account/usage" \
  --header "Authorization: Bearer ${TANUKI_TOKEN}" \
  --header "Content-Type: application/json" \
  --header "Accept: application/json" \
  --data '{"days":2}'

The sample asks for two days so the shape is visible. The default window is 30. rate_limit.per_minute is the shared user budget, here 120, not the account limit of 30. enabled_groups is sorted. A default token lists every catalog group. The three names in this sample are an explicit list, not the default.

200 application/json
{
  "ok": true,
  "data": {
    "client_id": "ab12cd34",
    "days": 2,
    "rate_limit": {"per_minute": 120},
    "enabled_groups": ["discovery", "fundamentals", "volatility"],
    "totals": {
      "requests": 14,
      "api_requests": 11,
      "mcp_requests": 3,
      "bytes_out": 184320,
      "bytes_in": 4096
    },
    "today": {
      "date": "2026-10-20",
      "requests": 6,
      "api_requests": 4,
      "mcp_requests": 2,
      "bytes_out": 81920,
      "bytes_in": 2048,
      "by_endpoint": [
        {"operation": "list_symbols", "group": "discovery", "api": 4, "mcp": 0, "total": 4},
        {"operation": "list_volatility_iv_metrics", "group": "volatility", "api": 0, "mcp": 2, "total": 2}
      ]
    },
    "daily": [
      {
        "date": "2026-10-19",
        "requests": 8,
        "api_requests": 7,
        "mcp_requests": 1,
        "bytes_out": 102400,
        "bytes_in": 2048
      },
      {
        "date": "2026-10-20",
        "requests": 6,
        "api_requests": 4,
        "mcp_requests": 2,
        "bytes_out": 81920,
        "bytes_in": 2048
      }
    ],
    "by_endpoint": [
      {"operation": "list_symbols", "group": "discovery", "api": 9, "mcp": 0, "total": 9},
      {"operation": "list_volatility_iv_metrics", "group": "volatility", "api": 2, "mcp": 3, "total": 5}
    ],
    "as_of": "2026-10-20T12:31:07-04:00"
  },
  "meta": {"request_id": "req_example_01"}
}
FieldTypeMeaning
totalsobjectrequests, api_requests, mcp_requests, bytes_out, bytes_in across the window.
daily[]arrayOne object per ET date, oldest first. date plus the five totals. No by_endpoint. Zero when nothing was stored.
todayobjectThe ET today row, plus by_endpoint for today only.
by_endpoint[]arrayoperation, group, api, mcp, total. Only operations with a positive count. total descending, then operation ascending. group is the market path segment. Today’s list is under today. The window list is the sibling of daily.
as_ofstringWhen this payload was built, ET, to the second.

Today’s row is the usage_daily total plus the minute buckets that have not been flushed yet. The live window is the last min(30, max(3, ceil(flush seconds / 60) + 2)) ET minutes. The default flush period is 60 seconds, so the window is 3 minutes. A bucket that is mid-claim can be missing for a few seconds. It is not counted twice. These account calls do not appear in by_endpoint, because they do not write usage. MCP discovery bytes are included in bytes_in and bytes_out. Discovery calls are not included in the request counts.

2 / 3

Clients

POST/api/v1/account/clientsMCP get_account_clients

Recent API and MCP clients for this token: transport, address, and user agent. The body is empty or {}. Any field is 422: This endpoint takes no fields.

curl
curl --request POST \
  --url "${TANUKI_BASE_URL}/api/v1/account/clients" \
  --header "Authorization: Bearer ${TANUKI_TOKEN}" \
  --header "Content-Type: application/json" \
  --header "Accept: application/json" \
  --data '{}'

Rows are ordered by last seen, newest first, at most 50. first_used_at and last_used_at are the earliest and latest times among the rows in this response. When the list is empty, both are null. A row with a timestamp that cannot be read is left out. user_agent is "" when none was stored. It is never null. Addresses in this example are the documentation range 203.0.113.0/24.

200 application/json
{
  "ok": true,
  "data": {
    "client_id": "ab12cd34",
    "first_used_at": "2026-09-01T09:15:00-04:00",
    "last_used_at": "2026-10-20T12:31:07-04:00",
    "clients": [
      {
        "transport": "api",
        "ip": "203.0.113.10",
        "user_agent": "tanuki-docs-example/1.0",
        "first_seen_at": "2026-10-01T09:15:00-04:00",
        "last_seen_at": "2026-10-20T12:31:07-04:00",
        "requests": 42
      },
      {
        "transport": "mcp",
        "ip": "203.0.113.11",
        "user_agent": "",
        "first_seen_at": "2026-09-01T09:15:00-04:00",
        "last_seen_at": "2026-10-02T16:00:00-04:00",
        "requests": 3
      }
    ],
    "as_of": "2026-10-20T12:31:07-04:00"
  },
  "meta": {"request_id": "req_example_01"}
}

transport is api or mcp. These account routes are not tracked, so they do not add a row. The list refreshes when the closed seen-bucket is flushed, on the same period as usage flush. The default period is 60 seconds. Rows are kept for 90 days.

3 / 3

Status

POST/api/v1/account/statusMCP get_account_status

Whether the service can answer, when the builder last finished, which groups this token may call, and the shared per-minute budget. The body is empty or {}. Any field is 422: This endpoint takes no fields.

curl
curl --request POST \
  --url "${TANUKI_BASE_URL}/api/v1/account/status" \
  --header "Authorization: Bearer ${TANUKI_TOKEN}" \
  --header "Accept: application/json"

The sample above omits the body. That is valid. {} is also valid, and then Content-Type: application/json is required.

200 application/json
{
  "ok": true,
  "data": {
    "service": "operational",
    "data_as_of": "2026-10-20T12:30:00-04:00",
    "client_id": "ab12cd34",
    "enabled_groups": ["discovery", "fundamentals", "volatility"],
    "rate_limit": {"per_minute": 120},
    "server_time": "2026-10-20T12:31:07-04:00"
  },
  "meta": {"request_id": "req_example_01"}
}
FieldTypeMeaning
servicestringoperational when the health check is ok. degraded otherwise, including when the check itself fails.
data_as_ofstring or nullThe last finished builder run, converted to ET and cut to the second. null when that timestamp is missing, not JSON, or has no timezone.
server_timestringWhen this payload was built, ET, to the second. Not cached.
enabled_groupsarray of stringsThe same sorted list as on usage.
rate_limit.per_minuteintegerThe shared user budget. Not the account limit of 30.

service and data_as_of may be up to 10 seconds old. Each app process keeps that pair that long. server_time is read again on every call.

Errors on these routes

HTTPCodeWhen
401AUTH_REQUIREDNo bearer token. WWW-Authenticate: Bearer is set.
401INVALID_CREDENTIALSThe token is malformed or not recognized.
403API_USER_INACTIVE / API_USER_SUSPENDED / IP_BLOCKEDThe same gate as a market route.
403DISCORD_REQUIREDOnly when Discord is required and this subscription has none connected.
422VALIDATION_ERRORBad JSON, the wrong content type, an unknown field, a repeated key, or a bad days. error.details names the field. Do not retry the same body.
413PAYLOAD_TOO_LARGEThe body is over 1024 bytes.
429RATE_LIMIT_EXCEEDEDThe shared ET-minute budget is spent. The account counter does not move.
429ACCOUNT_RATE_LIMITEDThe account budget is spent. Wait Retry-After.
405METHOD_NOT_ALLOWEDAllow names POST.
503DATA_UNAVAILABLEThe account database cannot be read. Retry-After is 5. Usage and clients need it. Status does not.
503OVERLOADEDEvery Redis slot is busy. Retry-After is 1. Wait, then retry.
503STATE_UNAVAILABLEShared state cannot be reached: the connection failed or timed out. Retry-After is 1.

Own-database failure on usage or clients is 503 DATA_UNAVAILABLE. Status still answers from the health check and the builder timestamp; it does not query that table. The full code list is on Errors.

TanukiTrade API and MCP. Derived options analytics for your code and for agents. Not investment advice. tanukitrade.com