Skip to main content

Tok{In} API

The Tok{In} API offers an array of features that can help determine whether a token is malicious or not. From storage checks, to simulation and static analysis features. The Tok{In} API supports all ERC20 contracts on Ethereum, Binance Smart Chain, Arbitrum, Base, Avalanche, and Robinhood Chain (New). The features cover all Uniswap V2/V3/V4 forks, Aerodrome V2/Slipstream (Base), and launchpad tokens: both bonding curves (four.meme and flap.sh on BSC, trench.today and flap on Robinhood Chain) and launchpads that seed a standard DEX pool directly (Pons, Noxa, Virtuals, Doppler, varo.rialto, bow.fun and letscash.fun on Robinhood Chain).

For AI coding agents (Claude Code, Cursor, Copilot, etc.): a migration guide written specifically for you lives at AGENTS.md alongside this page (canonical URL: https://docs.dedaub.com/docs/token_safety/AGENTS). Read it end-to-end before changing any code that integrates with Tok{In}: it covers the GoPlus → Tok{In} drop-in swap, field mappings, always-null fields, and common migration pitfalls.

Usage​

curl -X 'GET' \
'https://tokin-api.dedaub.com/token/{chain}/{token_address}' \
-H 'accept: application/json' \
-H 'X-API-Key: YOUR-API-KEY'

chain: The slug or the EVM chain id of the chain (see Available Chains) token_address: The address of the token

Optional query parameters:

ParameterDefaultEffect
response_formatdedaubgoplus gives the GoPlus-compatible shape. See Response Formats.
include_token_pricefalsetrue fills the four price fields. Without it they are null. See Price fields.
refreshfalsetrue computes the state part of the analysis again (pools, state features, source analysis). It needs an enterprise plan or an admin key. If not, the API answers 403.
redecompilefalsetrue runs the decompiler again. It needs an enterprise plan or an admin key. If not, the API answers 403.

User Interface​

A Swagger UI for easier interaction with the API is available at https://tokin-api.dedaub.com . The field descriptions in the Swagger UI are short. This page is the full reference.

Available Chains​

ChainSlugChain ID
Ethereumethereum1
Binance Smart Chainbinance56
Arbitrumarbitrum42161
Basebase8453
Avalancheavalanche43114
Robinhood Chain (New)robinhood4663

DEX coverage is most complete on Ethereum, with more pools being added to the other chains over time.

Quote tokens per chain​

Pool discovery and liquidity valuation are anchored on each chain's wrapped native token plus its canonical ~$1 stablecoins. That anchor set is not the same on every chain:

ChainWrapped nativeStable anchors
Ethereum, Arbitrum, BaseWETHUSDC, USDT
Binance Smart ChainWBNBUSDC, USDT
AvalancheWAVAXUSDC, USDT
Robinhood ChainWETHUSDG, USDE

Robinhood Chain has no canonical USDC or USDT: USDG and USDE are the real ~$1 tokens there, and any USDC/USDT-named deployment on 4663 is an impostor. Pools quoted in a non-anchor token can still be valued when a price is available (see Notes on liquidity).

Response Formats​

The Tok{In} API supports multiple response formats via the response_format query parameter.

Available Formats​

FormatDescription
dedaubDefault format
goplusGoPlus-compatible format

Usage​

# Default (Dedaub format)
GET /token/{chain}/{token_address}

# GoPlus format
GET /token/{chain}/{token_address}?response_format=goplus

GoPlus Format Mapping​

The GoPlus format transforms the default Tok{In} schema to a GoPlus-compatible one, with the following caveats.

Direct Mappings​

Dedaub FieldGoPlus FieldNotes
is_open_sourceis_open_sourceBoolean → "1"/"0" string
is_proxyis_proxyBoolean → "1"/"0" string
mint_or_burn_functionis_mintableBoolean → "1"/"0" string
owner_addressowner_addressDirect string. Not a renouncement signal, see caveat 7
can_selfdestructselfdestructBoolean → "1"/"0" string
external_callexternal_callBoolean → "1"/"0" string
is_in_dexis_in_dexBoolean → "1"/"0" string
receive_taxbuy_taxPercentage as string ("1" is 1%); pool-aware, see caveat 4; scale, see caveat 8
send_taxsell_taxPercentage as string ("1" is 1%); pool-aware, see caveat 4; scale, see caveat 8
cannot_buycannot_buyBoolean → "1"/"0" string
tax_can_be_modifiedslippage_modifiableBoolean → "1"/"0" string. Not owner-gated
trading_cooldowntrading_cooldownBoolean → "1"/"0" string
creator_addresscreator_addressDirect string
creator_percentcreator_percentPercentage as string ("0.02" is 0.02%); scale, see caveat 8
is_launchpad_tokenlaunchpad_tokenBoolean → "1"/"0" string

Computed Mappings​

GoPlus FieldDerived FromLogic
transfer_pausablecannot_buy, pause_status_can_be_modifiedTrue if either is True. Reads pause_status_can_be_modified after the owner gate
is_blacklistedhas_blacklist_or_whitelistSame value (temporary)
is_whitelistedhas_blacklist_or_whitelistSame value (temporary)
is_anti_whalehas_trading_cap, has_position_capTrue if either is True
anti_whale_modifiabletrading_cap_can_be_modified, position_cap_can_be_modifiedTrue if either is True. Reads both flags after the owner gate
hidden_ownerallowance_bypass, privileged_spendersTrue when the bypass is address specific, so at least one address is named
owner_change_balanceowner_can_change_balanceSame value. null while the owner simulation is off. It is off today

Fields Without Direct Equivalent (returns null)​

GoPlus FieldReason
cannot_sell_allNot currently tracked
can_take_back_ownershipNot currently tracked
gas_abuseNot currently tracked
personal_slippage_modifiableNot currently tracked
lp_total_supplyNot currently tracked
is_airdrop_scamNot currently tracked
trust_listNot currently tracked
holder_countNot currently tracked
owner_balanceNot currently tracked
owner_percentNot currently tracked

Caveats​

  1. is_blacklisted / is_whitelisted: Currently both map from has_blacklist_or_whitelist. We cannot distinguish between blacklist-only, whitelist-only, or both.

  2. Boolean Format: GoPlus uses string format ("1" / "0") instead of booleans. All boolean fields are converted accordingly.

  3. Tax Terminology: Our send_tax maps to GoPlus sell_tax, and our receive_tax maps to GoPlus buy_tax. Note the direction inverts: code computing slippage must follow the rename.

  4. buy_tax / sell_tax are pool-aware, and may disagree with the contract-level tax. In goplus mode these are taken from the first pool in dex[] that reports a tax, falling back to the contract-level receive_tax / send_tax only when no pool reports one. The pool figure reflects what a real swap would cost and is usually the more useful number, but it means buy_tax in a goplus response can differ from receive_tax in the equivalent dedaub response for the same token. If you compare the two formats, expect this and prefer the pool figure for trade decisions.

  5. is_honeypot is always null. It is present in the response shape but not yet produced. Integrations that gate trades on is_honeypot === "1" will treat every token as safe after migrating. Derive an equivalent from cannot_buy, the per-pool dex[].buy_success / sell_success (dedaub mode), and the tax fields; or block while the field is unavailable. See Reading buy_success / sell_success first, because the null/"pending" states matter for exactly this decision.

  6. hidden_owner is narrower than it looks: it is "1" only when the bypass is address-specific. A universal bypass (where the allowance check is skipped for everyone) sets allowance_bypass but leaves privileged_spenders empty, and maps to "0" because no undisclosed owner exists. It is null when the check could not run, which is not the same as "0". Use the Dedaub format if you need to tell those three apart, or to read the offending addresses.

  7. No GoPlus field carries the ownership verdict or the timebomb result. ownership_renounced, timebomb_status and timebomb_deadlines are only in the dedaub format. Do not use owner_address to decide if ownership is renounced. It is the answer of an owner getter, and it is null when no getter answered. In that case the verdict can be "renounced" or "not determined" (see ownership_renounced). transfer_pausable and anti_whale_modifiable read the setter flags after the owner gate. So "0" can mean that the code has a setter, but the owner is gone. slippage_modifiable is not owner-gated.

  8. buy_tax, sell_tax and creator_percent are percentages. "1" is 1%. The GoPlus API uses a 0–1 scale for these fields, where "0.01" is 1%. A client that reads these values on the GoPlus scale gets a value that is 100 times too high. holders[].percent uses the 0–1 scale of GoPlus. The Swagger UI text for these fields says 0–1 today. That text is wrong. Use the scale on this page.

Static Analysis Features​

We leverage the Gigahorse decompiler to analyze contracts whose source code is unavailable. The decompiled bytecode is checked for patterns commonly associated with undesirable behaviors in ERC20 tokens.

mint_or_burn_function:​

There is a public function that changes the token's total supply.

can_selfdestruct:​

The contract contains a selfdestruct call. (Note: The EVM no longer supports the SELFDESTRUCT opcode, so this is now a no-op.)

transfer_guarded_by_storage:​

The transfer function's execution depends on a storage variable (e.g., a flag or mapping check). This is a signal that transfers can be conditionally blocked by the contract owner or via some internal logic.

transfer_from_guarded_by_storage:​

The transferFrom function's execution depends on a storage variable. Same implications as transfer_guarded_by_storage: it indicates that delegated transfers can be conditionally blocked.

trading_cooldown:​

An address must wait for a certain period after its last transfer before it can transfer tokens again.

view_function_modifies_state:​

A function with the signature of one of the view functions defined by EIP-20 updates a storage variable.

has_trading_cap:​

The amount of tokens transferred by a single call to transfer cannot exceed a value, either constant or held in storage.

has_trading_cap_functionality:​

There is code that could enforce a trading cap. This can be true even if the cap is disabled.

trading_cap_can_be_modified:​

The transfer amount cap is held in storage and there is a function that can update it. The API sends false when ownership_renounced is true. When ownership_renounced is false or null, the flag keeps the static result. See Owner-gated setters.

has_position_cap:​

The amount of tokens an address can hold cannot exceed a value, either constant or held in storage.

has_position_cap_functionality:​

There is code that could enforce a position cap. This can be true even if the cap is disabled.

position_cap_can_be_modified:​

The position cap is held in storage and there is a function that can update it. The API sends false when ownership_renounced is true. When ownership_renounced is false or null, the flag keeps the static result. See Owner-gated setters.

has_owner_functionality:​

There is functionality that could let one or more addresses have access to functionality of the token that the rest do not. This can be true even if the owner has been renounced. For the current owner state, read ownership_renounced.

owner_address:​

The address that an owner getter gives: owner(), getOwner() or _owner(). The API calls the getters at the token address. For a proxy, the call goes through the proxy to the implementation code. When two getters answer, a live address comes before a burn address. null when no getter answered.

Do not use this field to decide if ownership is renounced. Read ownership_renounced.

ownership_renounced:​

The ownership verdict of the API.

ValueMeaning
trueRenounced. An owner getter gave a burn address. Or no owner getter answered, and the API did not find a wallet in a storage slot that the code uses for its owner checks. The API also gives true when it did not read that slot: there is no analysis, the code keeps its state at a different address, the slot is not a plain address slot, the read failed, or the read took more than 2 seconds.
falseLive. An owner getter gave an address that is not a burn address.
nullNot determined. No owner getter answered, but a wallet is in a storage slot that the code uses for its owner checks. Show it as unknown, never as renounced.

true with owner_address: null means that no owner getter answered. A contract with no owner getter can still give privileges in a different way. Examples are roles, or a contract address in storage, such as a multisig or a timelock. A contract in an owner slot does not change the verdict to null. A slot that the API did not read also leaves true. So in this case true does not prove that nobody has privileges. Read has_owner_functionality too.

When the value is true, the API sends false for three setter flags. See Owner-gated setters.

Since 2026-10-03, the API reads the owner of a proxy through the proxy, and a wallet in an owner slot gives null. Before that date, some proxies (for example USDC and BUSD on Ethereum) and some tokens with a wallet in an owner slot showed true.

⚠ Do not calculate this value from owner_address. The verdict also reads storage, so the two fields can disagree. For example, owner_address is null both when the verdict is true and when it is null.

⚠ Test the key, then the value. null is an answer: "not determined". A response that the API sent before 2026-08-24 does not have this key. Use a fallback only when the key is absent.

has_pause_functionality:​

The token has code that could pause trading. Can be true even if the current state of the contract does not allow pausing functionality to be re-enabled.

pause_status_can_be_modified:​

The pause status can be changed in the future. The API sends false when ownership_renounced is true. When ownership_renounced is false or null, the flag keeps the static result. See Owner-gated setters.

has_blacklist_or_whitelist:​

Addresses can be restricted from trading the token.

external_call:​

There is a call to another contract, whose target is not statically known and/or returns arbitrary data that are used by the token.

timebomb:​

The code compares a value with block.number or block.timestamp, so some behaviour can start or stop when the chain clock gets to that value. The API places each value against the chain clock and gives the result in timebomb_status and timebomb_deadlines.

timebomb is false when timebomb_status is passed or unreachable, also when the code has the check. Every other status keeps the static finding. A status never changes timebomb from false to true. "pending" means that the analysis has not run.

timebomb_status:​

Where the time checks of the code stand now. The API calculates this value each time it builds a response, not at last_scanned_at. A response can come from a cache that is at most two minutes old. So the value can change with time, also when no transaction occurs.

ValueMeaningtimebomb
armedA deadline is still ahead. The behaviour of the contract can change when the chain gets to it.true
passedEvery deadline has passed, and nobody can move a deadline. Its effect is now the current behaviour, and the other checks measure it.false
resettableNo deadline is ahead, but a value in storage can still change. The owner is live or not determined. Or a storage slot for the owner checks holds a value other than zero or a burn address, or the API could not read it. A storage value that is out of reach also gives this status while the owner is not gone. It is a deadline that nobody set yet.true
unreachableThe code compares the clock only with limits that no clock gets to, for example the overflow limit 2^32 or the uint48 maximum. These limits are not timers. A storage value at such a limit also gives this status, but only when the owner is gone.false
unknownThe API could not place a value against the clock. Or a deadline in storage has passed, but it is not old enough yet (see below). Also when the chain did not answer in 5 seconds.true
nullThere is no timebomb finding, or the analysis has not run.false or "pending"

When the values of one token point to different statuses, the API gives the first status that applies, in this order: armed, unknown, resettable, passed, unreachable. For example, the owner of BAT is not determined, but the API could not place its values, so the status is unknown.

How the API places a value:

  • It reads the clock that the contract reads. It runs block.number and block.timestamp in a call, and does not read a block header.
  • A value in the range of Unix time is a timestamp.
  • A block number counts only from about two years before the current block to ten years after it. It must also not be a multiple of 60. A duration such as 2592000 (30 days) looks like a block number, so an older value or a multiple of 60 is not placed.
  • A value that is more than ten years ahead of both clocks is a limit (unreachable).
  • Other values, for example zero, a small number, a duration or an old block number, are not placed. They give unknown, unless a deadline is still ahead (armed).
  • A deadline in storage counts as passed only when the owner is gone and the deadline is old enough. A timestamp must be more than one year old. For a block, the API counts the age at half the nominal block time. So the block must be about two years old by the nominal block time. The code can add a time to the stored value that the analysis does not see.

Live examples on Ethereum, 2026-10-03:

Tokentimebomb_statustimebombWhy
TORN 0x77777feddddffc19ff86db637967013e6c6a116cpassedfalseThe bytecode compares the clock with the constant timestamp 1612148437 (2021-02-01). It has passed, and it cannot change.
COMP 0xc00e94cb662c3520282e6f5717214004a7f26888unreachablefalseThe code compares the clock only with 0x100000000 (2^32).
UNI 0x1f9840a85d5af5bf1d1762f925bdaddc4201f984resettabletrueThe stored deadline in slot 0x2 (2024-01-01) has passed. The minter address in slot 0x1 can still write a new one.
BAT 0x0d8775f648430679a709e98d2b0cb6250d2887efunknowntrueThe code compares block.number with two stored block numbers from 2017. A block number that old is not placed.

timebomb_deadlines:​

The blocks and times that the code compares with its clock, where the API could place them. The list is in the order of estimated_unix_time.

FieldMeaning
sourceconstant: the value is in the bytecode and cannot change. storage: the value comes from a storage slot.
slotThe storage slot of a storage value. null for a constant.
clockblock (a block number) or timestamp (Unix seconds).
valueThe block number or the Unix timestamp.
passedtrue when the clock is at or after value.
estimated_unix_timeWhen the clock gets to value, in Unix seconds. Exact for a timestamp. For a block, an estimate from the nominal block time of the chain.

A limit (unreachable) and a value that the API could not place are not in the list. So the list can be empty, also when timebomb_status is unreachable or unknown. A deadline with passed: true does not turn timebomb off by itself. Read timebomb_status.

⚠ estimated_unix_time of a block is only an estimate. It can be wrong by a large amount, and it changes between two responses. On Binance Smart Chain it can move by minutes, because the real block time is shorter than the nominal block time. For a decision, use passed, which compares with the current block and is exact.

⚠ Arbitrum and Robinhood Chain. On these chains, block.number in a contract gives the Ethereum block number. So a block deadline is an Ethereum block, and its estimated_unix_time uses the Ethereum block time. Do not compare it with the L2 block number in an explorer.

UNI on Ethereum, live on 2026-10-03:

"ownership_renounced": true,
"timebomb": true,
"timebomb_status": "resettable",
"timebomb_deadlines": [
{
"source": "storage",
"slot": "0x2",
"clock": "timestamp",
"value": 1704067200,
"passed": true,
"estimated_unix_time": 1704067200
}
]

The deadline has passed, but timebomb stays true. UNI has no owner getter and its minter is a contract, so ownership_renounced is true. But slot 0x1 holds the minter address, so the stored deadline can still change, and the status is resettable.

has_tax_functionality:​

This token has a tax functionality implemented. This could be true even if the current tax is 0 as the taxes could have been wiped.

tax_can_be_modified:​

Tax value can be updated. This flag is not owner-gated: the API sends the static result also when ownership_renounced is true. See Owner-gated setters.

is_open_source:​

This indicates whether the contract's source code is available.

has_open_source_implementation:​

For proxy contracts: indicates whether the implementation contract's source code is verified. Only relevant when is_proxy is true. null for non-proxy contracts.

is_proxy:​

Indicates if the contract is a proxy contract.

balance_update_function:​

A list of function selectors (e.g., "0xa9059cbb") that modify the token's balance storage slot. Expected selectors are transfer and transferFrom; additional selectors may indicate hidden mint/burn or fee-skimming logic. The list can hold the same selector more than once.

"balance_update_function": [
"0xa9059cbb",
"0x23b872dd"
]

allowance_update_function:​

A list of function selectors that modify the token's allowance storage slot. Expected selectors include approve, transferFrom, increaseAllowance, and decreaseAllowance; unexpected selectors may indicate allowance manipulation.

"allowance_update_function": [
"0x095ea7b3",
"0x23b872dd",
"0x39509351",
"0xa457c2d7"
]

Owner-gated setters​

Three setter flags tell you that a function can change a limit: pause_status_can_be_modified, trading_cap_can_be_modified and position_cap_can_be_modified. The API assumes that only the owner can call these setters. The analysis does not check the caller.

When ownership_renounced is true, the API sends false for the three flags. The code still has the mechanism: has_pause_functionality, has_trading_cap_functionality and has_position_cap_functionality do not change. When ownership_renounced is false or null, each flag keeps the static result. A flag that is "pending" or null stays as it is.

The gate does not look at a proxy admin. For a proxy, an upgrade admin can still change the code, also when ownership_renounced is true.

tax_can_be_modified is not in this group. In a sample of renounced tokens with a tax setter, about one token in three still had a live address that controls that setter. So a renounced token can still show tax_can_be_modified: true.

REN on Ethereum, live on 2026-10-03:

"owner_address": "0x0000000000000000000000000000000000000001",
"ownership_renounced": true,
"has_pause_functionality": true,
"pause_status_can_be_modified": false

The owner getter gives 0x…0001, which is a burn address. So ownership_renounced is true, and the API sends false for the three setter flags. The code still has the pause mechanism.

Simulation Features​

transfer_success:​

Whether a holder's balance can actually be moved. false means the transfer did not go through, so the token may block transfers entirely, which is a common honeypot trait. null means the check did not settle, carries no signal, and must not be read as a pass.

The check sends tokens from a holder wallet that the API picks, or from a test wallet that it funds. If the API finds no holder wallet and cannot make a test wallet, transfer_success, send_tax and receive_tax are null. The API does not use the owner wallet in place of a holder wallet. It also does not repeat a failed transfer from the owner wallet. An owner is often exempt from the tax and the limits, so its result is not a valid answer for a holder. Since 2026-10-03, these three fields are null more often for this reason. When the API had no wallet for the check, it does the check again on a request that comes more than 6 hours after the last check. So a null from this cause can change to an answer. Ask again later.

allowance_bypass:​

true when some caller can move another holder's tokens through transferFrom without ever having been approved, the shape of a hidden drain backdoor.

The check is behavioural rather than a pattern match on the code, because a backdoor address is rarely a plain literal in a contract. Read it together with privileged_spenders, which is what separates the two positive cases:

allowance_bypassprivileged_spendersMeaning
trueone or more addressesAddress specific bypass. Those callers can move any holder's balance with no approval in place
trueemptyUniversal bypass. The token does not enforce allowances at all
falseemptyAllowances are enforced
nullemptyThe check did not run, for example because no holder wallet was available. Unknown, and never safe

⚠ Tri-state. null means the check could not run and must not be rendered as safe. Only false is an all-clear.

privileged_spenders:​

The addresses that can move a holder's balance with no allowance in place. Empty when allowance_bypass is false, and also when the bypass is universal rather than address specific, so read it together with allowance_bypass, never on its own.

"allowance_bypass": true,
"privileged_spenders": [
"0x961c7a5dde069aa1a4ea73e5b506e4312105f08d"
]

funds_can_be_drained:​

Somebody other than the holder can move tokens out of the wallet of a holder. This is the strongest finding of the API.

ValueMeaning
trueA measurement showed it
falseAt least one check ran and found nothing
nullNo check could run. Also null when a check found nothing, but the code has the shape of an allowance bypass: the API does not publish that negative

null is not false. Show it as unanswered, never as a pass. allowance_bypass and privileged_spenders tell you the mechanism and the address.

transfer_tax:​

  • send_tax (%): Calculated as the percentage loss from the sender's balance. For example, if the sender sent 100 tokens and 110 were deducted, the send tax is 10%.
  • receive_tax (%): Calculated as the percentage the recipient received. For example, if the sender sent 100 tokens and the recipient only received 90, the receive tax is 10%.
  • All values are expressed as percentages (e.g., 0.0 means 0%, and 1 means 1%).
  • null means that the API has no tax figure: the transfer check did not run, or the transfer did not go through (see transfer_success). null is not 0%.

dex:​

An array of liquidity pools where the token is paired with one of the chain's quote tokens (the wrapped native or a canonical stablecoin), along with details on liquidity and trading behavior. Launchpad bonding curves are reported here too, as synthetic pools.

{
"pair": "0x...",
"liquidity_type": "UniV2",
"liquidity": 2455608.22,
"buy_success": true,
"buy_tax": 0.0,
"sell_success": true,
"sell_tax": 0.0,
"errorMessage": null,
"errorCode": null,
"pool_id": null,
"is_launchpad": false
}
  • pair: Address of the liquidity pair.

  • liquidity_type: Type of the pool:

    TypeDescription
    UniV2Uniswap V2 or compatible fork
    UniV3Uniswap V3 or compatible fork
    UniV4Uniswap V4 or compatible fork
    SolidlyAerodrome (more Solidly forks coming!)
    SolidlyV3Aerodrome Slipstream
    four.memeFour.Meme bonding curve (BSC)
    flapFlap.sh bonding curve (BSC, Robinhood Chain)
    trench.todayTrench bonding curve (Robinhood Chain)

    The last three are bonding curves rather than DEX pools; they always carry is_launchpad: true.

    Uniswap V4 pools are simulated like V2 and V3. buy_success, sell_success, buy_tax and sell_tax are populated for UniV4 pools, including pools quoted in the chain's native coin and pools sitting behind a custom hook. Earlier releases returned no buy/sell result for V4 and reported every V4 pool as 0% tax; a client that cached those values, or that special-cased V4 as "tax unknown, assume zero", should stop doing so. A hook that taxes a swap now shows up in buy_tax / sell_tax like any other tax.

  • liquidity: USD value of the pool's known-token reserves:

    • Measured on the known token side. It is an approximation rather than an exact figure, and it is a reliable indicator of pool health and liquidity status. See Notes on liquidity for the per version caveats.
  • buy_success: Whether buying the token (e.g., WETH -> token) succeeded. Four-valued; see below.

  • buy_tax (%): Tax incurred on a buy. null or "pending" on the same terms as buy_success.

  • sell_success: Whether selling the token succeeded. Four-valued, same as buy_success.

  • sell_tax (%): Tax incurred on a sell.

  • errorMessage / errorCode: Populated for failed queries (e.g., low liquidity, failed swaps).

  • pool_id: ID of the pool in case of Uniswap V4, otherwise null.

  • is_launchpad: true if this pool is from a launchpad bonding curve (e.g., four.meme, flap.sh, trench.today). Otherwise false, or null in the response that computed the pools. Test is_launchpad === true. Do not test === false.

Reading buy_success / sell_success: null is not a failure​

These four fields are not booleans. Each carries one of four values, and conflating the last two with false is the most common way to misread a Tok{In} response:

ValueMeaningSafe reading
trueSimulated, and the trade succeededTradeable on this pool
falseSimulated, and the trade failedA real negative signal
"pending"Simulation is still runningNot yet known; poll again
nullNo simulation was attempted for this poolUnknown, carries no signal

null occurs where the API did not run a swap simulation. Bonding-curve pools are the common case, because a curve is not a DEX pool. The API also does not simulate a pool with less than $100 of liquidity (errorCode: "liquidity_too_low"), a pool with no liquidity at the current price ("no_in_range_liquidity"), or a pool that it cannot address ("missing_swap_parameters", "pair_not_found").

⚠ Do not render null or "pending" as "cannot buy" or "cannot sell". A client that treats these as falsy will report a healthy token as unsellable. Branch on === true and === false explicitly and handle the other two as "unknown". If you need a single tradeability verdict, require sell_success === false before calling a token unsellable, not merely "not true".

Because these fields are typed as boolean-or-string, statically typed clients should model them as a union (e.g. bool | "pending" | None) rather than bool.

is_liquid:​

This boolean indicates if the token is liquid, i.e. if at least one pool has more than $100 of value in it, measured on the chain's quote token side: WETH/USDC/USDT on most chains, WETH/USDG/USDE on Robinhood Chain.

Notes on liquidity​

  • For V2 pools, using the known token side (e.g., WETH in WETH/XYZ) is reliable since the reserves are fixed.
  • For V3 pools, the figure is measured on the known token side and is an approximation. It can include uncollected fees and dust, and it does not account for the active price range. It remains a strong signal of pool activity and health, and a very low value is a reliable indicator of an illiquid or abandoned pool.
  • For V4 pools, no per pool balance exists to read: every token balance is held by a single PoolManager contract rather than by one contract per pool. Liquidity for these pools is sourced from external market data for the pool id, as base and quote liquidity in USD.
  • For launchpad bonding curve pools (four.meme, flap.sh, trench.today), liquidity is derived from the on-chain reserve data of the bonding curve contract, converted to USD using the quote token price. Curves that quote in the chain's native coin rather than naming a quote token (trench.today, and flap on Robinhood Chain) are valued against the wrapped native.
  • When neither side of a pool is a chain anchor, it can still be valued when an external price is available for one side. This is what makes Robinhood Chain's tokenised equities usable as quote assets: a launchpad pool quoted in COIN, TSLA, GOOGL or SPCX carries real, reportable liquidity. A pool with no priced side at all is dropped rather than reported at $0.

Launchpad Detection​

Tok{In} detects tokens issued by launchpad platforms and reports which platform issued them, on every supported chain.

PlatformSlugChains
Ape.storeape.storeEthereum, BSC, Base, Robinhood Chain
ArenaarenaAvalanche, Robinhood Chain
ArrowPadarrowpadRobinhood Chain
BagsbagsRobinhood Chain
bow.funbow.funRobinhood Chain
CircuscircusRobinhood Chain
ClankerclankerEthereum, BSC, Base, Arbitrum, Robinhood Chain
CoinbarrelcoinbarrelRobinhood Chain
Doppler (bankr, long.xyz)dopplerBase, Robinhood Chain
DYOR Fundyor.funBase, Robinhood Chain
Flap.sh (bonding curve)flapBSC, Base, Robinhood Chain
Four.Meme (bonding curve)four.memeBSC, Base, Arbitrum
HoloLaunch (Holoworld)hololaunchBSC, Robinhood Chain
hood.funhood.funRobinhood Chain
hyper.memehyper.memeRobinhood Chain
KlikklikEthereum, BSC, Robinhood Chain
LaunchHoodlaunchhoodRobinhood Chain
LeaveHoodleavehoodRobinhood Chain
Lemon.funlemon.funRobinhood Chain
letscash.funletscash.funRobinhood Chain
LivolivoEthereum, Robinhood Chain
Lunch.funlunch.funRobinhood Chain
Memecoin.funmemecoin.funRobinhood Chain
MintFastmintfastRobinhood Chain
NoxanoxaRobinhood Chain
o1.exchangeo1.exchangeRobinhood Chain
OROoroRobinhood Chain
Pew.funpew.funRobinhood Chain
Pmav.funpmav.funRobinhood Chain
PonsponsRobinhood Chain
Pons (unofficial fork)pons-forkRobinhood Chain
Potato.fmpotato.fmRobinhood Chain
Real.funreal.funRobinhood Chain
RobinfunrobinfunRobinhood Chain
Stoxesstoxes.funRobinhood Chain
SushisushiBSC, Robinhood Chain
Trench (bonding curve)trench.todayRobinhood Chain
varo.rialtovaro.rialtoRobinhood Chain
Virtuals ProtocolvirtualsBase, Robinhood Chain
Unrecognised launchpadlaunchpadAll chains

Notes on reading the slugs:

  • One slug per platform, not per product line. Where a platform runs several launch routes (Pons V1/V2, Stoxes and its RWA line, o1's crypto- and stock-quoted routes, DYOR Fun's versions), they all report the platform's single slug. Screeners that list those routes separately will show more entries than this table does.
  • pons-fork is an independent operator running the same Pons launcher template. Its tokens look like official Pons tokens, but the launcher belongs to someone else, so it is labelled separately: launchpad reputation is the point of the field, and conflating the two would defeat it.
  • launchpad is a deliberate catch-all. A token that proves it came off a launchpad but whose platform we cannot yet name is still reported as launchpad-issued rather than dropped. Treat it as "yes, launchpad; platform unknown", not as a specific platform.
  • A platform on one chain is not the same product on another. Ape.store, Clanker, Arena and Virtuals are native to Base, Avalanche or Ethereum and also deployed on Robinhood Chain; the slug is the same, the deployment is not.

Detection is exact: it identifies the contract that issued the token, rather than guessing from the token's address. The one exception is documented below.

Graduated tokens (BSC). Four.Meme and Flap tokens carry a recognisable address pattern. It is used as a last resort on BSC only, for tokens that have left their bonding curve for a standard DEX such as PancakeSwap. Being a pattern match rather than an exact one, it can occasionally label a token that matches by coincidence. The exact signals are always preferred when available.

Bonding curves appear as pools. Four.Meme, Flap and Trench hold their reserves in a curve contract, so a token still on its curve appears in the dex array with liquidity_type set to the platform slug and is_launchpad: true. Every other platform in the table seeds a standard Uniswap pool at launch, so its tokens carry is_launchpad_token: true while their pools look like ordinary V2/V3/V4 pools.

How a platform is identified​

The evidence is always the contract that issued the token. For a curve platform that is the curve contract itself. For everything else the pool is an ordinary Uniswap pool that pair discovery already finds, so only the label is missing, and it is resolved from the issuer behind that pool. Several signals supply it and they are tried cheapest first, so most tokens are labelled without an extra RPC call.

Four properties of the result matter when you consume the field:

PropertyWhat it means for your integration
The match is exactA platform slug is set only when the issuer is identified. The BSC fallback above is the single exception
A new platform is not missedAn issuer that is recognisable but not yet named reports the generic launchpad slug rather than nothing
An ordinary DEX token is never labelledEvidence that a liquidity pool token could also produce is excluded, so a Uniswap LP token never reports as launchpad issued
One platform can match several signalsFlap on Robinhood Chain is identifiable from its issuer and also runs a curve whose reserves are reported as a pool
  • is_launchpad_token: tri-state.
    • true: the token was issued by a launchpad platform.
    • false: pools were resolved for this token and none of them, nor any provenance signal, indicates a launchpad.
    • null: unknown. No pools were resolved, so no determination could be made. Do not read this as "not a launchpad".
  • launchpad_name: The launchpad platform slug (e.g. "four.meme", "pons", "doppler", or the generic "launchpad"). null if is_launchpad_token is not true.
  • is_launchpad (per pool): true if the specific pool is a launchpad bonding curve. false otherwise. A launchpad token that graduated, or that never ran a curve, has is_launchpad_token: true while no pool in dex has is_launchpad: true; the two fields answer different questions.

Supply & Burn Features​

decimals:​

Number of decimal places the token uses (e.g., 18 for most ERC20 tokens). Used to convert between raw and human-readable amounts.

supply:​

Total supply of the token, normalized by decimals (raw_supply / 10^decimals). This is the human-readable value (e.g., 1000000000 for a token with 1 billion supply).

burned_supply:​

Total supply held by the burn addresses, normalized by decimals. The value is verified on-chain via multicall. 0 if no tokens have been sent to burn addresses.

burned_supply_pct:​

Percentage of the total supply held by the burn addresses, capped to 2 decimal places (e.g., 6.13). 0 if no tokens have been burned.

circulating_supply:​

Circulating supply of the token, calculated as supply - burned_supply. Represents the tokens that are still in active circulation.

burn_addresses:​

An array of balances of the burn addresses. Each entry is a holder object with the burn address and its token balance. Only addresses with a balance > 0 are included.

"burn_addresses": [
{
"owner_address": "0x000000000000000000000000000000000000dead",
"token_amount": 61256610.0,
"pct_held": 6.13
}
]

normalized_supply (deprecated):​

Use supply instead. Returns the same value as supply. Kept for backwards compatibility.

Burn addresses​

A burn address is an address that nobody controls. The API uses one set of five on every chain:

AddressNote
0x0000000000000000000000000000000000000000The zero address
0x000000000000000000000000000000000000dEaDThe usual dead address
0x0000000000000000000000000000000000000001The ecrecover precompile. It has no code and no key
0xdead000000000000000042069420694206942069A vanity burn address
0xdeaddeaddeaddeaddeaddeaddeaddeaddeaddeadA vanity burn address

The match is exact and ignores letter case. An address that only starts like a burn address is not a burn address. This set gives burned_supply, burn_addresses, the true value of ownership_renounced for a burn owner, and the burn_address refusal.

Since 2026-10-03 the set has five addresses, not two. So burned_supply, burned_supply_pct and circulating_supply can be different from an earlier response, with no new transfer. For example, on SHIB (Ethereum, live on 2026-10-03), 0xdead000000000000000042069420694206942069 holds 41.04% of the supply, so burned_supply_pct is 41.05. Before that date it was 0. Also, an owner getter that gives 0x…0001 now makes ownership_renounced true (REN on Ethereum is an example).

Other Features​

name:​

Name of the token.

symbol:​

Symbol of the token.

creator_address:​

Address of the creator (EOA) of the token.

creator_balance:​

Amount of tokens held by the creator (normalized by decimals).

creator_percent:​

Percentage of the total supply held by the creator of the token, capped to 2 decimal places.

top_holders:​

Top 10 token holders, returned as an array of holder objects. Balances are verified on-chain via multicall. This list can include a burn address.

[
{
"owner_address": "0x...",
"token_amount": 1234567.89,
"pct_held": 12.34
}
]

Each entry in top_holders has the following fields:

  • owner_address: Address of the token holder.
  • token_amount: Amount of tokens held by that address (normalized by decimals).
  • pct_held: Percentage of the total token supply held by that address.

is_in_dex:​

The token is in at least one DEX.

cannot_buy:​

Reads only the pools with a settled buy result (buy_success is true or false).

ValueMeaning
trueEvery pool with a settled buy result failed the buy
falseAt least one pool allowed a buy
nullNo pool has a settled buy result, so no buy was simulated. Examples: the token has no pool, or each pool is a bonding curve or has less than $100 of liquidity

null is not false. It does not tell you that the token can be bought.

owner_can_change_balance:​

The token owner can modify any holder's balance directly. This field is part of the extended analysis set and may be absent from the response entirely: an absent field means "not assessed", which is not the same as false. Key off its presence before reading it. Today the API does not run the owner simulation, so the field is absent.

is_launchpad_token:​

true if the token was issued by a launchpad platform. false if pools were resolved and none indicates a launchpad. null if no pools were resolved, meaning no determination could be made; see Launchpad Detection for the full platform list.

launchpad_name:​

The launchpad platform slug (e.g. "four.meme", "clanker", "doppler", "pons"). Falls back to the generic "launchpad" when the token is provably launchpad-issued but the platform is not yet named. null if is_launchpad_token is not true. The full list of slugs is in Launchpad Detection; treat it as an open set and keep a default branch, since new platforms are added over time.

last_scanned_at:​

ISO 8601 UTC timestamp of when this token was last analyzed (e.g., "2025-01-15T12:30:00Z").

Token Safety Summary​

The summary endpoint returns a compact, pre-processed view of a token's safety data, designed for dashboards and quick-scan UIs where you need actionable signals without parsing the full feature set.

curl -X 'GET' \
'https://tokin-api.dedaub.com/token/{chain}/{token_address}/summary' \
-H 'accept: application/json' \
-H 'X-API-Key: YOUR-API-KEY'

Response Fields​

FieldMeaning
verdictThe overall verdict, from the same rule as the Tok{In} scan page: danger, warning, safe or not_tested. null only when the request produced no analysis (a 422 or a 500 response)
verdict_reasonsEvery cause of verdict that holds, most severe first: sell_failed, buy_failed, danger_flags, no_liquidity, warning_flags, no_trade_simulated. Empty when verdict is safe or null. More keys can come later, so show a key that you do not know as neutral
active_flag_countThe number of checks that fired (status danger, warning or info). A check with no answer is not counted
evaluated_flag_countThe number of checks that gave an answer. Use it as the denominator. It is active_flag_count + passed_flag_count + notice_flag_count
total_flag_countThe number of checks in the catalogue. Active, passed, notice and unknown checks always add up to this value
passed_flag_countChecks that ran and found nothing
notice_flag_countChecks whose answer is neither a pass nor a finding. Only is_open_source today
unknown_flag_countChecks that gave no answer. Never show them as passes
danger_flag_count, warning_flag_count, info_flag_countActive checks at each severity
flagsEvery check, resolved, with key, label, status, active and value. The order is danger, warning, info, notice, safe, unknown
highlightsaccess_restrictions, transfer_restrictions and has_enough_liquidity as resolved flags
access_restrictionsThe value of has_blacklist_or_whitelist. true is a finding
transfer_restrictionstrue when has_trading_cap_functionality or has_position_cap_functionality is true. true is a finding
funds_can_be_drainedThe same value as funds_can_be_drained in the full response
has_enough_liquidityThe deepest pool has more than $100 of liquidity. true is the good answer
total_liquidity_usdThe USD liquidity of all pools. null, not 0, when the API found no pool
can_buy / can_selltrue when a buy (or a sell) succeeded in at least one simulated pool. false when it failed in every simulated pool. null when no trade was simulated
detected_pool_countThe number of pools
dominant_poolThe pool with the highest liquidity, or null if no pools exist

⚠ Colour each check from status, never from value. The checks do not have the same polarity. A null value is no answer, not a pass. status is one of danger, warning, info, notice, safe and unknown.

The fields of dominant_pool:

  • type: Pool type (e.g. UniV2, UniV3).
  • address: Pool contract address.
  • liquidity: Liquidity in USD.
  • can_buy / can_sell: Whether buys/sells succeed in this pool. null when no trade was simulated in this pool.
  • buy_tax / sell_tax: Tax as a percentage (e.g. 1.0 = 1%). null if unavailable. The Swagger UI text for these fields says 0–1 today. That text is wrong. Use the scale on this page.

Example​

curl -X 'GET' \
'https://tokin-api.dedaub.com/token/ethereum/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48/summary' \
-H 'accept: application/json' \
-H 'X-API-Key: YOUR-API-KEY'

Result (USDC, live on 2026-10-03; flags is trimmed to 3 of 23 entries):

{
"verdict": "warning",
"verdict_reasons": ["warning_flags"],
"active_flag_count": 8,
"evaluated_flag_count": 23,
"total_flag_count": 23,
"passed_flag_count": 15,
"notice_flag_count": 0,
"unknown_flag_count": 0,
"danger_flag_count": 0,
"warning_flag_count": 7,
"info_flag_count": 1,
"flags": [
{"key": "is_proxy", "label": "Proxy Contract", "status": "warning", "active": true, "value": true},
{"key": "has_owner_functionality", "label": "Owner Privileges Mechanism", "status": "info", "active": true, "value": true},
{"key": "pause_status_can_be_modified", "label": "Pause Modifiable", "status": "safe", "active": false, "value": false}
],
"highlights": [
{"key": "access_restrictions", "label": "Access Restrictions", "status": "warning", "active": true, "value": true},
{"key": "transfer_restrictions", "label": "Transfer Restrictions", "status": "safe", "active": false, "value": false},
{"key": "has_enough_liquidity", "label": "Has Enough Liquidity", "status": "safe", "active": false, "value": true}
],
"access_restrictions": true,
"transfer_restrictions": false,
"funds_can_be_drained": false,
"has_enough_liquidity": true,
"total_liquidity_usd": 401633007.6150325,
"can_buy": true,
"can_sell": true,
"detected_pool_count": 42289,
"dominant_pool": {
"type": "UniV3",
"address": "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640",
"liquidity": 98505369.9597153,
"can_buy": true,
"can_sell": true,
"buy_tax": 0.0,
"sell_tax": 0.0
}
}

Since April 2026, the summary has no major_flags list. Use highlights and flags.


Example​

curl -X 'GET' \
'https://tokin-api.dedaub.com/token/ethereum/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48?include_token_price=true' \
-H 'accept: application/json' \
-H 'X-API-Key: YOUR-API-KEY'

Result (USDC, live on 2026-10-03; dex and top_holders are trimmed to one entry):

{
"chain": "ethereum",
"contract_address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"features": {
"mint_or_burn_function": true,
"can_selfdestruct": false,
"transfer_guarded_by_storage": true,
"transfer_from_guarded_by_storage": true,
"trading_cooldown": false,
"view_function_modifies_state": false,
"has_blacklist_or_whitelist": true,
"external_call": true,
"timebomb": false,
"has_pause_functionality": true,
"pause_status_can_be_modified": false,
"has_owner_functionality": true,
"has_tax_functionality": false,
"tax_can_be_modified": false,
"has_trading_cap_functionality": false,
"trading_cap_can_be_modified": false,
"has_position_cap_functionality": false,
"position_cap_can_be_modified": false,
"balance_update_function": ["0xa9059cbb", "0x23b872dd", "0x2fc81e09", "0x40c10f19", "0x42966c68", "0xe3ee160e", "0xef55bec6", "0xcf092995", "0x88b7ab63", "0xf9f92be4", "0x430239b4", "0x2fc81e09", "0x1a895266"],
"allowance_update_function": ["0x39509351", "0x9fd5a6cf", "0xa457c2d7", "0xd505accf", "0x095ea7b3", "0x23b872dd"],
"name": "USD Coin",
"symbol": "USDC",
"decimals": 6,
"supply": 48963128240.24519,
"normalized_supply": 48963128240.24519,
"is_open_source": true,
"has_open_source_implementation": true,
"is_liquid": true,
"is_proxy": true,
"owner_address": "0xFcb19e6a322b27c06842A71e8c725399f049AE3a",
"ownership_renounced": false,
"send_tax": 0,
"receive_tax": 0,
"transfer_success": true,
"creator_address": "0x95ba4cf87d6723ad9c0db21737d862be80e93911",
"creator_balance": 2505.454092,
"creator_percent": 0,
"is_in_dex": true,
"cannot_buy": false,
"allowance_bypass": false,
"privileged_spenders": [],
"is_launchpad_token": false,
"launchpad_name": null,
"has_trading_cap": true,
"has_position_cap": false,
"timebomb_status": null,
"timebomb_deadlines": [],
"last_scanned_at": "2026-10-03T14:43:58Z",
"dex": [
{
"errorMessage": null,
"errorCode": null,
"pair": "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640",
"liquidity_type": "UniV3",
"liquidity": 98505369.9597153,
"buy_success": true,
"buy_tax": 0,
"sell_success": true,
"sell_tax": 0,
"pool_id": null,
"is_launchpad": false
}
],
"top_holders": [
{
"errorMessage": null,
"errorCode": null,
"owner_address": "0x37305b1cd40574e4c5ce33f8e8306be057fd7341",
"token_amount": 4227515543.911332,
"pct_held": 8.634079757258096
}
],
"burn_addresses": [
{
"errorMessage": null,
"errorCode": null,
"owner_address": "0x000000000000000000000000000000000000dead",
"token_amount": 56328.52738,
"pct_held": 0.00011504274625513169
},
{
"errorMessage": null,
"errorCode": null,
"owner_address": "0x0000000000000000000000000000000000000001",
"token_amount": 27657.636544,
"pct_held": 5.64866615717311e-05
},
{
"errorMessage": null,
"errorCode": null,
"owner_address": "0xdead000000000000000042069420694206942069",
"token_amount": 474.365227,
"pct_held": 9.68821323409839e-07
}
],
"burned_supply": 84460.529151,
"burned_supply_pct": 0,
"circulating_supply": 48963043779.71604,
"funds_can_be_drained": false
},
"error": "",
"error_code": null,
"token_usd_price": 0.9999655272004394,
"market_cap_usd": 48961355886.52195,
"price_source": "defillama",
"price_age_seconds": null
}

