juanfont/headscale · warning

%w: api key %d not found

Error message

%w: api key %d not found

What it means

Returned by apiKeyPrefixForID when 'headscale apikeys delete --id N' cannot find any key whose Id matches N in the ListApiKeys response — the prefix resolution needed for the DELETE path failed. Wraps errMissingParameter, so errors.Is(err, errMissingParameter) is true.

Source

Thrown at cmd/headscale/cli/api_key.go:222

	id uint64,
) (string, error) {
	resp, err := client.ListApiKeysWithResponse(ctx)
	if err != nil {
		return "", fmt.Errorf("listing api keys: %w", err)
	}

	if resp.StatusCode() != http.StatusOK {
		return "", apiError(resp.StatusCode(), resp.ApplicationproblemJSONDefault)
	}

	idStr := strconv.FormatUint(id, util.Base10)
	for _, key := range resp.JSON200.ApiKeys {
		if key.Id == idStr {
			return key.Prefix, nil
		}
	}

	return "", fmt.Errorf("%w: api key %d not found", errMissingParameter, id)
}

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Run 'headscale apikeys list' and copy the current id or prefix
  2. If the key is already gone, the goal is achieved — treat as success in scripts
  3. Prefer --prefix in automation since it addresses the key directly without the list/resolve step

Example fix

# before
headscale apikeys delete --id 42

# after
headscale apikeys list
headscale apikeys delete --prefix $(headscale apikeys list -o json | jq -r '.apiKeys[0].prefix')
Defensive patterns

Strategy: validation

Validate before calling

// verify the id exists before attempting delete-by-id
keys, err := listAPIKeys(ctx, client)
if err != nil { return err }
found := slices.ContainsFunc(keys, func(k ApiKey) bool { return k.Id == idStr })
if !found { return nil /* already gone: treat as success */ }

Try / catch

if err := deleteKey(ctx, client, id); err != nil {
	if strings.Contains(err.Error(), "not found") { return nil }
	return err
}

Prevention

When it happens

Trigger: Deleting by an --id that does not exist: already-deleted key, id from a different server/database, or a typo. The list call succeeds but no entry matches the formatted id string.

Common situations: Key was expired and pruned between listing and deleting; scripts reusing stale ids after a DB reset; confusing the key id with the node or user id.

Related errors


AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15). Data as JSON: /api/errors/26b0033ccfd8572c. Report an issue: GitHub.