TryGhost/Ghost · error · NotFoundError
ApiKey not found.
Error message
ApiKey not found.
What it means
A NotFoundError from `IntegrationsService.edit` when `options.keyid` is set and `ApiKeyModel.findOne({id: options.keyid})` returns no model. This path refreshes an integration's API-key secret; if the supplied key id does not exist the service refuses to proceed. It does not check the key's integration ownership beyond existence.
Source
Thrown at ghost/core/core/server/services/integrations/integrations-service.js:19
const {NotFoundError, InternalServerError} = require('@tryghost/errors');
const tpl = require('@tryghost/tpl');
const messages = {
notFound: '{resource} not found.'
};
class IntegrationsService {
constructor({IntegrationModel, ApiKeyModel}) {
this.IntegrationModel = IntegrationModel;
this.ApiKeyModel = ApiKeyModel;
}
async edit(data, options) {
if (options.keyid) {
const model = await this.ApiKeyModel.findOne({id: options.keyid});
if (!model) {
throw new NotFoundError({
message: tpl(messages.notFound, {
resource: 'ApiKey'
})
});
}
try {
await this.ApiKeyModel.refreshSecret(model.toJSON(), Object.assign({}, options, {id: options.keyid}));
return await this.IntegrationModel.findOne({id: options.id}, {
withRelated: ['api_keys', 'webhooks']
});
} catch (err) {
throw new InternalServerError({
err: err
});
}
}
View on GitHub (pinned to 47d8b0e2ad)
Solutions
- Re-fetch the integration's current API keys (`GET /integrations/{id}/?include=api_keys`) and use a live `keyid`.
- Confirm the `keyid` belongs to the integration being edited.
- Remove the `keyid` param if you only want to edit integration metadata, not refresh a secret.
- If the key truly no longer exists, create a new API key for the integration instead of refreshing a dead one.
Example fix
// before
await integrations.edit({id}, {keyid: staleKeyId, ...}); // -> NotFoundError
// after
const fresh = await integrations.findOne({id}, {withRelated: ['api_keys']});
const keyid = fresh.api_keys[0].id;
await integrations.edit({id}, {keyid}); Defensive patterns
Strategy: validation
Validate before calling
async function assertApiKeyExists(apiKeyModel, keyid) {
const m = await apiKeyModel.findOne({id: keyid});
if (!m) throw new Error(`ApiKey ${keyid} not found; refresh the integration's keys`);
return m;
} Type guard
const apiKeyExists = async (model, id) => Boolean(await model.findOne({id})); Try / catch
try {
await integrations.edit({id}, {keyid});
} catch (err) {
if (err.type === 'NotFoundError' && /ApiKey not found/i.test(err.message)) refreshApiKeys();
else throw err;
} Prevention
- Re-fetch the integration's api_keys before refreshing a secret.
- Confirm keyid belongs to the integration being edited.
- Omit keyid when only editing metadata.
When it happens
Trigger: PUT to edit an integration with a `keyid` parameter referring to an API key id that was deleted, never existed, or belongs to a different integration. The `require`-less `findOne` returns null and the service throws before attempting the secret refresh.
Common situations: The API key was rotated/deleted in a separate request before this call; the client cached a stale key id; the key id was copy-pasted incorrectly (truncated/wrong field); an integration was recreated and old key ids are invalid.
Related errors
- Integration not found.
- ${entityType} with id ${id} not found
- ${entityType} with id ${id} not found
- Cannot disconnect Stripe whilst you have active subscription
AI-assisted analysis of TryGhost/Ghost@47d8b0e2ad (2026-08-13).
Data as JSON: /api/errors/fc8a4f664d369368.
Report an issue: GitHub.