USDC is a proxy. The API calls the owner getter at the proxy address, so the call reaches the implementation code, and owner_address is the live owner. So ownership_renounced is false, and each owner-gated setter keeps its static result. timebomb_status is null, because the code has no timebomb finding. Three burn addresses hold USDC. Their total is less than 0.005% of the supply, so burned_supply_pct is 0 after the rounding to 2 decimals. error_code is null because the request succeeded. Without include_token_price=true, the four price fields are null.

Example: owner not determined​

BAT on Ethereum (0x0d8775f648430679a709e98d2b0cb6250d2887ef), live on 2026-10-03:

"owner_address": null,
"ownership_renounced": null,
"has_owner_functionality": true

BAT has no owner getter, so owner_address is null. A wallet is in a storage slot that the code uses for its owner checks. So the API cannot say that the owner role is gone, and ownership_renounced is null. Show this as "not determined", never as "renounced". With null, each owner-gated setter keeps its static result.

API Response Reference​

All endpoints use JSON unless otherwise noted. Authentication endpoints redirect via 307.


Common Response Codes​

CodeMeaning
200Success
307Redirect (OAuth flows)
400Bad request (invalid input, failed precondition)
401Missing, invalid or expired API key / unauthenticated
402A free trial allowance is spent. Not retryable; the allowance does not refill
403Forbidden (e.g. refresh without an enterprise plan, or a new trial key after the trial ended)
404Resource not found
409Conflict (e.g. max API keys reached, or a trial key was already issued)
422Validation error (invalid address format, missing params)
429Rate limit or quota exceeded
500Internal server error
503Service not configured (billing)

