API para desarrolladores de OpenBookCase

Crea apps con los datos de OpenBookCase. Leer datos públicos es abierto — no se necesita clave. Escribir (añadir/editar entradas, fotos, listas de deseos, …) requiere un token de acceso OAuth 2.0 que actúa en nombre de uno de tus usuarios. Todas las peticiones son JSON de entrada / JSON de salida y viven bajo /api/v1.

URL base en producción: https://openbookcase.de

Cómo funciona

  • Lecturas abiertas: el listado del área del mapa, las entradas individuales, sus fotos y listas de deseos son públicos — llámalos sin token.
  • Escrituras autenticadas: cada cambio se realiza como uno de tus usuarios. Ellos autorizan tu app una vez (OAuth) y tu token crea/edita datos a su nombre.
  • Sin endpoints de cuenta: deliberadamente no hay API para el correo, la contraseña, las notificaciones ni la eliminación de la cuenta.
  • Datos abiertos: el conjunto de datos procede de OpenStreetMap y está licenciado bajo la ODbL — consulta las Condiciones de los datos.

Obtener acceso

Abre tu Perfil → Acceso a la API y envía una solicitud describiendo tu app y cómo usarás los datos. Un administrador la revisa (hay un intercambio integrado de preguntas y respuestas si tienen dudas). Tras la aprobación recibes tus credenciales:

  • Cliente público (apps móviles / de página única): un client_id, sin secreto. Debe usar PKCE.
  • Cliente confidencial (apps con un backend seguro): un client_id y un client_secret que se muestra una sola vez — guárdalo de forma segura.

También registras una o varias URIs de redirección y eliges los ámbitos que necesitas.

Autenticación (OAuth 2.0 Authorization Code)

Dos endpoints: /oauth/authorize (el usuario aprueba tu app) y /oauth/token (intercambias el código resultante por tokens). Los tokens de acceso duran 1 hora; los tokens de actualización duran 1 año (se rotan al usarlos). Los tokens siguen siendo válidos hasta que se revocan.

1 · Clientes públicos (móvil/SPA) — PKCE

Primero crea un verificador + reto (challenge) PKCE:

# code_verifier: 43–128 random chars; code_challenge = base64url(sha256(verifier))
CODE_VERIFIER=$(openssl rand -base64 60 | tr -d '\n=+/' | cut -c1-64)
CODE_CHALLENGE=$(printf '%s' "$CODE_VERIFIER" \
  | openssl dgst -binary -sha256 | openssl base64 | tr '+/' '-_' | tr -d '=')

Envía al usuario a la URL de autorización (ábrela en un navegador / navegador integrado):

https://openbookcase.de/oauth/authorize
  ?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https://yourapp.example/callback
  &scope=bookcases.write%20images.write
  &state=RANDOM_STRING
  &code_challenge=$CODE_CHALLENGE
  &code_challenge_method=S256

Después de que el usuario apruebe, se le redirige a https://yourapp.example/callback?code=AUTH_CODE&state=RANDOM_STRING.

Intercambia el código por tokens (los clientes públicos envían el verificador, sin secreto):

curl -X POST https://openbookcase.de/oauth/token \
  -d grant_type=authorization_code \
  -d client_id=YOUR_CLIENT_ID \
  -d redirect_uri=https://yourapp.example/callback \
  -d code_verifier=$CODE_VERIFIER \
  -d code=AUTH_CODE

Respuesta:

{
  "token_type": "Bearer",
  "expires_in": 3600,
  "access_token": "eyJ0eXAiOiJKV1Qi...",
  "refresh_token": "def502..."
}

2 · Clientes confidenciales (con un backend)

El mismo flujo, pero te autenticas con tu secreto en lugar de un verificador PKCE:

curl -X POST https://openbookcase.de/oauth/token \
  -d grant_type=authorization_code \
  -d client_id=YOUR_CLIENT_ID \
  -d client_secret=YOUR_CLIENT_SECRET \
  -d redirect_uri=https://yourapp.example/callback \
  -d code=AUTH_CODE

3 · Actualizar un token

curl -X POST https://openbookcase.de/oauth/token \
  -d grant_type=refresh_token \
  -d client_id=YOUR_CLIENT_ID \
  -d refresh_token=YOUR_REFRESH_TOKEN
  # confidential clients also add: -d client_secret=YOUR_CLIENT_SECRET

