JENRIKS NL Web API

Natural-language search endpoint following the NLWeb protocol (JSON or SSE)

GitHub OpenAPI

Operations 2

GET /ask Natural-language search (NLWeb 0.55, flat query parameters) #
POST /ask Natural-language search (NLWeb 0.55, structured body) #

Work with this as data

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 Specification

jenriks-com-nlweb-api-openapi.yml Raw ↑
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