Token​

GET /token/{chain}/{contract_address}​

Returns token safety features.

Refusal codes​

Codeerror_codeMeaning
200nullTokenResponse with features
422invalid_addressThe address is not an EVM address
422burn_addressOne of the five burn addresses. It is not a token. Do not retry
422not_a_contractThere is no code at the address on this chain
422not_a_tokenThere is code, but it does not answer the ERC-20 calls (name, symbol, decimals, totalSupply)
422invalid_token_dataThe analysis gave a result that is not valid
422(none)Unknown chain or bad query value. FastAPI detail list
500analysis_failedA fault on our side. You can retry later
401api_key_requiredNo key. See AGENTS.md §7.1
401api_key_expiredThe key is past its expiry date
401(none)An unknown key: {"detail": "Invalid API Key"}
402free_quota_exhaustedA trial allowance is spent. Do not retry
403(none)refresh or redecompile without an enterprise plan or an admin key
429(none)See Rate Limiting

Branch on error_code, not on error. The error text can change. When error_code is absent, branch on the HTTP status. For a 422 or a 500 refusal with an error_code, the body is a TokenResponse with features: null. A 401 or a 402 with a code has the body {"error_code", "error", "detail", "next_step"}.

Burn address refusal (HTTP 422). The match ignores letter case:

{
"chain": "ethereum",
"contract_address": "0x000000000000000000000000000000000000dEaD",
"features": null,
"error": "Burn/null address — tokens sent here are permanently removed",
"error_code": "burn_address",
"token_usd_price": null,
"market_cap_usd": null,
"price_source": null,
"price_age_seconds": null
}

TokenResponse schema:

{
"chain": "ethereum",
"contract_address": "0x...",
"features": { ... },
"error": "",
"error_code": null,
"token_usd_price": null,
"market_cap_usd": null,
"price_source": null,
"price_age_seconds": null
}

Query param response_format=goplus returns GoPlus-compatible features shape.

Price fields​

The four price fields are null unless the request has include_token_price=true. The API resolves a price only on request, because the price costs an extra call.

  • token_usd_price: The USD price of the token.

  • market_cap_usd: token_usd_price multiplied by our own circulating_supply, not the market cap of the source. null when the circulating supply is not above zero, or when the result is not plausible.

  • price_source: The source that gave the price. The API tries the sources in this order:

    ValueMeaning
    defillamaA live aggregate across venues. The preferred source
    network_token_infoThe last trade price in our warehouse. It can be old, so read price_age_seconds
    pool_spotThe spot price of the deepest pool of the token that simulated a real buy
    stablecoin_pin$1, for the stable anchors of the chain
  • price_age_seconds: The age of the price in seconds. Set only for network_token_info. The API reads the other sources live.