Ámbitos (scopes)

Solicita solo lo que necesites (separados por espacios en el parámetro scope). Las lecturas no necesitan ámbito.

ÁmbitoConcede
bookcases.writeAñadir y editar entradas, mover su posición
bookcases.deleteEliminar una entrada (se requiere un motivo; se archiva una instantánea)
images.writeSubir fotos a una entrada
wishlist.writeAñadir elementos a la lista de deseos de una entrada
watchlist.writeSeguir / dejar de seguir una entrada para el usuario
ratings.writeValorar una entrada (1–5) en nombre del usuario y eliminar su valoración
home.writeEstablecer la posición de inicio del mapa del usuario

Referencia de endpoints

MétodoRutaAuthPropósito
GET/api/v1/bookcasesabiertoListar por área delimitadora (latMin,latMax,lonMin,lonMax, limit≤1000, offset)
GET/api/v1/bookcases/{id}abiertoEntrada individual con todo el detalle
GET/api/v1/bookcases/{id}/imagesabiertoListar las fotos de una entrada
GET/api/v1/bookcases/{id}/wishlistabiertoListar los elementos de la lista de deseos de una entrada
GET/api/v1/bookcases/{id}/caretakersabiertoListar los responsables de una entrada
GET/api/v1/bookcases/{id}/opening-timesabiertoListar los horarios de apertura de una entrada
GET/api/v1/bookcases/{id}/ratingabiertoResumen de valoraciones (recuento + media)
POST/api/v1/bookcasesbookcases.writeCrear una entrada
PATCH/api/v1/bookcases/{id}bookcases.writeActualizar campos (parcial)
POST/api/v1/bookcases/{id}/positionbookcases.writeMover (latitude/longitude)
DELETE/api/v1/bookcases/{id}bookcases.deleteEliminar (se requiere reason en JSON)
POST/api/v1/bookcases/{id}/imagesimages.writeSubir una foto (multipart)
POST/api/v1/bookcases/{id}/wishlistwishlist.writeAñadir un elemento a la lista de deseos
POST/api/v1/bookcases/{id}/watchwatchlist.writeSeguir una entrada
DELETE/api/v1/bookcases/{id}/watchwatchlist.writeDejar de seguir una entrada
POST/api/v1/profile/homehome.writeEstablecer la posición de inicio del usuario
POST/api/v1/bookcases/{id}/caretakersbookcases.writeAñadir un responsable a una entrada
PATCH/api/v1/bookcases/{id}/caretakers/{caretakerId}bookcases.writeActualizar un responsable (parcial)
DELETE/api/v1/bookcases/{id}/caretakers/{caretakerId}bookcases.writeEliminar un responsable
POST/api/v1/bookcases/{id}/opening-timesbookcases.writeAñadir un horario de apertura
PATCH/api/v1/bookcases/{id}/opening-times/{openingTimeId}bookcases.writeActualizar un horario de apertura (parcial)
DELETE/api/v1/bookcases/{id}/opening-times/{openingTimeId}bookcases.writeEliminar un horario de apertura
PUT/api/v1/bookcases/{id}/ratingratings.writeAñadir o actualizar la valoración del usuario (1–5)
DELETE/api/v1/bookcases/{id}/ratingratings.writeEliminar la valoración del usuario

El token Bearer va en la cabecera Authorization. Una escritura sin token devuelve 401; con el ámbito incorrecto, 403.

Usar la API

Leer el área del mapa (abierto)

curl "https://openbookcase.de/api/v1/bookcases?latMin=52.4&latMax=52.6&lonMin=13.3&lonMax=13.5&limit=200"

Leer una entrada (abierto)

curl https://openbookcase.de/api/v1/bookcases/01JZ8M4P3Q0000000000000000

Crear una entrada

curl -X POST https://openbookcase.de/api/v1/bookcases \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "title": "Community shelf at the park",
        "entryType": "bookcase",
        "latitude": 52.5200,
        "longitude": 13.4050
      }'
# → 201 Created, returns the new entry (incl. its id). Titles may not contain URLs.

Actualizar campos (parcial)

