Build in Public · LF-12

Handling API errors: From 401, 422 to limited self-healing

Differentiate authentication, authorization, capability, parameter, and unknown results, correct requests using the current directory, and constrain retries with a decision engine that sends no network requests to prevent error handling from becoming unauthorized privilege escalation or duplicate calls.

When a request fails, the primary question for a client is not "how many more times can we try?" but rather "did this execute, and what needs to change to make a retry meaningful?" Missing keys, incorrect platform names, unsupported parameters, and dropped connections during response streaming may all trigger the exact same catch block. Unifying these retries turns correctable input into repeated failures and can even duplicate tasks that were already created.

This post uses a EveryInfra REST interface as an example to outline error stratification, directory checking, and limited self-healing practices. The sample decision engine returns only next-step recommendations; it issues no requests, switches no credentials, and never automatically expands the data scope. Current source code, free public probes, and historical business samples are used separately, avoiding any attempt to infer all authenticated failure behavior from a single 400 response.

Preserve the attempt before analyzing the error

Generate a business operation ID prior to invocation to correlate multiple attempts under the same intent. Save start and end times, request versions, target capabilities, HTTP status codes, and available service request IDs for each attempt. Business IDs are not server idempotency keys; when a service does not declare support, you cannot assume that resending with the ID will trigger deduplication.

Response bodies may be HTML, plain text, empty content, or unparseable JSON. Check Content-Type and status before attempting to parse errors. If parsing fails, retain a minimal diagnostic summary without writing raw error pages directly into user prompts or passing them to agents as instructions. By default, logs omit keys, full signed URLs, raw bodies, and other unnecessary personal data.

The current EveryInfra public error envelope contains error.code, error.message, and error.request_id. Organize handling around codes and HTTP statuses, treating messages as human-readable text. Do not rely on English messages remaining static, and do not use the first URL found in a string to automatically navigate while carrying credentials.

Diagnostic allowlists can reference the OWASP Logging Cheat Sheet: use interaction identifiers to correlate events and redact tokens, session values, and sensitive information. Error logs should provide enough context to locate an attempt without becoming an alternative source of credentials or customer data copies.

401 and 403: Handle authentication and authorization separately

HTTP 401 indicates missing valid authentication credentials; 403 indicates server refusal. Protocol statuses supply general meanings, while specific causes depend on product error codes. In the EveryInfra current implementation, missing, invalid, or expired keys are authentication issues, whereas limits on product lines, granular capabilities, and source IPs are authorization issues.

First, verify whether the request reached the correct service, whether Authorization headers match service requirements, and whether the running process reads the expected environment. Check variable existence and configuration flags without printing secrets. A logged-in browser console session does not prove that the server-side process has acquired the correct API key.

For consecutive authentication failures, pause affected tasks and notify the owner; do not cycle through other keys, switch accounts, or disable permission limits to recover. Automatic refreshing applies only to explicitly authorized flows supported by the product; long-lived API keys cannot be treated as self-renewable tokens.

Incorrect capability name: Read candidates first, then verify the target

A key-free public error can be observed as follows. Here, an obsolete google_maps identifier is intentionally passed to read the directory only, without collecting place reviews:

Observe the status and error body of an unknown platform
curl --silent --show-error --include --max-time 30 \
  'https://api.everyinfra.com/api/v1/social/catalog?platform=google_maps'

The result at 2026-09-04 20:46 (Beijing time) is HTTP 400, where error.code is unknown_capability, and the message includes google_maps_reviews alongside another close candidate. Unknown capabilities cannot be universally mapped to 404, nor should original tasks be automatically switched simply because a candidate ranks first.

If the original task specifically targets place reviews, verify whether google_maps_reviews provides reviews, what URL structure it requires, and whether its output satisfies the original requirements. Candidate matching algorithms resolve name similarities only and do not understand commercial intent; app stores and place reviews are not interchangeable data sources.

422: Return inputs to the current contract

422 generally indicates that the request content is understood, but instructions within it cannot be processed. Applications should further differentiate missing fields, unknown fields, invalid enumerations, and size limits. Their remediation paths differ: missing URLs require real targets; unknown sort options require parameter validation; oversized inputs require confirmed chunking options rather than silently discarding half the source material.

Narrow down to a specific platform to check parameters
curl -fsS --max-time 30 \
  'https://api.everyinfra.com/api/v1/social/catalog?platform=douyin' \
  | jq '[.capabilities[] | {
      action, required_params, optional_params, param_meanings, max_limit
    }]'

The current implementation produces unknown_param for unknown parameters and invalid_value for unsupported enumerations, accompanied by actionable hints. However, identical parameter names may vary across actions, meaning global parameter tables cannot replace concrete contracts. required_params, optional_params, and permitted values must be evaluated together, and general list limits are not pagination commitments.

Generate a new request version after corrections, preserving the original error and modification rationale. Apply mappings automatically only when the original intent is explicitly preserved; human decision-making is required for multiple candidates, missing real targets, expanded time ranges, or model changes. Filling missing fields with model guesses is not safe self-healing.