All four fields are null when no source has a price. A missing price is unknown, not zero.


GET /token/{chain}/{contract_address}/summary​

Returns a compact safety summary. Uses the same cache as the full token endpoint.

CodeBody / Detail
200TokenSummaryResponse
422TokenSummaryResponse with verdict: null, evaluated_flag_count: 0 and every check counted as unknown (unknown_flag_count equals total_flag_count). The address is not valid, is a burn address, has no code, or is not a token. The body has no error_code; call the token route for the reason. An unknown chain gives the FastAPI detail list instead
500The same body as for 422 (analysis failure)
401api_key_required or api_key_expired (with error_code), or {"detail": "Invalid API Key"}
402free_quota_exhausted
429See Rate Limiting

TokenSummaryResponse schema: see Token Safety Summary for each field.

{
"verdict": "warning",
"verdict_reasons": ["warning_flags"],
"active_flag_count": 8,
"evaluated_flag_count": 23,
"total_flag_count": 23,
"passed_flag_count": 15,
"notice_flag_count": 0,
"unknown_flag_count": 0,
"danger_flag_count": 0,
"warning_flag_count": 7,
"info_flag_count": 1,
"flags": [{"key": "...", "label": "...", "status": "warning", "active": true, "value": true}],
"highlights": [{"key": "access_restrictions", "label": "Access Restrictions", "status": "warning", "active": true, "value": true}],
"access_restrictions": true,
"transfer_restrictions": false,
"funds_can_be_drained": false,
"has_enough_liquidity": true,
"total_liquidity_usd": 401633007.6150325,
"can_buy": true,
"can_sell": true,
"detected_pool_count": 42289,
"dominant_pool": {
"type": "UniV3",
"address": "0x...",
"liquidity": 98505369.9597153,
"can_buy": true,
"can_sell": true,
"buy_tax": 0.0,
"sell_tax": 0.0
}
}

