Help a developer figure out why a Meta access token is failing — without ever handling the raw token yourself.
Security first — never handle a live token
An access token is a bearer credential: whoever holds it can act as its owner. Anything pasted into this chat enters the agent's context and is transmitted to the AI provider (logs, history, retention). Treat a token like a password.
- Do NOT ask the developer to paste an access token (or an app secret) into this chat. Inspect tokens using the developer-run options below, and ask only for the resulting metadata.
- If a token was already pasted, tell the developer to revoke/rotate it (Graph API Explorer, or the app dashboard → regenerate), then continue with the metadata flow below using a fresh token.
Workflow
-
Confirm the symptom. Ask what failed:
- The error
codeandsubcode(e.g.190/463) - Which endpoint/request failed
- Which app the call was made with, and what the token is expected to do (which permissions/scopes)
- The error
-
Have the developer inspect the token themselves. Offer either option — both run in the developer's own environment and return only metadata, never routing the token through this agent:
- A. Access Token Debugger (web): open https://developers.facebook.com/tools/debug/accesstoken/, paste the token there (a Meta first-party surface, not this agent), and read the results panel.
- B.
debug_tokenGraph API via the bundled script: this skill ships a ready-to-run script,scripts/debug_token_probe.py, that reads the token and app secret from environment variables (by name — never their values) and prints only redacted metadata; the developer sets the env vars and runs it in their own shell, then brings back only the printed JSON. See the Using thedebug_tokenscript section below for how to run it and the expected output. Reference: debug_token docs. Never ask the developer to send you the token or the app secret.
If the developer needs exact, current steps, use devtools_discovery (action search_docs) to fetch the latest Access Token Debugger / debug_token documentation.
-
Ask only for the redacted metadata. Request they copy back the debug output with PII removed. Keep:
is_valid,type,app_id,application,issued_at,expires_at,data_access_expires_at,scopes/granular_scopes, and anyerror.code/error.subcode/error.message. Tell them to redactuser_idand any profile IDs — you do not need them to diagnose the failure. (The bundled script already emits only this allow-listed, redacted subset.) See Using thedebug_tokenscript below for an example of the token-free output to expect. -
Interpret the metadata and report (format below).
Using the debug_token script (Option B)
When the developer prefers the API over the web debugger, point them at the script
bundled with this skill, scripts/debug_token_probe.py. It reads the credentials
from environment variables in the developer's own shell — by name, never
their values — so no token or secret ever appears in the chat, in a script you
author, or on a command line. The shell provides the values at run time, and the
script prints only redacted metadata. Do not re-derive or paste the script into the
chat; it is already checked in and vetted — just tell the developer how to run it.
Give the developer the absolute path. The script lives inside the installed
plugin, not in the developer's working directory, so a relative path will not
resolve for them. Substitute the absolute path of this skill's directory — you know
it, having just read SKILL.md from there — wherever <skill-dir> appears below.
Have them run this in a throwaway shell, not the shell they start their agent from: an exported variable is inherited by every process that shell launches, so a token left exported can reach an agent started later from the same terminal.
Prompt for the two secrets. -s hides the input, so neither value is echoed to
the screen or typed on a command line. The prompt syntax differs by shell —
in zsh, read -p means "read from a coprocess", not "print this prompt", so the
bash form fails there:
# bash
read -rsp 'Access token: ' FB_INPUT_TOKEN; echo
read -rsp 'App secret: ' FB_APP_SECRET; echo
# zsh (the macOS default)
read -rs 'FB_INPUT_TOKEN?Access token: '; echo
read -rs 'FB_APP_SECRET?App secret: '; echo
Then, in either shell:
export FB_APP_ID='<your app id>'
export FB_INPUT_TOKEN FB_APP_SECRET
python3 <skill-dir>/scripts/debug_token_probe.py
unset FB_INPUT_TOKEN FB_APP_SECRET FB_APP_ID
The script needs only the Python 3 standard library — no packages to install. The
developer pastes back only the printed JSON. Example of the token-free output to
expect — note there is no token, no app secret, and no user_id; that is the
whole point:
{
"is_valid": true,
"type": "USER",
"app_id": "1234567890123456",
"application": "Example App",
"issued_at": 1785000000,
"expires_at": 1792800000,
"data_access_expires_at": 1800000000,
"scopes": ["public_profile", "email", "pages_show_list", "pages_read_engagement"]
}
Interpretation & Report Format
Token summary
- Valid? Token
type(User / Page / App / System User)? - Owning app: does
app_idmatch the app the call was made with? - Expiry:
expires_atin the past → expired;0→ never expires;data_access_expires_atin the past → data-access window lapsed (re-auth needed). - Scopes present vs. the scopes the failing call requires.
Diagnosis (common cases)
| Signal | Meaning | Fix |
|---|---|---|
is_valid=false, error 190 subcode 463 |
Session expired | Re-authenticate the user |
is_valid=false, error 190 (no subcode) |
Invalid or revoked token | Re-issue the token |
expires_at in the past |
Expired short-lived token | Exchange for a long-lived token, or re-login |
Required scope missing from scopes |
Permission not granted/approved | Request the scope; run App Review (/app-review-prep) |
app_id ≠ the calling app |
Token minted for a different app | Use a token issued by the correct app |
type not what the endpoint expects (User vs Page vs App) |
Wrong token type | Mint the correct token type for that endpoint |
Next steps
- Concrete remediation for the diagnosed cause, then cross-links (below).
Acceptable vs unacceptable usage
- ✅ Interpreting redacted
debug_tokenmetadata the developer brings back - ✅ Linking the developer to the web Access Token Debugger
- ✅ Pointing the developer to the bundled
scripts/debug_token_probe.py(reads the token/app secret from environment variables by name) for them to run themselves - ✅ Emitting a command that references the credentials by environment-variable
name only (
"$FB_INPUT_TOKEN"), for the developer to run in their own shell - ❌ Accepting a raw or live access token as chat input
- ❌ Asking for an app secret
- ❌ Echoing a token back, or storing a token anywhere
- ❌ Emitting a command containing a literal token or app secret — including a
placeholder the developer substitutes. The value would land in their shell
history and, for the life of the process, in
psoutput - ❌ Running the
read/exportcommands or the script yourself. The developer runs them in their own shell; you never hold the credential
Tips
- Error code
190is the catch-allOAuthException; the subcode disambiguates it (463 = expired, 467 = invalid, etc.). Always ask for the subcode. - If the developer can't run
debug_token, the web Access Token Debugger needs no setup — start there. - Related:
/api-integration(token types & auth setup) and/api-health(rate limits/quota, once the token works).