402 and 429: Do not misrepresent quota issues as rate limiting

EveryInfra currently uses 402 quota_exhausted to indicate insufficient quota—a product-specific convention rather than a universal 402 business meaning across all services. Quota issues require account owner intervention; lowering concurrency does not automatically increase balances, and clients must not auto-top-up or migrate credentials.

429 is a rate-limiting signal. If the response provides a valid Retry-After header, follow its instructions; its absence does not imply immediate dense retries or permit fabricating service recovery times. Retry intervals, jitter, maximum attempts, and deadlines must be explicitly defined by application policies.

Rate-limiting strategies must cover all working processes sharing a quota. If a single worker backs off while others continue dispatching, congestion remains unresolved. This guidance covers application scheduling and does not imply that the API coordinates multiple client processes on your behalf.

5xx errors, disconnects, and submitted tasks: Verify execution status first

A 5xx error cannot be simply interpreted as "nothing happened." The service may have accepted the request, generated partial results, or completed billing steps without the client receiving a full response. Lacking explicit idempotency guarantees or execution evidence, record unknown results and verify via request identifiers rather than automatically resending a POST.

When job_id or a service-provided query endpoint is available, prioritize querying the original task. Running, wrapping up, succeeded, failed, and query failures must be treated separately; query results showing 404 may involve identity or scoping issues and should not trigger alternative task creation. Confirm that queries use the identity corresponding to the original request.

Business content must still be checked following HTTP 200. MCP introduces JSON-RPC and tool error layers, preventing reliance on REST-only strategies. Empty sets, partial results, and abnormal responses retain their own states, are not uniformly marked as successes, and final billing charges cannot be derived solely from client-side status.

Microsoft's Retry pattern highlights a key reminder: retry only recoverable faults while considering operation idempotency and stacked retry layers. If SDKs, queues, and business layers each retry independently, total attempts may exceed budgets; a single layer understanding the full context should govern decisions rather than defaulting to "try again" everywhere.

A decision engine that routes without executing retries

The following is an offline application-layer example, not a EveryInfra SDK. Inputs must originate from REST attempt records that have completed protocol parsing; retry_after_policy_check must be set based on real interface agreements or explicit unexecuted evidence, and should not be enabled arbitrarily by models or error text. attempts tracks the number of prior tries.

Offline JavaScript: preserve the authorization gate and retry budget
function nextStep({status, hasJob = false, replaySafe = false,
                   attempts = 1, maxAttempts = 3}) {
  if (status !== null && (!Number.isInteger(status) || status < 100 || status > 599)) {
    throw new Error("invalid HTTP status");
  }
  if (typeof hasJob !== "boolean" || typeof replaySafe !== "boolean"
      || !Number.isSafeInteger(attempts) || attempts < 1
      || !Number.isSafeInteger(maxAttempts) || maxAttempts < 1) {
    throw new Error("invalid decision inputs");
  }
  if (status === 401 || status === 403) return "authorization_review";
  if (status === 402) return "account_review";
  if (hasJob) return "inspect_existing_job";
  if (status === 400 || status === 422) return "revise_request";
  if (status === 429 || status === null || (status >= 500 && status <= 599)) {
    return replaySafe && attempts < maxAttempts
      ? "retry_after_policy_check"
      : "inspect_attempt";
  }
  if (status >= 200 && status < 300) return "validate_delivery";
  return "manual_review";
}

// synthetic attempt; contains no network calls.
console.log(nextStep({status: 503}));
console.log(nextStep({status: 429, replaySafe: true, attempts: 3}));
console.log(nextStep({status: 202, hasJob: true}));

The three outputs require checking the current attempt, checking attempts after budget exhaustion, and querying the original task, respectively. retry_after_policy_check is not a command to retry immediately; outer layers must also check wait times, global rate limits, and user-permitted operation scopes. Constructing new attempts after parameter adjustments counts against overall business budgets and must not loop indefinitely via counter resets.

Verifying that systems stop is as important as verifying recovery

  • Invalid authentication: Accesses no alternative credentials and triggers no business retries.
  • Ambiguous capability candidates: Retains original targets, requires verification, and prevents automatic product switching.
  • Accepted requests with interrupted responses: Preserves original attempts and tasks without duplicate creation.
  • Continued failure after parameter fixes: Records new versions while adhering to overall attempt budgets.
  • Non-JSON errors, missing request IDs, unknown codes: Enters conservative handling without crashing or fabricating success.
  • Available data returned but business goals unmet: Preserves delivery status and follow-up issues without unilaterally declaring balance restoration.

Maintain a small error sample set and replay it when upgrading SDKs, modifying contracts, or altering authentication strategies. Source tests verify expected sequences, but production deployments require constrained live input evidence, with dates recorded separately for each. Do not substitute old screenshots or free directory errors for today's authenticated business acceptance.

The goal of safe self-healing is to let clearly fixable issues interrupt humans less frequently while stopping execution promptly when genuine judgment is required. As long as every step clarifies where an error occurred, what changed, whether execution took place, and why the next step is permitted, error messages become genuine integration aids rather than infinite retry triggers.