Auth​

POST /agent-keys​

A free, capped API key for an automated caller that cannot sign in. Sign-in needs a mailbox, so software has no way through the OAuth flow; this is the way in for an agent or a script.

Requires no credential. Send a reason saying what you are building.

curl -X POST 'https://tokin-api.dedaub.com/agent-keys' \
-H 'Content-Type: application/json' \
-d '{"reason": "screening tokens before a user buys", "contact": "[email protected]"}'
FieldRequiredNotes
reasonyesUp to 500 characters. Recorded, and not verified.
contactnoUp to 200 characters.
CodeBody / Detail
201{"api_key": "...", "plan": "agent", "api_calls_total": ..., "api_calls_per_day": ..., "rate_limit_per_minute": ..., "expires_at": "...", "next_step": "..."}
403error_code: agent_trial_expired. The trial of this client network has ended. It does not renew
409error_code: agent_trial_already_issued. This client network already has a trial key. The body has no key
422Missing or over-long reason
429error_code: agent_registration_throttled. Registration is closed for now. Wait for the Retry-After header, or sign in
503Trial registration is unavailable on this deployment

The trial allowance is small by design: enough to build and demonstrate an integration against a handful of tokens, not enough to run a product on. api_calls_total is a lifetime total, and expires_at is the time when the key stops working. On 2026-10-03 a trial key had 100 calls in total, 25 calls for each day, 20 calls for each minute and 5 calls in 10 seconds, and it expired after 14 days. The 201 response does not show the limit for 10 seconds. Read the other values from the 201 response.

