AGENTS.md: Migrating from GoPlus to Tok{In}
This file gives AI coding agents (Claude Code, Cursor, Copilot, etc.) the context they need to migrate an existing GoPlus token-security integration over to Dedaub's Tok{In} API with minimal code changes.
Source of truth: https://docs.dedaub.com/docs/token_safety/tokin/
If you are an AI agent helping a developer migrate, read this file end-to-end before editing their code. The sections are ordered so that the first three give you everything needed for a one-line drop-in swap; the rest covers field-level differences, edge cases, and known gaps.
1. TL;DR: the one-line swap
GoPlus's token_security endpoint can be replaced by Tok{In} with three changes:
- Base URL →
https://tokin-api.dedaub.com - Path →
/token/{chain}/{token_address}(chain is a name, not a numeric chain id; see §6) - Auth →
X-API-Key: <YOUR-API-KEY>header (instead of GoPlus's?api_key=query param or app-id/secret signing)
Append ?response_format=goplus to receive field values in GoPlus's schema with "1"/"0" string-boolean encoding. The outer envelope is different (see §2.1): fields live under features, not at the top level, so a literal drop-in is rarely possible without one small parser change.
# Before (GoPlus)
curl 'https://api.gopluslabs.io/api/v1/token_security/1?contract_addresses=0xABC...'
# After (Tok{In}, GoPlus-compatible response)
curl -H 'X-API-Key: YOUR-API-KEY' \
'https://tokin-api.dedaub.com/token/ethereum/0xABC...?response_format=goplus'
2. Endpoint reference
GET https://tokin-api.dedaub.com/token/{chain}/{token_address}
GET https://tokin-api.dedaub.com/token/{chain}/{token_address}?response_format=goplus
Headers:
X-API-Key: <required>
| Query param | Values | Default | Effect |
|---|---|---|---|
response_format | dedaub | goplus | dedaub | goplus reshapes the body to GoPlus's schema and string-boolean encoding. |
The native dedaub schema is richer (typed booleans, additional fields, structured tax info). Use it for greenfield code; use goplus only when you are migrating existing GoPlus parsers and want to defer schema changes.
2.1 Response envelope (read this before parsing)
Tok{In} always wraps the result in this envelope, regardless of response_format:
{
"chain": "ethereum",
"contract_address": "0xA0b8...eB48",
"features": { /* SafetyFeatures (dedaub) | GoPlusMappedFeatures (goplus) */ },
"error": ""
}
This differs from GoPlus's { "code": 1, "message": "OK", "result": { "0xABC...": { ... } } } shape in two ways that matter to a migrating client:
- Fields are nested under
features, not underresult["<address>"]. Update every accessor:body.is_open_source→body.features.is_open_source. - Errors come via HTTP status code +
errorstring, not an in-bodycode/messagepair. On non-2xx,featuresisnullanderrorcarries the human-readable reason.
Full response schema: see tokin.md.
2.2 Getting a key when you cannot sign in
Every /token route needs an X-API-Key header. A person gets one by signing
in at https://tokin.dedaub.com/login and creating it in the dashboard.
If you are an agent running without a person present, you cannot do that: sign-in needs a mailbox. Request a capped trial key instead.
curl -X POST 'https://tokin-api.dedaub.com/agent-keys' \
-H 'Content-Type: application/json' \
-d '{"reason": "LLM agent screening tokens before a user buys, ~200 scans/day",
"contact": "[email protected]"}'
{
"api_key": "...",
"plan": "agent",
"api_calls_per_month": 200,
"api_calls_per_day": 50,
"rate_limit_per_minute": 10,
"next_step": "Send this key as the X-API-Key header on every request, ..."
}
reason is required and contact is optional. The reason is recorded so a
person at Dedaub can see who uses this and for what. It is not verified and it
grants nothing — the allowance is what limits you, not what you write.
Registration is idempotent. Asking again from the same network returns the key you already have, along with the allowance you have already partly spent. This is deliberate, and it is the thing most likely to surprise you: requesting another key is not a way to get more calls.
Four things to do with this, in the order they will bite:
- Store the key, but do not panic if you lose it. Re-registering returns the same key rather than failing, so a client that has lost its key can recover. It will not get a fresh allowance.
- Treat the trial as a trial. The allowance is small on purpose — enough to build and demonstrate an integration against a handful of tokens, and not enough to run a product on. Ask the developer you are working for to sign in and create a full key before shipping.
- Do not retry a 402. See §7.1. A spent allowance does not refill, and asking for another key will not produce one.
- Do not embed a trial key in code you hand to a user. It is bound to your allowance, not theirs, and the first thing that happens at scale is that it runs out for everyone at once.
3. Migration steps for an AI agent
When asked to migrate a codebase from GoPlus to Tok{In}, follow this sequence:
- Locate the GoPlus call sites. Grep for
gopluslabs.io,token_security,goplus, and known SDK imports (e.g.goplus-sdk-py,@goplus/sdk-node). - Identify the auth mechanism in use. GoPlus integrations vary: anonymous,
?api_key=, or app-id/secret signed access tokens. All three collapse to a singleX-API-Keyheader in Tok{In}. - Translate the chain identifier. GoPlus uses numeric chain IDs in the path (
/token_security/1); Tok{In} uses names (/token/ethereum/...). See §6 for the full mapping. If the codebase passes chain IDs around as integers, add a small lookup helper rather than rewriting every call site. - Update the URL builder and add the
X-API-Keyheader. Append?response_format=goplusif the existing parser relies on GoPlus field names and"1"/"0"strings; otherwise migrate to the nativededaubschema in the same change. - Audit field reads. Cross-reference every field the code reads against §4. Pay attention to:
- Computed fields (
transfer_pausable,is_anti_whale, etc.): these are derived, not 1:1. - Always-
nullfields (§4): if downstream code branches on them, simplify the branch or wire them to a different signal.
- Computed fields (
- Update error handling. Tok{In} returns standard HTTP codes (§7); GoPlus historically returned
200with acodefield in the body. Replace body-codechecks with HTTP status checks. - Update rate-limit handling. Tok{In} emits standard
Retry-AfterplusX-RateLimit-*andX-Quota-*headers (§8). Wire any retry/backoff logic to those headers. - Run the test suite. If there isn't one for token-security calls, add at least one integration test against a known token (e.g. USDC on Ethereum:
0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) before declaring the migration done.
Do not silently delete GoPlus fields the user's code reads; flag them as TODOs (especially the always-null set in §4) so the developer can decide what to do.
4. Field mapping (Tok{In} dedaub → GoPlus)
When response_format=goplus, the server applies these mappings. All booleans become the strings "1" (true) or "0" (false).
Direct mappings
| Tok{In} (Dedaub) | GoPlus | Notes |
|---|---|---|
is_open_source | is_open_source | bool → "1"/"0" |
is_proxy | is_proxy | bool → "1"/"0" |
mint_or_burn_function | is_mintable | bool → "1"/"0" |
owner_address | owner_address | string |
can_selfdestruct | selfdestruct | bool → "1"/"0" |
external_call | external_call | bool → "1"/"0" |
is_in_dex | is_in_dex | bool → "1"/"0" |
receive_tax | buy_tax | decimal as string |
send_tax | sell_tax | decimal as string |
cannot_buy | cannot_buy | bool → "1"/"0" |
tax_can_be_modified | slippage_modifiable | bool → "1"/"0" |
trading_cooldown | trading_cooldown | bool → "1"/"0" |
creator_address | creator_address | string |
creator_percent | creator_percent | decimal as string |
is_launchpad_token | launchpad_token | bool → "1"/"0" |
⚠ Tax direction is inverted. send_tax → sell_tax and receive_tax → buy_tax. Make sure any code computing slippage matches the renamed semantic.
⚠ buy_tax / sell_tax are pool-aware. The mapper first walks features.dex[] and uses the first pool with non-null buy_tax / sell_tax, falling back to the contract-level receive_tax / send_tax only if no pool reports a value. The result is usually closer to real swap behavior than GoPlus's contract-level tax, but it can disagree with the contract-level fields in the same response.
Additional fields populated in goplus mode
Beyond the GoPlus-spec fields, the response also carries:
token_name,token_symbol,total_supply: populated when known.holders[]: array of{ address, balance, percent }. Note thatholder_countitself isnull(see below) but theholders[]array is populated.
Computed mappings (derived, not 1:1)
| GoPlus field | Derived from | Logic |
|---|---|---|
transfer_pausable | cannot_buy, pause_status_can_be_modified | True if either is True |
is_blacklisted | has_blacklist_or_whitelist | same value (temporary) |
is_whitelisted | has_blacklist_or_whitelist | same value (temporary) |
is_anti_whale | has_trading_cap, has_position_cap | True if either is True |
anti_whale_modifiable | trading_cap_can_be_modified, position_cap_can_be_modified | True if either is True |
hidden_owner | allowance_bypass, privileged_spenders | True when the bypass is address specific, so at least one address is named |
owner_change_balance | owner_can_change_balance | same value |
⚠ Blacklist/whitelist ambiguity. Both flags map from the same source, so the API cannot distinguish blacklist-only, whitelist-only, or both. Code that branches on one vs. the other should be reviewed.