Skip to main content

API Credential Verification

Use before trusting a stored API key. For any "I configured <service> at some point — can you use it?" request against a stored third-party API key (mail, payments, data, SaaS). The job is to establish, with live evidence, whether the credential authenticates, what it is allowed to do, and whether the resource it must act on exists — before attempting the user-visible action.

The partner skill github-token-access-verification covers the same discipline for GitHub tokens specifically. This one is the provider-agnostic class.

Always-on rules​

  • Never print, echo, or repeat a secret value. Print only length, a SHA-256 prefix, a character-class structure mask, or HTTP status codes.
  • Never fabricate a successful action. A send/write is "done" only when the provider returned a confirmed id for it. Otherwise report the blocker.
  • Never send a real payload to a real recipient as a "test" unless the user asked for that exact action. Probe with a deliberately nonexistent resource id instead (Step 5).
  • Report in the shortest honest form: what is fixed, what is verified held, what is missing, and the one or two concrete things the user can supply to unblock it. Name the blocker and its owner; do not bury it under process narration.

Procedure​

Step 1 — Locate every copy of the credential​

Search the agent home, profile directories, state snapshots, and project .env files for the variable name. Compare copies by hash, not by eye:

import hashlib
for p in paths:
for line in open(p, errors="ignore"):
if line.strip().startswith(VAR + "="):
v = line.split("=", 1)[1].strip().strip('"').strip("'")
print(p, hashlib.sha256(v.encode()).hexdigest()[:12], len(v))

Identical hashes across profiles mean one fix must be applied everywhere; differing hashes mean you must determine which copy the running process actually reads.

Step 2 — Structure-check the value before blaming the provider​

Before concluding a key is revoked or wrong, inspect the value's shape:

  • Does it start with the variable name again (VAR=VAR=...)? A shell append that duplicated the prefix is invisible in a listing and authenticates as garbage.
  • Does it carry surrounding quotes, trailing whitespace, a trailing inline # comment, or an embedded newline?
  • Does its length and prefix match the provider's documented key format?
import re
mask = re.sub(r"[a-z]", "a", re.sub(r"[A-Z]", "A", re.sub(r"[0-9]", "9", v)))
print(len(v), mask[:60], v.startswith(VAR + "="))

Strip the defect in code and re-test before asking the user for a new key.

Step 3 — Run control probes to locate the failure layer​

Call one cheap read endpoint three times: with the real key, with an obviously bogus key, and with an empty key. Compare the bodies, not just the codes.

  • Identical, unstructured bodies for all three (e.g. a bare {"message":"Forbidden"} from a CDN/edge) → the request never reached the provider's auth layer. Suspect a malformed value (Step 2), wrong host, or wrong API version prefix.
  • A structured error that names a permission or reason for the real key only → the key authenticated; the problem is scope, and you can now enumerate it.

Use scripts/probe_api_key.py in this skill to run this sweep without hand-typing it.

Step 4 — Get the exact endpoint from current provider docs​

Consult Context7 first per the standing library-documentation gate. When Context7 is unavailable or over quota, say so and go to the provider's own docs rather than memory: many modern docs sites serve clean markdown by appending .md to any page URL and publish /llms.txt and /llms-full.txt indexes at the docs root. Take the HTTP method, full path, and required body fields from that page — guessing between /v0 and /v1, or between a collection path and a nested /{resource_id}/... path, produces a not_found that is easy to misread as a permission failure.

Step 5 — Map the permission scope with read and write probes​

Sweep the documented read endpoints and record each result. Then probe the write path using a deliberately nonexistent resource id. The status code discriminates cleanly:

  • 403 naming a missing permission → the permission itself is absent.
  • 404 not found for the fake resource → the write permission is held; only the resource is missing or invisible to this credential's scope.

This distinction decides what you ask the user for: a broader key vs. just a resource id. See references/scoped-key-permission-probes.md for the full probe table and interpretation rules.

Step 6 — Confirm the resource id exists before promising the action​

Most write actions need an id the key cannot always list: a sender inbox, an account, a project, a from-address. Search the filesystem and config for a stored id before assuming one. If it is absent everywhere and the key lacks the permission to list or create it, that is the blocker — report it instead of attempting the action.

Step 7 — Report​

State, in order: the defect you fixed and the evidence it is fixed (a real status code), the permissions verified held, the permissions verified missing, the missing resource id, and the one or two alternatives that unblock it. Explicitly say that the user-visible action was not performed. Offer the remaining safe cleanup (e.g. correcting the malformed value in every copy) as a question rather than doing it unasked when the user's request was only "try it".

Pitfalls​

  • Treat an opaque, detail-free 4xx as a malformed-credential signal, not a revocation. Edge proxies reject a badly formed Authorization header before the application sees it, so the body carries no permission detail; a key that reaches the auth layer almost always gets a structured error naming what is missing.
  • Compare every stored copy by hash before fixing one. A malformed secret propagates into profile and snapshot .env files, so repairing a single file leaves the next process reading the broken value and reproducing the same failure.
  • Do not read secrets by sourcing the file into a shell you also log. Parse the line and strip quotes/whitespace in code; sourcing masks prefix and whitespace defects that are exactly what you are testing for.
  • A credential that authenticates is not a working integration. Authentication, scope, and resource existence are three independent checks; report them separately so the user knows which one to fix.
  • Use the provider's SDK as a second opinion on error detail. A raw HTTP call and the official client can surface different amounts of error context for the same request, and the richer message often names the exact missing permission.
  • Bound filesystem searches for a resource id. A recursive walk of the whole home directory for an address or id pattern will run for minutes; scope it to config, profile, and project directories and skip node_modules, .git, and cache trees.

Support files​

  • references/scoped-key-permission-probes.md — probe table, status-code interpretation, and how to turn results into a precise ask.
  • scripts/probe_api_key.py — re-runnable control-probe and scope sweep that never prints the secret.

Supporting files: this skill's supporting files are held in the docsite at docs/15-skills/_support/engineering/api-credential-verification/ — fetch them fresh from jknash/docsite main alongside this page. Source: jknash/hermes-shared-skills · branch hermes-jkdev001 @ 1d0d545c3970 · skills/engineering/api-credential-verification/ · view source · Imported 2026-10-04. Supporting files (references, scripts) remain in the source repository.

version 1.0.0 · author Hermes Agent · license MIT.

Published by Muse · 2026-10-04.