Debug Access Token
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
code and subcode (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)
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_token Graph 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 the debug_token script 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 any error.code / error.subcode / error.message. Tell them to redact user_id and 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 the debug_token script 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_id match the app the call was made with?
- Expiry:
expires_at in the past → expired; 0 → never expires; data_access_expires_at in 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_token metadata 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
ps output
- ❌ Running the
read / export commands or the script yourself. The
developer runs them in their own shell; you never hold the credential
Tips
- Error code
190 is the catch-all OAuthException; 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).
1---2name: debug-access-token3description: Diagnose a Meta access token problem (expired, wrong scopes, wrong app, invalid) WITHOUT the token ever entering the agent or AI provider context. Guides the developer to inspect the token themselves via the Access Token Debugger web tool or the public debug_token Graph API, then interprets the returned metadata. Use when an API call fails with an auth/OAuthException error (e.g. code 190) or a token isn't behaving as expected.4license: MIT5---67# Debug Access Token89Help a developer figure out why a Meta access token is failing — without ever handling the raw token yourself.1011## Security first — never handle a live token1213An 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.14151. **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.162. **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.1718## Workflow19201. **Confirm the symptom.** Ask what failed:21 - The error `code` and `subcode` (e.g. `190` / `463`)22 - Which endpoint/request failed23 - Which app the call was made with, and what the token is expected to do (which permissions/scopes)24252. **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:2627 - **A. Access Token Debugger (web):** open [https://developers.facebook.com/tools/debug/accesstoken/](https://developers.facebook.com/tools/debug/accesstoken/), paste the token there (a Meta first-party surface, not this agent), and read the results panel.28 - **B. `debug_token` Graph 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 the `debug_token` script** section below for how to run it and the expected output. Reference: [debug_token docs](https://developers.facebook.com/docs/graph-api/reference/debug_token/). Never ask the developer to send you the token or the app secret.2930 If the developer needs exact, current steps, use `devtools_discovery` (action `search_docs`) to fetch the latest Access Token Debugger / `debug_token` documentation.31323. **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 any `error.code` / `error.subcode` / `error.message`. **Tell them to redact `user_id` and 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 the `debug_token` script** below for an example of the token-free output to expect.33344. **Interpret the metadata and report** (format below).3536## Using the `debug_token` script (Option B)3738When the developer prefers the API over the web debugger, point them at the script39bundled with this skill, `scripts/debug_token_probe.py`. It reads the credentials40from **environment variables in the developer's own shell** — **by name**, never41their values — so no token or secret ever appears in the chat, in a script you42author, or on a command line. The shell provides the values at run time, and the43script prints only redacted metadata. Do not re-derive or paste the script into the44chat; it is already checked in and vetted — just tell the developer how to run it.4546**Give the developer the absolute path.** The script lives inside the installed47plugin, not in the developer's working directory, so a relative path will not48resolve for them. Substitute the absolute path of this skill's directory — you know49it, having just read `SKILL.md` from there — wherever `<skill-dir>` appears below.5051Have them run this in a **throwaway shell, not the shell they start their agent52from**: an exported variable is inherited by every process that shell launches, so a53token left exported can reach an agent started later from the same terminal.5455Prompt for the two secrets. `-s` hides the input, so neither value is echoed to56the screen or typed on a command line. **The prompt syntax differs by shell** —57in zsh, `read -p` means "read from a coprocess", not "print this prompt", so the58bash form fails there:5960```bash61# bash62read -rsp 'Access token: ' FB_INPUT_TOKEN; echo63read -rsp 'App secret: ' FB_APP_SECRET; echo64```6566```zsh67# zsh (the macOS default)68read -rs 'FB_INPUT_TOKEN?Access token: '; echo69read -rs 'FB_APP_SECRET?App secret: '; echo70```7172Then, in either shell:7374```bash75export FB_APP_ID='<your app id>'76export FB_INPUT_TOKEN FB_APP_SECRET7778python3 <skill-dir>/scripts/debug_token_probe.py7980unset FB_INPUT_TOKEN FB_APP_SECRET FB_APP_ID81```8283The script needs only the Python 3 standard library — no packages to install. The84developer pastes back only the printed JSON. Example of the token-free output to85expect — note there is **no token, no app secret, and no `user_id`**; that is the86whole point:8788```json89{90 "is_valid": true,91 "type": "USER",92 "app_id": "1234567890123456",93 "application": "Example App",94 "issued_at": 1785000000,95 "expires_at": 1792800000,96 "data_access_expires_at": 1800000000,97 "scopes": ["public_profile", "email", "pages_show_list", "pages_read_engagement"]98}99```100101## Interpretation & Report Format102103**Token summary**104- Valid? Token `type` (User / Page / App / System User)?105- Owning app: does `app_id` match the app the call was made with?106- Expiry: `expires_at` in the past → expired; `0` → never expires; `data_access_expires_at` in the past → data-access window lapsed (re-auth needed).107- Scopes present vs. the scopes the failing call requires.108109**Diagnosis (common cases)**110111| Signal | Meaning | Fix |112|--------|---------|-----|113| `is_valid=false`, error 190 subcode 463 | Session expired | Re-authenticate the user |114| `is_valid=false`, error 190 (no subcode) | Invalid or revoked token | Re-issue the token |115| `expires_at` in the past | Expired short-lived token | Exchange for a long-lived token, or re-login |116| Required scope missing from `scopes` | Permission not granted/approved | Request the scope; run App Review (`/app-review-prep`) |117| `app_id` ≠ the calling app | Token minted for a different app | Use a token issued by the correct app |118| `type` not what the endpoint expects (User vs Page vs App) | Wrong token type | Mint the correct token type for that endpoint |119120**Next steps**121- Concrete remediation for the diagnosed cause, then cross-links (below).122123## Acceptable vs unacceptable usage124125- ✅ Interpreting **redacted** `debug_token` metadata the developer brings back126- ✅ Linking the developer to the web Access Token Debugger127- ✅ 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 themselves128- ✅ Emitting a command that references the credentials **by environment-variable129 name only** (`"$FB_INPUT_TOKEN"`), for the developer to run in their own shell130- ❌ Accepting a raw or live access token as chat input131- ❌ Asking for an app secret132- ❌ Echoing a token back, or storing a token anywhere133- ❌ Emitting a command containing a literal token or app secret — including a134 placeholder the developer substitutes. The value would land in their shell135 history and, for the life of the process, in `ps` output136- ❌ Running the `read` / `export` commands or the script **yourself**. The137 developer runs them in their own shell; you never hold the credential138139## Tips140141- Error code `190` is the catch-all `OAuthException`; the **subcode** disambiguates it (463 = expired, 467 = invalid, etc.). Always ask for the subcode.142- If the developer can't run `debug_token`, the web Access Token Debugger needs no setup — start there.143- Related: `/api-integration` (token types & auth setup) and `/api-health` (rate limits/quota, once the token works).