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_idy unclient_secretque 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.
| Ámbito | Concede |
|---|---|
| bookcases.write | Añadir y editar entradas, mover su posición |
| bookcases.delete | Eliminar una entrada (se requiere un motivo; se archiva una instantánea) |
| images.write | Subir fotos a una entrada |
| wishlist.write | Añadir elementos a la lista de deseos de una entrada |
| watchlist.write | Seguir / dejar de seguir una entrada para el usuario |
| ratings.write | Valorar una entrada (1–5) en nombre del usuario y eliminar su valoración |
| home.write | Establecer la posición de inicio del mapa del usuario |
Referencia de endpoints
| Método | Ruta | Auth | Propósito |
|---|---|---|---|
| GET | /api/v1/bookcases | abierto | Listar por área delimitadora (latMin,latMax,lonMin,lonMax, limit≤1000, offset) |
| GET | /api/v1/bookcases/{id} | abierto | Entrada individual con todo el detalle |
| GET | /api/v1/bookcases/{id}/images | abierto | Listar las fotos de una entrada |
| GET | /api/v1/bookcases/{id}/wishlist | abierto | Listar los elementos de la lista de deseos de una entrada |
| GET | /api/v1/bookcases/{id}/caretakers | abierto | Listar los responsables de una entrada |
| GET | /api/v1/bookcases/{id}/opening-times | abierto | Listar los horarios de apertura de una entrada |
| GET | /api/v1/bookcases/{id}/rating | abierto | Resumen de valoraciones (recuento + media) |
| POST | /api/v1/bookcases | bookcases.write | Crear una entrada |
| PATCH | /api/v1/bookcases/{id} | bookcases.write | Actualizar campos (parcial) |
| POST | /api/v1/bookcases/{id}/position | bookcases.write | Mover (latitude/longitude) |
| DELETE | /api/v1/bookcases/{id} | bookcases.delete | Eliminar (se requiere reason en JSON) |
| POST | /api/v1/bookcases/{id}/images | images.write | Subir una foto (multipart) |
| POST | /api/v1/bookcases/{id}/wishlist | wishlist.write | Añadir un elemento a la lista de deseos |
| POST | /api/v1/bookcases/{id}/watch | watchlist.write | Seguir una entrada |
| DELETE | /api/v1/bookcases/{id}/watch | watchlist.write | Dejar de seguir una entrada |
| POST | /api/v1/profile/home | home.write | Establecer la posición de inicio del usuario |
| POST | /api/v1/bookcases/{id}/caretakers | bookcases.write | Añadir un responsable a una entrada |
| PATCH | /api/v1/bookcases/{id}/caretakers/{caretakerId} | bookcases.write | Actualizar un responsable (parcial) |
| DELETE | /api/v1/bookcases/{id}/caretakers/{caretakerId} | bookcases.write | Eliminar un responsable |
| POST | /api/v1/bookcases/{id}/opening-times | bookcases.write | Añadir un horario de apertura |
| PATCH | /api/v1/bookcases/{id}/opening-times/{openingTimeId} | bookcases.write | Actualizar un horario de apertura (parcial) |
| DELETE | /api/v1/bookcases/{id}/opening-times/{openingTimeId} | bookcases.write | Eliminar un horario de apertura |
| PUT | /api/v1/bookcases/{id}/rating | ratings.write | Añadir o actualizar la valoración del usuario (1–5) |
| DELETE | /api/v1/bookcases/{id}/rating | ratings.write | Eliminar 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.