toeverything/AFFiNE · error · UnsplashIsNotConfigured
unsplash_is_not_configured
unsplash_is_not_configured
Error message
Unsplash is not configured.
What it means
Raised by the copilot controller's GET /unsplash/photos endpoint when the deployment has no Unsplash API key configured. The route proxies Unsplash's photo search for AI features; with copilot.unsplash.key empty (its default), the proxy refuses to forward the request and throws UnsplashIsNotConfigured so the client can show a proper 'not set up' state instead of an upstream 401.
Source
Thrown at packages/backend/server/src/plugins/copilot/controller.ts:314
);
return this.mergePingStream(prepared.messageId || '', source$);
} catch (err) {
metrics.ai.counter('images_stream_errors').add(1, info);
return mapSseError(err, info);
}
}
@Get('/unsplash/photos')
@CallMetric('ai', 'unsplash')
async unsplashPhotos(
@Req() req: Request,
@Res() res: Response,
@Query() params: Record<string, string>
) {
const { key } = this.config.copilot.unsplash;
if (!key) {
throw new UnsplashIsNotConfigured();
}
const query = new URLSearchParams(params);
const response = await fetch(
`https://api.unsplash.com/search/photos?${query}`,
{
headers: { Authorization: `Client-ID ${key}` },
signal: getSignal(req).signal,
}
);
res.set({
'Content-Type': response.headers.get('Content-Type'),
'Content-Length': response.headers.get('Content-Length'),
'X-Ratelimit-Limit': response.headers.get('X-Ratelimit-Limit'),
'X-Ratelimit-Remaining': response.headers.get('X-Ratelimit-Remaining'),
});
View on GitHub (pinned to b4c8548c09)
Solutions
- Set the copilot.unsplash.key config item to a valid Unsplash Access Key (Client-ID) and restart/reload the server.
- Verify the key is non-empty in the environment the request actually hits (config dump or diagnostics).
- On the client, check the advertised copilot capabilities before showing Unsplash search, and render 'not configured' when absent.
- If you do not want the feature, stop calling the endpoint rather than relying on the error.
Example fix
# before # .env AFFINE_COPILOT_UNSPLASH_KEY= # after AFFINE_COPILOT_UNSPLASH_KEY=your_unsplash_access_key
Defensive patterns
Strategy: validation
Validate before calling
if (!config.copilot.unsplash.key) {
disableUnsplashSearchUi(); // don't call the endpoint unconfigured
} Type guard
const isUnsplashConfigured = (config: Config): boolean => Boolean(config.copilot?.unsplash?.key);
Try / catch
try {
const photos = await fetch('/api/copilot/unsplash/photos?query=' + q);
} catch (e) {
if (e?.code === 'unsplash_is_not_configured') {
showNotice('Image search is not configured on this server');
} else throw e;
} Prevention
- Set copilot.unsplash.key during deployment setup if Unsplash search is wanted.
- Expose an integration-availability flag to clients and hide Unsplash UI when unset.
- Include the key in environment checklists/diagnostics so empty keys surface at boot.
When it happens
Trigger: Calling GET /api/copilot/unsplash/photos on any deployment where the copilot.unsplash.key config item was never filled (default key: ''); key set in the wrong environment (staging var set, production empty); config reload not applied after setting the key; key string containing only whitespace.
Common situations: Fresh self-hosted installs enabling copilot but skipping the Unsplash integration step; cloud environments where only some regions configure the key; frontends not gating the image-search UI on availability.
Understand the failure class
Background: "API key is required" / "API key not found" / "No API key was set": the missing-api-key error family across 16 libraries — this error's family across 16 libraries.
Related errors
- action_forbidden
- copilot_embedding_unavailable
- Invalid config for module [${module}] with key [${key}] Valu
- A config file path is required
- invalid_app_config_input
AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18).
Data as JSON: /api/errors/62883ce5b6f1bb7b.
Report an issue: GitHub.