> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hashdit.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Token Approval Security

> Synchronous, risk-scored view of a wallet's outstanding token approvals.

<Note>Request fields accept both `camelCase` and `snake_case`. See [Request Field Names](/#request-field-names).</Note>

For each approval: the raw allowance, the realistic exposure after capping to the current balance, the USD value at risk, and — opt-in — risk assessments for the spender, the token, and the owner.

A top-level `summary` aggregates total USD at risk so an approval-manager screen can render its banner without walking the array.

## Request Notes

### Supported Chains

Only Ethereum (`1`), BNB Smart Chain (`56`), and TRON. See [Supported Chains](/api-reference/endpoint/token-approval-security/supported-chains). Any other `chainId` is rejected with `400`.

Responses are cached \~60 seconds per unique query + flag combination. A revocation the user just made may take up to that long to disappear.

On TRON, responses echo case-preserved base58 — TRON encodes its checksum in letter casing, so a lowercased TRON address is not usable. Use the returned form for anything sent to a node or explorer.

### Request Combinations

At least one of `owner` or `spender` is required. `token` cannot be sent on its own.

| Combination                             | Allowed    |
| --------------------------------------- | ---------- |
| `owner` / `spender` / `owner`+`spender` | Yes        |
| any of the above + `token`              | Yes        |
| `token` alone                           | No — `400` |

### Opt-In Fields

These response fields are added only when you set the matching request flag to `true`. All flags default to `false`. Enable only the enrichments you render — each flag adds a lookup and its own latency. For a wallet-facing approval list, `includeBalance` + `includeSpenderRisk` is usually the right pairing.

| Request flag          | Adds to each row             |
| --------------------- | ---------------------------- |
| `includeBalance`      | `balance`                    |
| `includeAddressTypes` | `spender_type`, `owner_type` |
| `includeSpenderRisk`  | `spender_risk`               |
| `includeTokenRisk`    | `token_risk`                 |
| `includeOwnerRisk`    | `owner_risk`                 |

If the flag is `false` or omitted, that field is **left out of the JSON** — you did not ask for it.

If the flag is `true`, the field is **always in the JSON**. A value means the lookup succeeded. `null` means the lookup failed; the rest of the row is still valid. Prefer retrying over showing an error screen.

In JavaScript, check that the key exists (`"spender_risk" in row`) before reading it. Do not use `row.spender_risk !== null` — a missing key is `undefined`, and `undefined !== null` is `true`.

Always send `includeBalance: true` on an approval-management screen. It turns "Unlimited / Unlimited" into "\$4.50 at risk now, unlimited going forward", and it lets an unlimited approval on an emptied wallet correctly report `valid_allowance: "0"`.

## Response Notes

### USD Fields

Every row always includes both fields. They are never `null`. They price different amounts and can disagree.

`amount_raw`, `valid_allowance`, and `balance` are uint256-scale strings — parse with `BigInt`. The USD fields are ordinary floats when they are numbers.

| Field                           | Prices                                          | Question it answers                                 |
| ------------------------------- | ----------------------------------------------- | --------------------------------------------------- |
| `affected_usd_amount`           | `valid_allowance`                               | How much can be taken **right now**?                |
| `potential_affected_usd_amount` | `amount_raw` (not capped to the wallet balance) | How much could be taken if the wallet is re-funded? |

Each value is one of three types. Check the type before formatting — do not assume a number.

| Value         | Meaning                                                                                                          | Show as                |
| ------------- | ---------------------------------------------------------------------------------------------------------------- | ---------------------- |
| A number      | A finite USD figure. `0` means nothing is at risk.                                                               | `$1,234.56` or `$0`    |
| `"Unlimited"` | The allowance is unbounded (infinite approval). No dollar figure is invented.                                    | `Unlimited`            |
| `"Unknown"`   | Decimals or price were missing, so USD could not be computed. This is **not** zero — funds may still be at risk. | `Unknown` — never `$0` |

With `includeBalance: true`, an infinite on-chain approval often returns both a number and `"Unlimited"`:

* `affected_usd_amount`: `4.5` — the wallet holds \$4.50, so only that can be taken today
* `potential_affected_usd_amount`: `"Unlimited"` — the allowance is still unbounded, so later deposits can be taken too

Without `includeBalance`, both fields are often `"Unlimited"`, even if the wallet is empty.

### Risk Object

Returned on a row when you set `includeSpenderRisk`, `includeTokenRisk`, or `includeOwnerRisk`.

| Field                               | Use it for                                              |
| ----------------------------------- | ------------------------------------------------------- |
| `is_risky`                          | The yes/no verdict. Show a warning when this is `true`. |
| `risk_level` / `risk_level_display` | Color and label in the UI (table below).                |
| `risk_details`                      | Logs and support tickets only.                          |

Do not decide “risky” from `risk_level` yourself (for example `risk_level >= 4`). Use `is_risky`.

Do not write logic against `risk_details` values (for example `"blacklist_manual"`). Those strings are not a stable list and can change. The array is `[]` when `is_risky` is `false`.

| `risk_level` | `risk_level_display` | Suggested UI                                            |
| ------------ | -------------------- | ------------------------------------------------------- |
| `-1`         | `"Unknown"`          | Grey. Not in the database — **not** a safety statement. |
| `0`          | `"Trusted"`          | Green. Affirmatively verified.                          |
| `1`–`3`      | `"Low Risk"`         | Yellow / informational.                                 |
| `4`          | `"Medium Risk"`      | Orange. Ask the user to confirm.                        |
| `≥ 5`        | `"High Risk"`        | Red. Confirmed malicious — block or strongly warn.      |

When `risk_level` is `0`, the address is considered safe. `-1` means “no data” — do not consider the address as safe.

### `summary` Totals

`summary` is a page-level banner. Do not add up the rows yourself.

* The same token balance approved to several spenders is counted **once**, not once per spender.
* Only rows with a **number** USD value are included. `"Unlimited"` and `"Unknown"` add `0`.
* So `0` can mean “nothing at risk” or “every row on this page is unlimited or unpriceable.”
* If any row is `"Unlimited"` or `"Unknown"`, treat the total as a floor: *at least \$X, plus N unlimited approvals*.
* `includeBalance: true` turns most unlimited rows into a number and closes that gap.

`valid_approved_tokens` is a lookup table for tokens HashDit has indexed (name, address, price, decimals). Unindexed tokens still appear in `approval_status`; they are just missing from this list.

`hasMore` is evaluated *before* `filter: "only-valid"` runs, so a filtered page can show `count` well below `limit` while `hasMore` is still `true`. Do not stop paginating on a short `count`.


## OpenAPI

````yaml POST /v2/hashdit/token-approval-security
openapi: 3.1.0
info:
  title: HashDit API
  description: >-
    HashDit threat intelligence API for domain security analysis, blockchain
    address classification, address poisoning detection, address security
    assessment, transaction simulation, transaction security, token security,
    Solana token security, token approval security, and EIP-712 signature
    security
  version: 2.0.0
servers:
  - url: https://service.hashdit.io
security: []
paths:
  /v2/hashdit/token-approval-security:
    post:
      summary: Token Approval Security
      description: Synchronous, risk-scored view of a wallet's outstanding token approvals.
      operationId: tokenApprovalSecurity
      parameters:
        - name: X-API-KEY
          in: header
          description: Your HashDit API key
          required: true
          schema:
            type: string
      requestBody:
        description: >-
          Approval query. At least one of `owner` or `spender` is required.
          `token` cannot be sent on its own.
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TokenApprovalSecurityRequest'
            examples:
              Owner with balance and spender risk:
                summary: BSC owner — typical approval-manager query
                value:
                  chainId: '56'
                  owner: '0x26d61f23bbc94d7b2139f9c581722bf4e8b64b87'
                  includeBalance: true
                  includeSpenderRisk: true
              TRON:
                summary: TRON — `chainId` as `"tron"`
                value:
                  chainId: tron
                  owner: TUAEpR16Th4pLbjECPVYTgvPyStNKyB54h
                  includeBalance: true
      responses:
        '200':
          description: Successful response. Envelope is `{code, status, data}`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenApprovalSecurityResponse'
              examples:
                Approvals with balance cap:
                  summary: >-
                    Row 1: $4.50 at risk today, unlimited going forward. Row 2:
                    risky spender.
                  value:
                    code: '0'
                    status: ok
                    data:
                      count: 2
                      hasMore: false
                      approval_status:
                        - token: '0x55d398326f99059ff775485246999027b3197955'
                          owner: '0x26d61f23bbc94d7b2139f9c581722bf4e8b64b87'
                          spender: '0x10ed43c718714eb63d5aa57b78b54704e256024e'
                          amount_raw: >-
                            115792089237316195423570985008687907853269984665640564039457584007913129639935
                          valid_allowance: '4500000000000000000'
                          token_decimal: 18
                          affected_usd_amount: 4.5
                          potential_affected_usd_amount: Unlimited
                          token_name: Binance-Peg BSC-USD
                          balance: '4500000000000000000'
                          spender_risk:
                            is_risky: false
                            risk_level: 0
                            risk_details: []
                            risk_level_display: Trusted
                        - token: '0xd8a2ae43fd061d24acd538e3866ffc2c05151b41'
                          owner: '0x26d61f23bbc94d7b2139f9c581722bf4e8b64b87'
                          spender: '0xb0b195aefa3650a6908f15cdac7d92f8a5791b0b'
                          amount_raw: '9999999999999999999999'
                          valid_allowance: '9999999999999999999999'
                          token_decimal: 18
                          affected_usd_amount: 0.02
                          potential_affected_usd_amount: 0.02
                          token_name: null
                          balance: '9999999999999999999999'
                          spender_risk:
                            is_risky: true
                            risk_level: 5
                            risk_details:
                              - blacklist_manual
                            risk_level_display: High Risk
                      valid_approved_tokens:
                        - token_name: Binance-Peg BSC-USD
                          token_address: '0x55d398326f99059ff775485246999027b3197955'
                          token_price: 1
                          token_decimal: 18
                      summary:
                        total_affected_usd_amount: 4.52
                        total_potential_affected_usd_amount: 4.52
                        risky_approval_count: 1
                        total_risky_usd_amount: 0.02
                        risky_approval_indices:
                          - 1
        '400':
          description: >-
            Bad request — malformed or missing field, unsupported chain, invalid
            address, `token` alone, or an invalid `filter`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CurrentServiceError'
              examples:
                Invalid request:
                  summary: Malformed or missing field
                  value:
                    code: '-1'
                    status: invalid request
                    detail: …
                Token alone:
                  summary: '`token` without `owner` or `spender`'
                  value:
                    code: '-1'
                    status: invalid request
                    detail: …
                Unsupported chain:
                  summary: Chain not enabled for this endpoint (only `1`, `56`, TRON)
                  value:
                    code: '-1'
                    status: unsupported chain
                    detail: 'unsupported chainId: 137'
                Invalid address:
                  summary: Address fails the format check for that chain
                  value:
                    code: '-1'
                    status: invalid address
                    detail: …
                Invalid filter:
                  summary: '`filter` must be `"only-valid"` if sent'
                  value:
                    code: '-1'
                    status: invalid request
                    detail: …
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'
components:
  schemas:
    TokenApprovalSecurityRequest:
      type: object
      required:
        - chainId
      properties:
        chainId:
          type: string
          description: >-
            `int` or `string`. Only `1` (Ethereum), `56` (BSC), or `728126428` /
            `"tron"` (TRON). Any other chain is rejected with `400`. See
            [Supported
            Chains](/api-reference/endpoint/token-approval-security/supported-chains).
          example: '56'
        owner:
          type: string
          description: >-
            Owner / approver address. At least one of `owner` or `spender` is
            required.
          example: '0x26d61f23bbc94d7b2139f9c581722bf4e8b64b87'
        spender:
          type: string
          description: Spender address. At least one of `owner` or `spender` is required.
        token:
          type: string
          description: >-
            Restrict to one token contract. Cannot be sent on its own — `token`
            alone returns `400`.
        page:
          type: integer
          default: 1
          description: 1-based page number. Values below 1 are treated as 1.
        limit:
          type: integer
          default: 50
          description: >-
            Rows per page. Ceiling `100` — higher values are clamped, not
            rejected.
        filter:
          type: string
          description: >-
            Only `"only-valid"` is accepted; it drops rows whose
            `valid_allowance` is `0`. Any other value is a validation error.
        includeBalance:
          type: boolean
          default: false
          description: Read each token's live on-chain balance for the owner.
        includeSpenderRisk:
          type: boolean
          default: false
          description: Add `spender_risk` to every row.
        includeTokenRisk:
          type: boolean
          default: false
          description: Add `token_risk` to every row.
        includeOwnerRisk:
          type: boolean
          default: false
          description: Add `owner_risk` to every row.
        includeAddressTypes:
          type: boolean
          default: false
          description: Classify each spender and owner as `"eoa"` or `"contract"`.
    TokenApprovalSecurityResponse:
      type: object
      properties:
        code:
          type: string
          description: API status code. `"0"` indicates success.
          example: '0'
        status:
          type: string
          example: ok
        data:
          $ref: '#/components/schemas/TokenApprovalSecurityData'
    CurrentServiceError:
      type: object
      properties:
        code:
          type: string
          description: >-
            Error code. Typically `-1` for client errors, `-2` for server
            errors, or `401` / `429` for auth and rate limits.
        status:
          type: string
          description: Short error status string.
        detail:
          type: string
          description: Human-readable explanation of the error.
      required:
        - code
        - status
    TokenApprovalSecurityData:
      type: object
      properties:
        count:
          type: integer
          description: Rows in `approval_status`, after `filter`.
        hasMore:
          type: boolean
          description: >-
            `true` when this page came back full. Evaluated *before* `filter:
            "only-valid"` runs — a filtered page can have `count` well below
            `limit` while `hasMore` is still `true`.
        approval_status:
          type: array
          description: The approval rows, most-recent on-chain activity first.
          items:
            $ref: '#/components/schemas/TokenApprovalStatusRow'
        valid_approved_tokens:
          type: array
          description: >-
            Metadata for each unique indexed token in `approval_status`. Tokens
            HashDit has not indexed do not appear here, even though their
            approvals still appear in `approval_status`.
          items:
            $ref: '#/components/schemas/TokenApprovalValidToken'
        summary:
          $ref: '#/components/schemas/TokenApprovalSummary'
    MissingAPIKeyErrorResponse:
      type: object
      properties:
        code:
          type: string
          description: '`401`'
          example: '401'
        status:
          type: string
          description: Missing API key
          example: Missing API key
        detail:
          type: string
          description: Missing API key
          example: Missing API key
      required:
        - code
        - status
        - detail
    RateLimitExceededErrorResponse:
      type: object
      properties:
        code:
          type: string
          description: '`429`'
          example: '429'
        status:
          type: string
          description: '`error`'
          example: error
        message:
          type: string
          description: Rate limit exceeded
          example: Rate limit exceeded
    TokenApprovalStatusRow:
      type: object
      required:
        - token
        - owner
        - spender
        - amount_raw
        - valid_allowance
        - token_decimal
        - affected_usd_amount
        - potential_affected_usd_amount
        - token_name
      properties:
        token:
          type: string
          description: Token contract address.
        owner:
          type: string
          description: Owner / approver address.
        spender:
          type: string
          description: The party that can move the tokens.
        amount_raw:
          type: string
          description: >-
            Approved allowance, raw smallest units, uint256 decimal string.
            Overflows `Number` — parse as big-int.
        valid_allowance:
          type: string
          description: >-
            Realistic exposure. `min(amount_raw, balance)` when `includeBalance`
            is on and the balance was readable; otherwise equals `amount_raw`.
        token_decimal:
          type:
            - integer
            - 'null'
          description: '`null` when decimals could not be resolved.'
        affected_usd_amount:
          $ref: '#/components/schemas/TokenApprovalUsdAmount'
          description: >-
            USD value of `valid_allowance` — what can be taken right now. Never
            `null`.
        potential_affected_usd_amount:
          $ref: '#/components/schemas/TokenApprovalUsdAmount'
          description: >-
            USD value of `amount_raw`, never balance-capped — what could be
            taken if the wallet is re-funded. Never `null`.
        token_name:
          type:
            - string
            - 'null'
          description: '`null` when the token is not indexed.'
        balance:
          type:
            - string
            - 'null'
          description: >-
            Owner's current balance, raw units. Opt-in `includeBalance`: absent
            when the flag is `false`; present but possibly `null` when the flag
            is `true`.
        spender_type:
          type:
            - string
            - 'null'
          description: >-
            `"eoa"` or `"contract"`. Opt-in `includeAddressTypes`: absent when
            the flag is `false`; present but possibly `null` when the flag is
            `true`.
        owner_type:
          type:
            - string
            - 'null'
          description: >-
            `"eoa"` or `"contract"`. Opt-in `includeAddressTypes`: absent when
            the flag is `false`; present but possibly `null` when the flag is
            `true`.
        spender_risk:
          $ref: '#/components/schemas/TokenApprovalRisk'
          description: >-
            Opt-in `includeSpenderRisk`: absent when the flag is `false`;
            present but possibly `null` when the flag is `true`.
        token_risk:
          $ref: '#/components/schemas/TokenApprovalRisk'
          description: >-
            Opt-in `includeTokenRisk`: absent when the flag is `false`; present
            but possibly `null` when the flag is `true`.
        owner_risk:
          $ref: '#/components/schemas/TokenApprovalRisk'
          description: >-
            Opt-in `includeOwnerRisk`: absent when the flag is `false`; present
            but possibly `null` when the flag is `true`.
    TokenApprovalValidToken:
      type: object
      properties:
        token_name:
          type:
            - string
            - 'null'
        token_address:
          type: string
        token_price:
          type:
            - number
            - 'null'
          description: USD unit price at query time.
        token_decimal:
          type:
            - integer
            - 'null'
    TokenApprovalSummary:
      type: object
      properties:
        total_affected_usd_amount:
          type: number
          description: >-
            Total USD at risk on this page. Not the sum of every row — exposure
            is de-duplicated across spenders approved against the same token
            balance. Counts only rows with a finite figure; `"Unlimited"` and
            `"Unknown"` contribute nothing.
        total_potential_affected_usd_amount:
          type: number
          description: Same aggregation over `potential_affected_usd_amount`.
        risky_approval_count:
          type: integer
          description: Rows where any of the three risk objects is risky.
        total_risky_usd_amount:
          type: number
          description: Sum of `affected_usd_amount` across those risky rows.
        risky_approval_indices:
          type: array
          description: >-
            0-based positions of the risky rows in `approval_status`. Index
            straight in — no scan needed.
          items:
            type: integer
    TokenApprovalUsdAmount:
      type:
        - number
        - string
      description: >-
        Never `null`. A number (including `0`), `"Unlimited"`, or `"Unknown"`.
        Check the type before formatting. `"Unknown"` is not zero — do not
        render it as `$0`.
    TokenApprovalRisk:
      type: object
      properties:
        is_risky:
          type: boolean
          description: >-
            Whether this address warrants a red flag. The single boolean to
            branch on — do not re-derive it from `risk_level`.
        risk_level:
          type: integer
          description: >-
            0–5, higher is riskier. `-1` = address absent from HashDit's
            database.
        risk_details:
          type: array
          description: >-
            Opaque labels indicating what the finding was based on. Useful for
            support and logging; not a stable enum — do not branch on the
            values. Empty when `is_risky` is `false`.
          items:
            type: string
        risk_level_display:
          type: string
          description: Human-readable label. Always populated.
  responses:
    Unauthorized:
      description: >-
        Missing or invalid `X-API-KEY`. This is the response customers see on
        current-service endpoints.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/MissingAPIKeyErrorResponse'
          examples:
            Missing API key:
              summary: Missing or invalid API key
              value:
                code: '401'
                status: Missing API key
                detail: Missing API key
    UnprocessableEntity:
      description: Schema validation failed. `detail` names the failing field path.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/CurrentServiceError'
          examples:
            Field required:
              summary: Schema validation
              value:
                code: '-1'
                status: invalid request
                detail: 'chainId: field required'
            Field path:
              summary: detail names the failing field path
              value:
                code: '-1'
                status: invalid request
                detail: evm_transactions[0].from
    RateLimitExceeded:
      description: Rate limit exceeded
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/RateLimitExceededErrorResponse'
          examples:
            Rate limit exceeded:
              summary: Rate limit exceeded error
              value:
                code: '429'
                status: error
                message: Rate limit exceeded
    InternalServerError:
      description: Server-side failure
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/CurrentServiceError'
          examples:
            Internal server error:
              summary: Internal server error
              value:
                code: '-2'
                status: internal server error
                detail: internal server error

````