Every response to a key with a deadline has the header X-API-Key-Expires-At. In the last 7 days, the response also has X-API-Key-Expires-In and a Warning header.

The API issues one trial key for each client network (your IP address, or your IPv6 block), and it shows the key one time. A second request from the same client network gets 409 with error_code: agent_trial_already_issued and no key. So keep the key that you get. After the trial ends, a request gets 403 with error_code: agent_trial_expired. When the allowance is spent, the API answers 402 with error_code: free_quota_exhausted. The allowance does not refill, so a retry does not help. Sign in and create a full key instead.

AI coding agents: read AGENTS.md §2.2 and §7.1 before writing any client against this.

GET /auth/login/{provider}​

CodeDetail
307Redirect to OAuth provider (Google or GitHub)

GET /auth/callback/{provider}​

CodeDetail
307Redirect to {FRONTEND_URL}/auth/success?token=<jwt> on success
400OAuth callback failure (provider error)

Profile​

All profile endpoints require a valid JWT (set via cookie from auth callback).

GET /profile/me​

CodeBody
200User object
401{"detail": "Not authenticated"}

GET /profile/api-keys​

CodeBody
200Array of APIKey objects
401{"detail": "Not authenticated"}

POST /profile/api-keys​

CodeBody / Detail
200Created APIKey object
401{"detail": "Not authenticated"}
409{"detail": "Maximum of N API key(s) allowed per user"}
500{"detail": "Error creating API key"}

