Use it when your goal in security and identity is clear and you can limit it to the data and actions it actually needs.
Rest Graphql Debug
Debug REST/GraphQL APIs: status codes, auth, schemas, repro
What does it add to Hermes?
Debug REST/GraphQL APIs: status codes, auth, schemas, repro
Rest Graphql Debug is a skill related to security and identity. It focuses on risk checks or access management. These tools are sensitive because they may see account or infrastructure information.
This plain-language explanation is based on the publisher description. The original text remains visible for verification.
Do not add it merely to experiment when Hermes already has a simpler path, or when you cannot review its source and permissions.
Best for users who want a repeatable way of working inside Hermes.
Use test data or read-only access and confirm findings include evidence without automatic action.
Debug REST/GraphQL APIs: status codes, auth, schemas, repro
This entry was indexed from Hermes Optional Skills. Our explanation interprets the type and domain without inventing a capability not present upstream.
The source is official or editorially reviewed, but you still need to review permissions and version compatibility.
Inspect, install, then test.
- 01Open the source
Match the publisher, license, and description to your need. Check the real update history.
- 02Review permissions and secrets
Never paste a secret value into this site. Use environment-variable names and grant the smallest scope.
- 03Copy setup only after review
The controls below copy text. They do not execute commands on your device.
- 04Test with a non-sensitive task
Inspect the visible tools, then exclude write or delete tools you do not need.
Review the command, then copy it.
hermes skills install rest-graphql-debugHermes Belarabi does not execute this command. Installation happens on your device and remains subject to Hermes scanning and your review.
Exactly what Hermes loads when this skill runs.
Reproduced from the official documentation. Read it before enabling the skill: this text becomes the agent's instructions.
Debug REST/GraphQL APIs: status codes, auth, schemas, repro.
Skill metadata
A lookup table. Do not read it all; find the row that applies to you.
| Source | Optional — install with hermes skills install official/software-development/rest-graphql-debug |
| Path | optional-skills/software-development/rest-graphql-debug |
| Version | 1.2.0 |
| Author | eren-karakus0 |
| License | MIT |
| Platforms | linux, macos, windows |
| Tags | api, rest, graphql, http, debugging, testing, curl, integration |
| Related skills | systematic-debugging, test-driven-development |
Reference: full SKILL.md
Explains the idea itself. Read it slowly; the later sections build on it.
Drive REST and GraphQL diagnosis through Hermes tools — terminal for curl, execute_code for Python requests, web_extract for vendor docs. Isolate the failing layer before guessing at the fix.
When to Use
Explains the idea itself. Read it slowly; the later sections build on it.
- API returns unexpected status or body
- Auth fails (401/403 after token refresh, OAuth, API key)
- Works in Postman but fails in code
- Webhook / callback integration debugging
- Building or reviewing API integration tests
- Rate limiting or pagination issues
Skip for UI rendering, DB query tuning, or DNS/firewall infra (escalate).
Core Principle
Explains the idea itself. Read it slowly; the later sections build on it.
Isolate the layer, then fix. A 200 OK can hide broken data. A 500 can mask a one-character auth typo. Walk the chain in order; never skip a step.
1. Connectivity → can we reach the host at all?
1.5 Timeouts → connect-slow vs read-slow?
2. TLS/SSL → cert valid and trusted?
3. Auth → credentials correct and unexpired?
4. Request format → payload shape match server expectations?
5. Response parse → does our code accept what came back?
6. Semantics → does the data mean what we assume?5-Minute Quickstart
Ordered, practical steps. Run one and confirm it worked before moving on.
REST via terminal
# Verbose request/response exchange
terminal('curl -v https://api.example.com/users/1')
# POST with JSON
terminal("""curl -X POST https://api.example.com/users \\
-H 'Content-Type: application/json' \\
-H "Authorization: Bearer $TOKEN" \\
-d '{"name":"test","email":"test@example.com"}'""")
# Headers only
terminal('curl -sI https://api.example.com/health')
# Pretty-print JSON
terminal('curl -s https://api.example.com/users | python3 -m json.tool')GraphQL via terminal
terminal("""curl -X POST https://api.example.com/graphql \\
-H 'Content-Type: application/json' \\
-H "Authorization: Bearer $TOKEN" \\
-d '{"query":"{ user(id: 1) { name email } }"}'""")GraphQL gotcha: servers often return HTTP 200 even when the query failed. Always inspect the errors field regardless of status code:
execute_code('''
resp = requests.post(
"https://api.example.com/graphql",
json={"query": "{ user(id: 1) { name email } }"},
headers={"Authorization": f"Bearer {os.environ['TOKEN']}"},
timeout=10,
)
data = resp.json()
if data.get("errors"):
for err in data["errors"]:
print(f"GraphQL error: {err['message']} (path: {err.get('path')})")
print(data.get("data"))
''')Python (requests) via executecode
execute_code('''
resp = requests.get(
"https://api.example.com/users/1",
headers={"Authorization": "Bearer <TOKEN>"},
timeout=(3.05, 30), # (connect, read)
)
print(resp.status_code, dict(resp.headers))
print(resp.text[:500])
''')Layered Debug Flow
A troubleshooting section. Find the symptom that matches yours rather than reading it end to end.
Step 1 — Connectivity
terminal('nslookup api.example.com')
terminal('curl -v --connect-timeout 5 https://api.example.com/health')Failures: DNS not resolving, firewall, VPN required, proxy missing.
Step 1.5 — Timeouts
Distinguish can't reach from reaches but slow:
terminal('''curl -w "dns:%{time_namelookup}s connect:%{time_connect}s tls:%{time_appconnect}s ttfb:%{time_starttransfer}s total:%{time_total}s\\n" \\
-o /dev/null -s https://api.example.com/endpoint''')In Python, always pass a tuple timeout — requests has no default and will hang forever:
execute_code('''
from requests.exceptions import ConnectTimeout, ReadTimeout
try:
requests.get(url, timeout=(3.05, 30))
except ConnectTimeout:
print("Cannot reach host — DNS, firewall, VPN")
except ReadTimeout:
print("Connected but server is slow")
''')Diagnosis: high time_connect is network/firewall; high time_starttransfer with low time_connect is a slow server.
Step 2 — TLS/SSL
terminal('curl -vI https://api.example.com 2>&1 | grep -E "SSL|subject|expire|issuer"')Failures: expired cert, self-signed, hostname mismatch, missing CA bundle. Use -k only for ad-hoc debug, never in code.
Step 3 — Authentication
# Token validity check
terminal('curl -s -o /dev/null -w "%{http_code}\\n" -H "Authorization: Bearer $TOKEN" https://api.example.com/me')
# Decode JWT exp claim — handles base64url padding correctly
execute_code('''
tok = os.environ["TOKEN"]
payload = tok.split(".")[1]
payload += "=" * (-len(payload) % 4)
print(json.dumps(json.loads(base64.urlsafe_b64decode(payload)), indent=2))
''')Checklist:
- Token expired? (
expclaim in JWT) - Right scheme? Bearer vs Basic vs Token vs
X-Api-Key - Right environment? Staging key on prod is a classic
- API key in header vs query param (
?api_key=…)?
Step 4 — Request Format
terminal("""curl -v -X POST https://api.example.com/endpoint \\
-H 'Content-Type: application/json' \\
-d '{"key":"value"}' 2>&1""")Content-Type / body mismatch — the silent 415/400:
# WRONG — data= sends form-encoded, header lies
requests.post(url, data='{"k":"v"}', headers={"Content-Type": "application/json"})
# RIGHT — json= auto-sets header AND serializes
requests.post(url, json={"k": "v"})
# WRONG — Accept says XML, code calls .json()
requests.get(url, headers={"Accept": "text/xml"})
# RIGHT — let requests build multipart with boundary
requests.post(url, files={"file": open("doc.pdf", "rb")})Common: form-encoded vs JSON, missing required fields, wrong HTTP method, unencoded query params.
Step 5 — Response Parsing
Always inspect content-type before calling .json():
execute_code('''
resp = requests.post(url, json=payload, timeout=10)
print(f"status={resp.status_code}")
print(f"headers={dict(resp.headers)}")
ct = resp.headers.get("Content-Type", "")
if "application/json" in ct:
print(resp.json())
else:
print(f"unexpected content-type {ct!r}, body={resp.text[:500]!r}")
''')Failures: HTML error page where JSON expected, empty body, wrong charset.
Step 6 — Semantic Validation
Parsed cleanly — but is the data correct?
- Does
"status": "active"mean what your code thinks? - ID in response matches the one requested?
- Timestamps in expected timezone?
- Pagination returning all results, or just page 1?
HTTP Status Playbook
Explains the idea itself. Read it slowly; the later sections build on it.
401 Unauthorized — credentials missing or invalid
Authorizationheader actually present? (curl -vto confirm)- Token correct and unexpired?
- Right auth scheme? (
BearervsBasicvsToken) - Some APIs use query param (
?api_key=…) instead of header.
403 Forbidden — authenticated but not authorized
- Token has the required scopes/permissions?
- Resource owned by a different account?
- IP allowlist blocking you?
- CORS in browser? (check
Access-Control-Allow-Origin)
404 Not Found — resource doesn't exist or URL is wrong
- Path correct? (trailing slash, typo, version prefix)
- Resource ID exists?
- Right API version (
/v1/vs/v2/)? - Right base URL (staging vs prod)?
409 Conflict — state collision
- Resource already exists (duplicate create)?
- Stale
ETag/If-Match? - Concurrent modification by another process?
422 Unprocessable Entity — valid JSON, invalid data
The error body usually names the bad fields. Check:
- Field types (string vs int, date format)
- Required vs optional
- Enum values inside the allowed set
429 Too Many Requests — rate limited
Check Retry-After and X-RateLimit-* headers. Exponential backoff:
execute_code('''
def with_backoff(method, url, **kwargs):
for attempt in range(5):
resp = requests.request(method, url, **kwargs)
if resp.status_code != 429:
return resp
wait = int(resp.headers.get("Retry-After", 2 ** attempt))
time.sleep(wait)
return resp
''')5xx — server-side, usually not your fault
- 500 — server bug. Capture correlation ID, file with provider.
- 502 — upstream down. Backoff + retry.
- 503 — overloaded / maintenance. Check status page.
- 504 — upstream timeout. Reduce payload or raise timeout.
For all 5xx: backoff with jitter, alert on persistence.
Pagination & Idempotency
Explains the idea itself. Read it slowly; the later sections build on it.
Pagination. Verify you're getting all results. Look for next_cursor, next_page, total_count. Two patterns:
- Offset (
?limit=100&offset=200) — simple, can skip items if data shifts. - Cursor (
?cursor=abc123) — preferred for live or large datasets.
Idempotency. For non-idempotent operations (POST), send Idempotency-Key: <uuid> so retries don't double-charge / double-create. Mandatory for payments and orders.
Contract Validation
Explains the idea itself. Read it slowly; the later sections build on it.
Catch schema drift before it hits production:
execute_code('''
def validate_user(data: dict) -> list[str]:
errors = []
required = {"id": int, "email": str, "created_at": str}
for field, expected in required.items():
if field not in data:
errors.append(f"missing field: {field}")
elif not isinstance(data[field], expected):
errors.append(f"{field}: want {expected.__name__}, got {type(data[field]).__name__}")
return errors
resp = requests.get(f"{BASE}/users/1", headers=HEADERS, timeout=10)
issues = validate_user(resp.json())
if issues:
print(f"contract violations: {issues}")
''')Run after API upgrades, when integrating new third parties, or in CI smoke tests.
Correlation IDs
Explains the idea itself. Read it slowly; the later sections build on it.
Always capture the provider's request ID — fastest path to vendor support:
execute_code('''
resp = requests.post(url, json=payload, headers=headers, timeout=10)
request_id = (
resp.headers.get("X-Request-Id")
or resp.headers.get("X-Trace-Id")
or resp.headers.get("CF-Ray") # Cloudflare
)
if resp.status_code >= 400:
print(f"failed status={resp.status_code} req_id={request_id} ts={resp.headers.get('Date')}")
''')Vendor bug-report template:
Endpoint: POST /api/v1/orders
Request ID: req_abc123xyz
Timestamp: 2026-03-17T14:30:00Z
Status: 500
Expected: 201 with order object
Actual: 500 {"error":"internal server error"}
Repro: curl -X POST … (auth: <REDACTED>)Regression Test Template
Settings you configure once. Change one at a time so you can see what each does. Set BASE_URL, API_BASE_URL in your environment, not in the chat.
Drop this into tests/ and run via terminal('pytest tests/test_api_smoke.py -v'):
BASE_URL = os.environ.get("API_BASE_URL", "https://api.example.com")
TOKEN = os.environ.get("API_TOKEN", "")
HEADERS = {"Authorization": f"Bearer {TOKEN}"}
class TestAPISmoke:
def test_health(self):
resp = requests.get(f"{BASE_URL}/health", timeout=5)
assert resp.status_code == 200
def test_list_users_returns_array(self):
resp = requests.get(f"{BASE_URL}/users", headers=HEADERS, timeout=10)
assert resp.status_code == 200
data = resp.json()
assert isinstance(data.get("data", data), list)
def test_get_user_required_fields(self):
resp = requests.get(f"{BASE_URL}/users/1", headers=HEADERS, timeout=10)
assert resp.status_code in (200, 404)
if resp.status_code == 200:
user = resp.json()
assert "id" in user and "email" in user
def test_invalid_auth_returns_401(self):
resp = requests.get(
f"{BASE_URL}/users",
headers={"Authorization": "Bearer invalid-token"},
timeout=10,
)
assert resp.status_code == 401Security
Explains the idea itself. Read it slowly; the later sections build on it.
Token handling
- Never log full tokens. Redact:
Bearer <REDACTED>. - Never hardcode tokens in scripts. Read from env (
os.environ["API_TOKEN"]) or${HERMES_HOME:-~/.hermes}/.env. - Rotate immediately if a token surfaces in logs, error messages, or git history.
Safe logging
def redact_auth(headers: dict) -> dict:
sensitive = {"authorization", "x-api-key", "cookie", "set-cookie"}
return {k: ("<REDACTED>" if k.lower() in sensitive else v) for k, v in headers.items()}Leak checklist
- [ ] Credentials in URLs. API keys in query strings end up in server logs, browser history, referrer headers — use headers.
- [ ] PII in error responses.
404 on /users/123shouldn't reveal whether the user exists (enumeration). - [ ] Stack traces in prod. 500s shouldn't leak file paths, framework versions.
- [ ] Internal hostnames/IPs.
10.x.x.x,internal-api.corp.localin error bodies. - [ ] Tokens echoed back. Some APIs include the auth token in error details. Verify they don't.
- [ ] Verbose
Server/X-Powered-By. Stack-info leaks. Note for security review.
Hermes Tool Patterns
Settings you configure once. Change one at a time so you can see what each does. Set API_TOKEN in your environment, not in the chat.
terminal — for curl, dig, openssl
terminal('curl -sI https://api.example.com')
terminal('openssl s_client -connect api.example.com:443 -servername api.example.com </dev/null 2>/dev/null | openssl x509 -noout -dates')executecode — for multi-step Python flows
When debugging spans auth → fetch → paginate → validate, use execute_code. Variables persist for the script, results print to stdout, no risk of token spam in your context:
execute_code('''
token = os.environ["API_TOKEN"]
base = "https://api.example.com"
H = {"Authorization": f"Bearer {token}"}
# 1. auth
me = requests.get(f"{base}/me", headers=H, timeout=10)
print(f"auth {me.status_code}")
# 2. paginate
all_users, cursor = [], None
while True:
params = {"cursor": cursor} if cursor else {}
r = requests.get(f"{base}/users", headers=H, params=params, timeout=10)
body = r.json()
all_users.extend(body["data"])
cursor = body.get("next_cursor")
if not cursor:
break
print(f"users={len(all_users)}")
''')webextract — for vendor API docs
Pull the spec for the endpoint you're debugging instead of guessing:
web_extract(urls=["https://docs.example.com/api/v1/users"])delegatetask — for full CRUD test sweeps
delegate_task(
goal="Test all CRUD endpoints for /api/v1/users",
context="""
Follow the rest-graphql-debug skill (optional-skills/software-development/rest-graphql-debug).
Base URL: https://api.example.com
Auth: Bearer token from API_TOKEN env var.
For each verb (POST, GET, PATCH, DELETE):
- happy path: assert status + response schema
- error cases: 400, 404, 422
- log a repro curl for any failure (redact tokens)
Output: pass/fail per endpoint + correlation IDs for failures.
""",
toolsets=["terminal", "file"],
)Output Format
Explains the idea itself. Read it slowly; the later sections build on it.
When reporting findings:
## Finding
Endpoint: POST /api/v1/users
Status: 422 Unprocessable Entity
Req ID: req_abc123xyz
## Repro
curl -X POST https://api.example.com/api/v1/users \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <REDACTED>' \
-d '{"name":"test"}'
## Root Cause
Missing required field `email`. Server validation rejects before processing.
## Fix
-d '{"name":"test","email":"test@example.com"}'