different-ai/openwork · error · ApiError

token_not_found

token_not_found

Error message

Token not found

What it means

The DELETE /tokens/:id route deletes an API token by id via tokens.revoke(). The error is thrown when no token with the supplied id exists in the store, so revoke() returned false and the handler raises a 404 ApiError with code token_not_found. It is a normal client-side 404, not a server fault.

Source

Thrown at apps/server/src/routes/core.ts:354

  addRoute(routes, "POST", "/tokens", "host", async (ctx) => {
    ensureWritable(config);
    const body = await readJsonBody(ctx.request);
    const scopeRaw = typeof body.scope === "string" ? body.scope.trim() : "";
    const scope = scopeRaw === "owner" || scopeRaw === "collaborator" || scopeRaw === "viewer" ? scopeRaw : null;
    if (!scope) {
      throw new ApiError(400, "invalid_scope", "Token scope must be owner, collaborator, or viewer");
    }
    const label = typeof body.label === "string" ? body.label.trim() : undefined;
    const issued = await tokens.create(scope, { label });
    return jsonResponse(issued, 201);
  });

  addRoute(routes, "DELETE", "/tokens/:id", "host", async (ctx) => {
    ensureWritable(config);
    const ok = await tokens.revoke(ctx.params.id);
    if (!ok) {
      throw new ApiError(404, "token_not_found", "Token not found");
    }
    return jsonResponse({ ok: true });
  });

  function rethrowEnvStoreReadError(error: unknown): never {
    if (error instanceof EnvStoreReadError) {
      throw new ApiError(
        409,
        error.code,
        "Environment variable store is invalid. Fix or remove the local env file before editing.",
      );
    }
    throw error;
  }

  // User-level env vars (see apps/app/pr/environment-variables.md). All routes
  // require the desktop host token (not owner bearer tokens). List callers can
  // request metadata-only results so renderer settings panes do not receive

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Fetch the current token list and confirm the id exists before deleting
  2. Treat a 404 token_not_found on delete as success (already revoked) and continue
  3. Check for stale cached token ids in the client; refresh the list after each revoke
  4. Verify you are pointing at the same server/data directory that issued the token

Example fix

// before
await api.delete(`/tokens/${id}`); // throws 404 if already revoked
// after
try { await api.delete(`/tokens/${id}`); }
catch (e) { if (e.code !== "token_not_found") throw e; }
Defensive patterns

Strategy: try-catch

Validate before calling

const tokens = await api.get("/tokens").then(r => r.json());
const exists = tokens.some(t => t.id === id);
if (!exists) return; // nothing to revoke

Type guard

function isTokenNotFoundError(e: unknown): e is { code: "token_not_found"; status: 404 } {
  return typeof e === "object" && e !== null && (e as { code?: string }).code === "token_not_found";
}

Try / catch

try {
  await api.delete(`/tokens/${id}`);
} catch (e) {
  if (isTokenNotFoundError(e)) return; // already revoked — treat as success
  throw e;
}

Prevention

When it happens

Trigger: Issuing DELETE /tokens/:id with an id that was never created, an id whose token was already revoked (delete is not idempotent — second call 404s), or an id from a different environment/store.

Common situations: A client retries a delete after a timeout when the first call actually succeeded; a UI holds a stale token list after another tab/agent revoked the token; tests hard-code token ids from fixtures.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/90c8d7ad132f5bf0. Report an issue: GitHub.