Every API here is available over the APIs.io API and to AI agents over MCP.
MCP server
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
Tools for apis
7 MCP tools reach this
find_apisBrowse and filter every API in the catalog.
get_api_artifactsOne API's artifacts, grouped by type.
get_openapiThe primary OpenAPI for this API.
find_similar_apisAPIs that look like this one.
apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
resolveTurn a domain, URL or GitHub org into the provider it belongs to.
find_cohortsEvery scored population of providers in the catalog.
All 92 tools →
Call it yourself
curl for this page
This API
curl "https://apis.io/api/v1/apis/jenriks-com:jenriks-com-nlweb-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"
Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.
Get an API key
Free tier, no form to fill in. Signing in shares your email address with us — we
store it to create your key and to recognise you if you sign in with another
provider. See our Privacy Policy and
Terms.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
title: JENRIKS public NL Web API
version: 1.1.0
summary: Read-only JSON API, NLWeb endpoint and MCP server for the JENRIKS daily photoblog (one photo per day since 2006).
description: 'Everything here is public, read-only and free: no API key, no OAuth, no accounts, no write operations. Stable REST operations carry a major version in the path (/api/v1). Backward-compatible fields may be added within v1; breaking changes get a new major path. Before a version is removed, JENRIKS publishes a migration note at /developers/#versioning and signals it with RFC 9745 Deprecation and Link headers; a dated Sunset header is announced at least 90 days ahead. No sunset is scheduled. Search results page by offset or by an opaque cursor (next_cursor); POST /api/v1/batch runs up to 20 GET requests in one call. Every error is an RFC 9457 problem document (application/problem+json) with a stable code and a resolution hint. Quota: 120 requests per minute per client IP on /api, /mcp and /ask, advertised through RateLimit headers; a 429 carries Retry-After. Every page URL /x/y/ has a Markdown twin at /x/y.md (the homepage at /index.md); Accept: text/markdown or ?mode=agent on the page URL returns the same text, and machine documents wrap as /openapi.json.md, /server.json.md, /api/v1.md or /.well-known/.md. Photographs are copyrighted: credit the photographer and link the page; licensing via https://jenriks.com/contact/.'
termsOfService: https://jenriks.com/developers/#licensing
contact:
name: JENRIKS
url: https://jenriks.com/contact/
email: jenriks@gmail.com
license:
name: 'Photographs: all rights reserved. API documents: may be copied freely.'
url: https://jenriks.com/developers/#licensing
servers:
- url: https://jenriks.com
description: JENRIKS production
tags:
- name: NLWeb
description: Natural-language search endpoint following the NLWeb protocol (JSON or SSE)
paths:
/ask:
get:
operationId: nlwebAskGet
tags:
- NLWeb
summary: Natural-language search (NLWeb 0.55, flat query parameters)
security: []
description: 'NLWeb /ask over the photo archive: the query is matched as free text against title, place, categories, tags and photographer, newest first, scored 100 down by rank. JSON answer by default; server-sent events with streaming=true or Accept: text/event-stream.'
parameters:
- name: query
in: query
required: true
description: Natural-language query; substring match over the archive, falling back to all-tokens-must-match
schema:
type: string
maxLength: 200
example: fog barcelona
- name: site
in: query
description: Accepted for protocol compatibility; only jenriks.com is searched
schema:
type: string
- name: streaming
in: query
description: 'true or 1 switches the response to server-sent events; Accept: text/event-stream does the same'
schema:
type: boolean
default: false
- name: mode
in: query
schema:
type: string
enum:
- list
default: list
- name: max_results
in: query
schema:
type: integer
minimum: 1
maximum: 50
default: 10
responses:
'200':
description: NLWeb answer (JSON by default; text/event-stream with start, result and complete events when streaming was requested). Zero matches are a 200 failure object with code NO_RESULTS.
headers:
RateLimit-Policy:
description: 'Quota policy, IETF RateLimit structured field: "public-read";q=120;w=60'
schema:
type: string
RateLimit:
description: 'Remaining quota and seconds until reset: "public-read";r=57;t=41'
schema:
type: string
RateLimit-Limit:
description: Compatibility form of the quota
schema:
type: integer
RateLimit-Remaining:
description: Compatibility form of the remaining quota
schema:
type: integer
RateLimit-Reset:
description: Seconds until the window resets
schema:
type: integer
content:
application/json:
schema:
$ref: '#/components/schemas/NlwebAnswer'
text/event-stream:
schema:
type: string
description: 'event: start, then one event: result per item ({index, item}), then event: complete; event: error before complete on failure'
'400':
description: Missing or empty query, or a malformed body
headers:
RateLimit-Policy:
description: 'Quota policy, IETF RateLimit structured field: "public-read";q=120;w=60'
schema:
type: string
RateLimit:
description: 'Remaining quota and seconds until reset: "public-read";r=57;t=41'
schema:
type: string
RateLimit-Limit:
description: Compatibility form of the quota
schema:
type: integer
RateLimit-Remaining:
description: Compatibility form of the remaining quota
schema:
type: integer
RateLimit-Reset:
description: Seconds until the window resets
schema:
type: integer
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
'405':
description: Method not allowed
headers:
RateLimit-Policy:
description: 'Quota policy, IETF RateLimit structured field: "public-read";q=120;w=60'
schema:
type: string
RateLimit:
description: 'Remaining quota and seconds until reset: "public-read";r=57;t=41'
schema:
type: string
RateLimit-Limit:
description: Compatibility form of the quota
schema:
type: integer
RateLimit-Remaining:
description: Compatibility form of the remaining quota
schema:
type: integer
RateLimit-Reset:
description: Seconds until the window resets
schema:
type: integer
Allow:
schema:
type: string
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
'429':
description: Rate limit exceeded (120 requests per minute per client IP)
headers:
RateLimit-Policy:
description: 'Quota policy, IETF RateLimit structured field: "public-read";q=120;w=60'
schema:
type: string
RateLimit:
description: 'Remaining quota and seconds until reset: "public-read";r=57;t=41'
schema:
type: string
RateLimit-Limit:
description: Compatibility form of the quota
schema:
type: integer
RateLimit-Remaining:
description: Compatibility form of the remaining quota
schema:
type: integer
RateLimit-Reset:
description: Seconds until the window resets
schema:
type: integer
Retry-After:
schema:
type: integer
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
default:
description: Any other error, as an RFC 9457 problem document
headers:
RateLimit-Policy:
description: 'Quota policy, IETF RateLimit structured field: "public-read";q=120;w=60'
schema:
type: string
RateLimit:
description: 'Remaining quota and seconds until reset: "public-read";r=57;t=41'
schema:
type: string
RateLimit-Limit:
description: Compatibility form of the quota
schema:
type: integer
RateLimit-Remaining:
description: Compatibility form of the remaining quota
schema:
type: integer
RateLimit-Reset:
description: Seconds until the window resets
schema:
type: integer
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
post:
operationId: nlwebAsk
tags:
- NLWeb
summary: Natural-language search (NLWeb 0.55, structured body)
security: []
description: 'Same search as GET /ask. Accepts the NLWeb 0.55 body ({query: {text, site}, context: {prev}, prefer: {streaming, mode}, meta: {version}}) or the flat legacy fields as JSON or form data. Streams when prefer.streaming is true or Accept contains text/event-stream.'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/NlwebRequest'
example:
query:
text: fog barcelona
site: jenriks.com
prefer:
streaming: false
mode: list
meta:
version: '0.55'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/NlwebRequest'
responses:
'200':
description: NLWeb answer (JSON by default; text/event-stream with start, result and complete events when streaming was requested). Zero matches are a 200 failure object with code NO_RESULTS.
headers:
RateLimit-Policy:
description: 'Quota policy, IETF RateLimit structured field: "public-read";q=120;w=60'
schema:
type: string
RateLimit:
description: 'Remaining quota and seconds until reset: "public-read";r=57;t=41'
schema:
type: string
RateLimit-Limit:
description: Compatibility form of the quota
schema:
type: integer
RateLimit-Remaining:
description: Compatibility form of the remaining quota
schema:
type: integer
RateLimit-Reset:
description: Seconds until the window resets
schema:
type: integer
content:
application/json:
schema:
$ref: '#/components/schemas/NlwebAnswer'
text/event-stream:
schema:
type: string
description: 'event: start, then one event: result per item ({index, item}), then event: complete; event: error before complete on failure'
'400':
description: Missing or empty query, or a malformed body
headers:
RateLimit-Policy:
description: 'Quota policy, IETF RateLimit structured field: "public-read";q=120;w=60'
schema:
type: string
RateLimit:
description: 'Remaining quota and seconds until reset: "public-read";r=57;t=41'
schema:
type: string
RateLimit-Limit:
description: Compatibility form of the quota
schema:
type: integer
RateLimit-Remaining:
description: Compatibility form of the remaining quota
schema:
type: integer
RateLimit-Reset:
description: Seconds until the window resets
schema:
type: integer
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
'405':
description: Method not allowed
headers:
RateLimit-Policy:
description: 'Quota policy, IETF RateLimit structured field: "public-read";q=120;w=60'
schema:
type: string
RateLimit:
description: 'Remaining quota and seconds until reset: "public-read";r=57;t=41'
schema:
type: string
RateLimit-Limit:
description: Compatibility form of the quota
schema:
type: integer
RateLimit-Remaining:
description: Compatibility form of the remaining quota
schema:
type: integer
RateLimit-Reset:
description: Seconds until the window resets
schema:
type: integer
Allow:
schema:
type: string
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
'429':
description: Rate limit exceeded (120 requests per minute per client IP)
headers:
RateLimit-Policy:
description: 'Quota policy, IETF RateLimit structured field: "public-read";q=120;w=60'
schema:
type: string
RateLimit:
description: 'Remaining quota and seconds until reset: "public-read";r=57;t=41'
schema:
type: string
RateLimit-Limit:
description: Compatibility form of the quota
schema:
type: integer
RateLimit-Remaining:
description: Compatibility form of the remaining quota
schema:
type: integer
RateLimit-Reset:
description: Seconds until the window resets
schema:
type: integer
Retry-After:
schema:
type: integer
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
default:
description: Any other error, as an RFC 9457 problem document
headers:
RateLimit-Policy:
description: 'Quota policy, IETF RateLimit structured field: "public-read";q=120;w=60'
schema:
type: string
RateLimit:
description: 'Remaining quota and seconds until reset: "public-read";r=57;t=41'
schema:
type: string
RateLimit-Limit:
description: Compatibility form of the quota
schema:
type: integer
RateLimit-Remaining:
description: Compatibility form of the remaining quota
schema:
type: integer
RateLimit-Reset:
description: Seconds until the window resets
schema:
type: integer
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
components:
schemas:
Problem:
type: object
description: RFC 9457 problem details. Served as application/problem+json on every 4xx/5xx from /api, /mcp and the discovery documents, and on page URLs when the client accepts only JSON.
required:
- type
- title
- status
- detail
- instance
- code
- resolution
properties:
type:
description: URI identifying the problem type; resolves to the matching entry in /developers/#errors
type: string
format: uri
title:
type: string
status:
type: integer
minimum: 400
maximum: 599
detail:
type: string
instance:
description: The request URL
type: string
format: uri
code:
description: Stable machine-readable code
type: string
enum:
- not_found
- api_route_not_found
- photo_not_found
- method_not_allowed
- invalid_parameter
- not_acceptable
- unsupported_media_type
- payload_too_large
- rate_limit_exceeded
- service_unavailable
resolution:
description: What to do instead
type: string
documentation_url:
type: string
format: uri
additionalProperties: true
NlwebAnswer:
type: object
required:
- _meta
description: 'NLWeb 0.55 non-streaming response: an answer with results, or a failure (HTTP 200) with an error object.'
properties:
_meta:
type: object
required:
- response_type
- version
properties:
response_type:
type: string
enum:
- answer
- failure
version:
type: string
const: '0.55'
response_format:
type: string
enum:
- conversational_search
site:
type: string
mode:
type: string
processing_time_ms:
type: integer
minimum: 0
query_id:
type: string
results:
type: array
items:
type: object
required:
- '@type'
- name
- url
properties:
'@type':
type: string
const: Photograph
'@context':
type: string
name:
type: string
url:
type: string
format: uri
description:
type: string
image:
type: string
format: uri
thumbnailUrl:
type: string
format: uri
datePublished:
type: string
format: date
creator:
type: object
properties:
'@type':
type: string
name:
type: string
contentLocation:
type: object
properties:
'@type':
type: string
name:
type: string
keywords:
type: string
site:
type: string
score:
description: 100 for the best match, descending by rank
type: integer
minimum: 1
maximum: 100
schema_object:
type: object
error:
type: object
required:
- code
- message
description: Present when _meta.response_type is failure
properties:
code:
type: string
enum:
- NO_RESULTS
- INVALID_QUERY
- INTERNAL_ERROR
message:
type: string
NlwebRequest:
type: object
required:
- query
description: NLWeb 0.55 structured body; the flat legacy fields (query as a string, site, prev, streaming, mode, max_results) are accepted as well.
properties:
query:
oneOf:
- type: string
maxLength: 200
- type: object
required:
- text
properties:
text:
type: string
maxLength: 200
site:
type: string
context:
type: object
properties:
prev:
type: array
items:
type: string
prefer:
type: object
properties:
streaming:
type: boolean
default: false
mode:
type: string
enum:
- list
default: list
response_format:
type: string
enum:
- conversational_search
meta:
type: object
properties:
version:
type: string
const: '0.55'
site:
type: string
prev:
type: array
items:
type: string
streaming:
type: boolean
mode:
type: string
enum:
- list
max_results:
type: integer
minimum: 1
maximum: 50
default: 10
externalDocs:
description: Developer documentation
url: https://jenriks.com/developers/
x-api-lifecycle:
current_major_version: v1
deprecation_policy_url: https://jenriks.com/developers/#versioning
sunset_notice_days: 90
x-deprecation-policy:
policy_url: https://jenriks.com/developers/#versioning
current_major_version: v1
minimum_notice_days: 90
signals:
- 'Deprecation (RFC 9745, structured Date: @1735689600)'
- Sunset (RFC 8594, HTTP-date)
- Link rel="deprecation"
openapi_marker: 'operations, parameters and schemas being retired carry deprecated: true'
deprecated_versions: []
x-rate-limit-policy:
name: public-read
quota: 120
window_seconds: 60
partition: client IP address
scope:
- /api
- /mcp
- /ask