DELETE /profile/api-keys/{api_key_id}​

CodeBody
200{"message": "API key deleted successfully"}
200{"message": "API key not found or does not belong to the user"} (no-op)
401{"detail": "Not authenticated"}

GET /profile/logs​

CodeBody
200Array of APILog objects
401{"detail": "Not authenticated"}

GET /profile/logs_status_codes​

CodeBody
200Array of {"status_code": int, "count": int}
401{"detail": "Not authenticated"}

GET /profile/logs_chains​

CodeBody
200Array of {"chain": string, "count": int}
401{"detail": "Not authenticated"}

GET /profile/logs_addresses​

CodeBody
200Array of {"address": string, "count": int}
401{"detail": "Not authenticated"}

GET /profile/linked-accounts​

CodeBody
200Array of {"id", "provider", "provider_id", "email", "linked_at"}
401{"detail": "Not authenticated"}

DELETE /profile/linked-accounts/{account_id}​

CodeBody / Detail
200{"message": "Account unlinked successfully"}
200{"message": "Account not found or does not belong to user"} (no-op)
400{"detail": "Cannot unlink last provider account"}
401{"detail": "Not authenticated"}

Rate Limiting​

429 responses are returned by the auth middleware for API-key-authenticated endpoints. Four independent checks:

CheckScopeHeaders
Burst (10 s)Per API key (Redis)Retry-After, X-RateLimit-Burst-Limit, X-RateLimit-Burst-Window, X-RateLimit-Remaining: 0
Per-minute QPSPer API key (Redis)Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining: 0
Daily quotaPer user (Redis)X-Quota-Daily-Limit, X-Quota-Daily-Used, Retry-After
Monthly quotaPer user (PostgreSQL)X-Quota-Monthly-Limit, X-Quota-Monthly-Used

A trial key also has a lifetime total (api_calls_total). The API checks it before the daily quota. When the total is spent, the API answers 402 with error_code: free_quota_exhausted, not 429. Do not retry a 402.

Admin users bypass all limits. A Host or Referer header does not bypass authentication or rate limiting.