curl -X PATCH https://openbookcase.de/api/v1/bookcases/01JZ8M4P3Q... \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"comment": "Refilled weekly", "isMobile": false, "accessibilityLevel": 3}'

Mover una entrada

curl -X POST https://openbookcase.de/api/v1/bookcases/01JZ8M4P3Q.../position \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"latitude": 52.5198, "longitude": 13.4042}'

Subir una foto (multipart)

curl -X POST https://openbookcase.de/api/v1/bookcases/01JZ8M4P3Q.../images \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -F imageFile=@shelf.jpg \
  -F author="Jane Doe" \
  -F altText="Front view of the shelf"
# imageFile + author are required; altText is optional. Max 5 photos per entry.

Eliminar una entrada (motivo obligatorio)

curl -X DELETE https://openbookcase.de/api/v1/bookcases/01JZ8M4P3Q... \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"reason": "Bookcase was removed by the property owner"}'

Seguir · lista de deseos · inicio

# Watch an entry (get notified of changes)
curl -X POST https://openbookcase.de/api/v1/bookcases/01JZ.../watch \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# Add a wishlist item (title required; author/isbn/misc optional)
curl -X POST https://openbookcase.de/api/v1/bookcases/01JZ.../wishlist \
  -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \
  -d '{"title": "The Left Hand of Darkness", "author": "Ursula K. Le Guin"}'

# Set the user's home map location
curl -X POST https://openbookcase.de/api/v1/profile/home \
  -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \
  -d '{"latitude": 52.52, "longitude": 13.405, "zoom": 14}'

Responsables, horarios de apertura y valoraciones

Los responsables y los horarios de apertura forman parte de la entrada, así que usan bookcases.write. Una valoración pertenece al usuario, así que usa ratings.write.

# Añadir un responsable (se requiere un nombre y/o un contacto; la dirección es opcional)
curl -X POST https://openbookcase.de/api/v1/bookcases/01JZ.../caretakers \
  -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \
  -d '{"name": "Local library", "contact": "hello@example.org",
       "address": {"street": "Main St", "houseNumber": "5", "city": "Berlin"}}'
# → 201 Created, devuelve el responsable (incl. su id). PATCH .../caretakers/{id} lo edita,
#   DELETE .../caretakers/{id} lo desvincula (y lo elimina si ya no lo usa ninguna entrada).

# Añadir un horario de apertura (texto libre, O el indicador 24/7 — se requiere uno)
curl -X POST https://openbookcase.de/api/v1/bookcases/01JZ.../opening-times \
  -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \
  -d '{"openTime": "Mo-Fr 08:00-20:00"}'        # o: {"twentyFourSeven": true}
# PATCH/DELETE .../opening-times/{id} lo edita o lo elimina.

# Valorar la entrada en nombre del usuario (1–5; crea o actualiza su única valoración)
curl -X PUT https://openbookcase.de/api/v1/bookcases/01JZ.../rating \
  -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \
  -d '{"value": 5}'
# → devuelve {userValue, count, average, rounded}. DELETE .../rating elimina su valoración.

Límites de uso y errores

  • Lecturas: 600 peticiones / minuto, por IP.
  • Escrituras: 60 peticiones / 10 minutos, por token de acceso.

Superar el límite devuelve 429 Too Many Requests con una cabecera Retry-After (segundos) y X-RateLimit-Limit. Los demás errores son JSON con un mensaje error: 400 petición incorrecta, 401 token ausente/no válido, 403 ámbito incorrecto, 404 no encontrado, 422 validación fallida (con una lista violations).

Condiciones de los datos y atribución

  • Todos los datos de este sitio se publican bajo la Open Database License (ODbL).
  • Si usas estos datos en otro proyecto, ese proyecto también debe publicarse bajo la ODbL e incluir un enlace al texto de la licencia.
  • OpenBookCase contiene datos importados de OpenStreetMap, por lo que cualquier proyecto que use estos datos debe atribuir a OpenStreetMap y sus colaboradores — el enlace a la página de derechos de autor de OpenStreetMap es obligatorio.
  • Estos datos no deben volver a importarse a OpenStreetMap. A veces las entradas se añaden de memoria en lugar de en coordenadas exactas y verificadas, lo que las hace inservibles para OpenStreetMap.