tinyhumansai/openhuman · error · Error

${method} ${resourcePath} failed: ${message}

Error message

${method} ${resourcePath} failed: ${message}

What it means

The request() helper wraps fetch against https://api.appstoreconnect.apple.com/v1 and, on any non-2xx, formats the App Store Connect error envelope (payload.errors[] of {status, code, detail}) into the thrown message. So this error's text is ASC's own diagnosis: HTTP status plus machine-readable code plus human detail, one line per error — read it before changing anything.

Source

Thrown at scripts/ios-appstore-metadata.mjs:82

) {
  const res = await fetch(`${apiBase}${resourcePath}`, {
    method,
    headers: {
      Authorization: `Bearer ${jwt}`,
      Accept: "application/json",
      ...(body && !raw ? { "Content-Type": "application/json" } : {}),
      ...headers,
    },
    body: body ? (raw ? body : JSON.stringify(body)) : undefined,
  });
  const text = await res.text();
  const payload = text ? JSON.parse(text) : null;
  if (!res.ok) {
    const message =
      payload?.errors
        ?.map((e) => `${e.status} ${e.code}: ${e.detail}`)
        .join("\n") || text;
    throw new Error(`${method} ${resourcePath} failed: ${message}`);
  }
  return payload;
}

async function uploadOperation(operation, fileBuffer) {
  const headers = Object.fromEntries(
    (operation.requestHeaders || []).map((h) => [h.name, h.value]),
  );
  const offset = Number(operation.offset || 0);
  const length = Number(operation.length || fileBuffer.length);
  const chunk = fileBuffer.subarray(offset, offset + length);
  const res = await fetch(operation.url, {
    method: operation.method,
    headers,
    body: chunk,
  });
  if (!res.ok) {
    throw new Error(

View on GitHub (pinned to a221052e0d)

Solutions

  1. Read each `status code: detail` line in the message — the ASC code names the exact problem (NOT_AUTHORIZED vs NOT_FOUND vs ENTITY_ERROR need opposite fixes)
  2. 401: verify ASC_KEY_ID matches the .p8 filename, the issuer id is the team issuer shown in ASC → Integrations, and the key has not been revoked; re-run to mint a fresh JWT
  3. 404: confirm the numeric ASC_APP_ID (App Store Connect → App → the id in the URL) and that the app/appStoreVersion exists
  4. 409/422: fix the named field in the fastlane/metadata/en-US text files per the detail line
  5. 429: wait and re-run; the script is idempotent per-run, so a clean retry after the rate window resolves it

Example fix

# before — old revoked key id still exported:
export ASC_KEY_ID="OLDKEY0001"

# after — key id matches the newly downloaded .p8:
export ASC_KEY_ID="NEWKEY9999"   # AuthKey_NEWKEY9999.p8
Defensive patterns

Strategy: try-catch

Validate before calling

// Cheap smoke test before the full run: GET /apps/{id} with the same JWT
// A 200 proves key id + issuer + .p8 are valid and the app is visible.
const res = await fetch(`https://api.appstoreconnect.apple.com/v1/apps/${appId}`, {
  headers: { Authorization: `Bearer ${jwt}` },
});
if (res.status === 401) { console.error('ASC key invalid/revoked — fix credentials before the real run'); process.exit(1); }
if (res.status === 404) { console.error(`ASC_APP_ID ${appId} not visible to this key`); process.exit(1); }

Try / catch

try {
  await request('POST', '/appInfoLocalizations', payload);
} catch (err) {
  // Message lines look like '409 ENTITY_ERROR: ...' — classify before retrying
  const lines = err.message.split('\n');
  const unauthorized = lines.some(l => l.startsWith('401 '));
  const rateLimited = lines.some(l => l.startsWith('429 '));
  if (unauthorized) throw new Error('ASC credentials rejected — check ASC_KEY_ID/ASC_ISSUER_ID/ASC_KEY_PATH');
  if (rateLimited) { await sleep(60_000); return retry(); }
  throw err; // 404/409/422 are config/validation — read the detail lines
}

Prevention

When it happens

Trigger: 401 NOT_AUTHORIZED from an expired/revoked key or wrong issuer id; 404 NOT_FOUND from a bad ASC_APP_ID or resource id in the URL; 409 ENTITY_ERROR such as a duplicate locale or validation on metadata fields; 429 RATE_LIMITED when hammering the API during screenshot uploads.

Common situations: The .p8 was regenerated in ASC (old key id revoked) but ASC_KEY_ID still names the old key; ASC_APP_ID points at an app in another team; metadata text files under fastlane/metadata/en-US exceed length limits; CI retrying the whole script and tripping rate limits.

Related errors


AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16). Data as JSON: /api/errors/2ba56f254fe5fc67. Report an issue: GitHub.