Read-only endpoints for mobile and web clients. No authentication required.
/api/site-settingsSite-wide configuration for clients. Returns the list of categories (with per-language translations), the configured languages, and the default language. Sensitive settings (API keys, model names) are never exposed.
Example response
{
"categories": [
{
"id": 1,
"type": "news",
"name": "Events",
"slug": "events",
"translations": {
"en": { "name": "Events" },
"de": { "name": "Veranstaltungen" }
}
},
{
"id": 2,
"type": "info",
"name": "Services",
"slug": "services",
"translations": {
"en": { "name": "Services" },
"de": { "name": "Dienstleistungen" }
}
}
],
"languages": ["hr", "en", "de"],
"default_language": "hr"
}/api/newsPaginated list of published news items, joined with their category and camp site. Each item includes any associated files with ready-to-use URLs, the array of scheduled notification datetimes (notify_datetimes), an is_special_offer boolean (only ever true for Gastro-category entries), an is_published flag, and a sort_number integer used by the manual ordering rule. Visibility filter: only rows with is_published=true AND publish_datetime <= NOW() (or null) AND unpublish_datetime >= NOW() (or null) are returned. Ordering: rows with sort_number > 0 come first, ascending by sort_number; remaining rows fall back to publish_datetime descending. Supports multi-language: translatable fields (title, content, links, notification_text) are returned in the requested language when available. If no language is supplied the site default language is used. Requesting a language not present in /api/site-settings returns 400.
Query parameters
| Name | Type | Description |
|---|---|---|
| category | string | Category slug (e.g. events, announcements). |
| camp_site_id | integer | Filter by camp site id. |
| special_offer | '1' | When set to 1, returns only special-offer entries (alias for /api/news/special-offers). |
| page | integer | 1-indexed page number. Defaults to 1. Page size is 20. |
| language | string | Language code (e.g. 'en', 'de'). Must be one of the configured languages. Defaults to the site default language. |
Example response
{
"data": [
{
"id": 12,
"category_id": 3,
"camp_site_id": 1,
"cover_image_id": 42,
"title": "Pool party this Saturday",
"content": "Join us at the main pool...",
"links": null,
"publish_datetime": "2026-04-20T10:00:00.000Z",
"unpublish_datetime": null,
"notify_datetimes": [
"2026-04-20T09:00:00.000Z",
"2026-04-20T17:00:00.000Z"
],
"notification_text": "Pool party starts in 1 hour",
"is_special_offer": false,
"is_published": true,
"sort_number": 0,
"created_at": "2026-04-19T09:11:00.000Z",
"updated_at": "2026-04-19T09:11:00.000Z",
"category_name": "Events",
"category_slug": "events",
"camp_site_name": "Zaton",
"files": [
{
"id": 42,
"url": "/api/files/42",
"thumb_url": "/api/files/42?thumb=1"
}
],
"language": "en"
}
],
"language": "en",
"pagination": {
"page": 1,
"pageSize": 20,
"total": 1,
"totalPages": 1
}
}/api/news/special-offersPaginated list of news items flagged as special offers. Same response shape as /api/news (including is_published and sort_number) and the same visibility + ordering rules: only published rows currently inside their publish/unpublish window are returned, with sort_number > 0 entries appearing first. The special-offer flag is admin-controlled and only available on the Gastro category, so this endpoint effectively returns the gastro promotions feed.
Query parameters
| Name | Type | Description |
|---|---|---|
| camp_site_id | integer | Filter by camp site id. |
| page | integer | 1-indexed page number. Defaults to 1. Page size is 20. |
| language | string | Language code (e.g. 'en', 'de'). Must be one of the configured languages. Defaults to the site default language. |
Example response
{
"data": [
{
"id": 84,
"category_id": 36,
"camp_site_id": 9,
"cover_image_id": 117,
"title": "Pizza & wine — 20% off all week",
"content": "Visit La Tavola from Mon-Fri after 6pm...",
"links": null,
"publish_datetime": "2026-05-01T00:00:00.000Z",
"unpublish_datetime": "2026-05-08T22:00:00.000Z",
"notify_datetimes": ["2026-05-01T08:00:00.000Z"],
"notification_text": "Pizza & wine special starts today!",
"is_special_offer": true,
"is_published": true,
"sort_number": 1,
"created_at": "2026-04-28T11:24:00.000Z",
"updated_at": "2026-04-28T11:24:00.000Z",
"category_name": "Gastro",
"category_slug": "gastro",
"camp_site_name": "Baldarin",
"files": [
{ "id": 117, "url": "/api/files/117", "thumb_url": "/api/files/117?thumb=1" }
],
"language": "en"
}
],
"language": "en",
"pagination": { "page": 1, "pageSize": 20, "total": 1, "totalPages": 1 }
}/api/notificationsPaginated list of news items whose scheduled notifications have already fired (i.e. at least one entry in notify_datetimes is in the past). Each row returns the news title, the notification_text payload, the category id and slug (so the client can route a tap straight to the news detail page), the camp site id and name, and last_notification_at — the most recent notify_datetime that is <= now. Items with no past notifications are excluded. Sorted by last_notification_at descending so the most recently fired notifications come first. Translatable fields (title, notification_text) are returned in the requested language when available.
Query parameters
| Name | Type | Description |
|---|---|---|
| camp_site_id | integer | Filter by camp site id. |
| page | integer | 1-indexed page number. Defaults to 1. Page size is 20. |
| language | string | Language code (e.g. 'en', 'de'). Must be one of the configured languages. Defaults to the site default language. |
Example response
{
"data": [
{
"id": 12,
"title": "Pool party this Saturday",
"notification_text": "Pool party starts in 1 hour",
"category_id": 3,
"category_slug": "events",
"camp_site_id": 1,
"camp_site_name": "Zaton",
"last_notification_at": "2026-04-20T17:00:00.000Z",
"language": "en"
}
],
"language": "en",
"pagination": {
"page": 1,
"pageSize": 20,
"total": 1,
"totalPages": 1
}
}/api/text-infoPaginated list of static text information (services, rules, FAQ, etc.), joined with their category and camp site. Supports multi-language: translatable fields (title, content, links) are returned in the requested language when available. If no language is supplied the site default language is used. Requesting a language not present in /api/site-settings returns 400.
Query parameters
| Name | Type | Description |
|---|---|---|
| category | string | Category slug. |
| camp_site_id | integer | Filter by camp site id. |
| page | integer | 1-indexed page number. Defaults to 1. Page size is 20. |
| language | string | Language code (e.g. 'en', 'de'). Must be one of the configured languages. Defaults to the site default language. |
Example response
{
"data": [
{
"id": 5,
"category_id": 2,
"camp_site_id": 1,
"title": "Wi-Fi access",
"content": "Connect to 'Jadranka-Guest'...",
"links": null,
"created_at": "2026-04-10T12:00:00.000Z",
"updated_at": "2026-04-10T12:00:00.000Z",
"category_name": "Services",
"category_slug": "services",
"camp_site_name": "Zaton",
"language": "en"
}
],
"language": "en",
"pagination": {
"page": 1,
"pageSize": 20,
"total": 1,
"totalPages": 1
}
}/api/files/:idServes a file (image, etc.) by its id. The response is the raw file body with an immutable cache header.
Query parameters
| Name | Type | Description |
|---|---|---|
| id | integer | required File id (path parameter). |
| thumb | '1' | When set to 1, returns the thumbnail variant if available. |
Example response
HTTP/1.1 200 OK Content-Type: image/jpeg Cache-Control: public, max-age=31536000, immutable <binary file contents>
/api/camp-homeReturns the per-camp-site home page layout configured in admin. The response is an ordered list of visual blocks the mobile app renders top-to-bottom on the camp's home screen, plus the camp site's display color (used for the title bar). Block types are: static_picture, static_text (Markdown), map, image_overlay (image with title + body overlay), social_links (instagram / facebook / web / email URLs in config), big_title (FAQ-style row with background_color in config). Each block carries an optional link object — `type` is one of: `none`, `news_item`, `text_info_item`, `news_category`, `text_info_category`, or `special_offer_list`. For *_item the target_id points at a news/text_info row; for *_category category_id and category_slug identify the section page; for special_offer_list no extra fields are needed — clients should open the gastro special-offers feed (the data behind /api/news/special-offers). Translatable fields (text_title, text_body) are returned in the requested language when available.
Query parameters
| Name | Type | Description |
|---|---|---|
| camp_site_id | integer | required Camp site id (matches /api/site-settings camp_sites entries). |
| language | string | Language code. Must be one of the configured languages. Defaults to the site default language. |
Example response
{
"camp_site": { "id": 9, "name": "Baldarin", "color": "#003865" },
"language": "hr",
"blocks": [
{
"id": 28,
"block_type": "static_picture",
"sort_order": 0,
"text_title": null,
"text_body": null,
"image_url": "/api/uploads/1777641099257-9708c8aef3536271.png",
"thumb_url": "/api/uploads/thumbs/1777641099257-9708c8aef3536271.png",
"config": {},
"link": {
"type": "none",
"target_id": null,
"category_id": null,
"category_slug": null
}
},
{
"id": 29,
"block_type": "image_overlay",
"sort_order": 1,
"text_title": "Camp News",
"text_body": "Latest updates from the camp.",
"image_url": "/api/uploads/1777641099258-news.jpg",
"thumb_url": "/api/uploads/thumbs/1777641099258-news.jpg",
"config": {},
"link": {
"type": "news_category",
"target_id": null,
"category_id": 29,
"category_slug": "camp-news"
}
},
{
"id": 30,
"block_type": "image_overlay",
"sort_order": 2,
"text_title": "Posebna ponuda",
"text_body": "Sezonske pogodnosti i posebne pakete.",
"image_url": "/api/uploads/1777641099260-special.jpg",
"thumb_url": "/api/uploads/thumbs/1777641099260-special.jpg",
"config": {},
"link": {
"type": "special_offer_list",
"target_id": null,
"category_id": null,
"category_slug": null
}
},
{
"id": 31,
"block_type": "big_title",
"sort_order": 3,
"text_title": "Pitanje?",
"text_body": "Kontaktirajte nas",
"image_url": null,
"thumb_url": null,
"config": { "background_color": "#00A3E0" },
"link": {
"type": "text_info_category",
"target_id": null,
"category_id": 41,
"category_slug": "kontakti"
}
},
{
"id": 32,
"block_type": "social_links",
"sort_order": 4,
"text_title": null,
"text_body": null,
"image_url": null,
"thumb_url": null,
"config": {
"instagram": "https://instagram.com/jadrankacamps",
"facebook": "https://facebook.com/jadrankacamps",
"web": "https://jadranka.example",
"email": "[email protected]"
},
"link": {
"type": "none",
"target_id": null,
"category_id": null,
"category_slug": null
}
}
]
}/api/uploads/*Serves a file from the upload directory by its relative path. Used by camp-home blocks (image_url / thumb_url returned by /api/camp-home point here). Always uses forward slashes — thumbs live under /api/uploads/thumbs/<filename>. Returned with a one-hour cache header.
Query parameters
| Name | Type | Description |
|---|---|---|
| * | string | required Path to the file relative to the upload directory (e.g. 'foo.jpg' or 'thumbs/foo.jpg'). Path traversal outside the upload root returns 403. |
Example response
HTTP/1.1 200 OK Content-Type: image/jpeg Cache-Control: public, max-age=3600 <binary file contents>
/api/devices/registerIdempotent registration of a mobile device's FCM token. The mobile app calls this on first launch and again whenever the user changes camp site, language, or the push-notifications toggle in Settings. The backend upserts on `token`: re-registering the same token refreshes camp_site_id, language, and last_seen_at. The `push_enabled` field is preserved across re-registrations when omitted from the body — only an explicit boolean overwrites the stored opt-in. Devices with push_enabled=false are excluded from broadcast pushes by the dispatcher.
Request body (application/json)
| Name | Type | Description |
|---|---|---|
| token | string | required FCM registration token from Firebase Messaging. |
| platform | 'android'|'ios'|'web' | required Platform that issued the token. |
| camp_site_id | integer | null | User-selected camp site. Must reference an existing camp_sites row. Null clears the assignment. |
| language | string | null | User-selected language code. Must be one of the configured languages from /api/site-settings. |
| push_enabled | boolean | Push-notifications opt-in. Defaults to true on first registration. Omit to preserve the existing value when re-registering for an audience refresh. |
Example request body
{
"token": "fEX4...long-FCM-token...",
"platform": "android",
"camp_site_id": 1,
"language": "hr",
"push_enabled": true
}Example response
{
"id": 17,
"token": "fEX4...long-FCM-token...",
"platform": "android",
"camp_site_id": 1,
"language": "hr",
"push_enabled": true
}/api/weatherCached weather lookup proxying Open-Meteo. Responses are cached in process memory keyed by (today's calendar date, lat, lng): once a location is fetched on a given day, every subsequent request that day is served instantly from memory. The response includes a `cache` field ("hit" | "miss") and `fetched_at` so clients can tell when the upstream call actually happened. The inner `weather` object is the raw Open-Meteo payload — see the Open-Meteo card below for unit notes and UI mappings.
Query parameters
| Name | Type | Description |
|---|---|---|
| camp_site_id | integer | Camp site id; uses the camp's preset coordinates. One of camp_site_id OR (lat & lng) is required. |
| lat | number | Latitude (decimal degrees). Required if camp_site_id is not given. |
| lng | number | Longitude (decimal degrees). Required if camp_site_id is not given. |
Example response
{
"camp_site_id": 9,
"cache": "miss",
"fetched_at": "2026-05-01T13:42:11.000Z",
"weather": {
"latitude": 44.6939,
"longitude": 14.4728,
"timezone": "Europe/Zagreb",
"current": {
"time": "2026-05-01T15:00",
"temperature_2m": 18.4,
"weather_code": 2,
"relative_humidity_2m": 62,
"wind_speed_10m": 7.3,
"uv_index": 4.1,
"visibility": 24140
},
"daily": {
"time": ["2026-05-01","2026-05-02","2026-05-03","2026-05-04","2026-05-05","2026-05-06","2026-05-07"],
"weather_code": [2, 1, 0, 3, 61, 2, 1],
"temperature_2m_max": [20.1, 21.5, 22.0, 19.8, 17.5, 19.2, 20.6],
"temperature_2m_min": [12.3, 13.1, 14.0, 12.9, 11.5, 12.8, 13.5]
}
}
}api.open-meteo.com/v1/forecastPublic, key-less weather forecast used by the mobile app. Replace {LAT} and {LON} with the target camp site coordinates. The request is configured with timezone=auto, wind_speed_unit=mph, and precipitation_unit=inch.
Request URL
https://api.open-meteo.com/v1/forecast?latitude={LAT}&longitude={LON}¤t=temperature_2m,weather_code,relative_humidity_2m,wind_speed_10m,uv_index,visibility&daily=weather_code,temperature_2m_max,temperature_2m_min&wind_speed_unit=mph&precipitation_unit=inch&timezone=auto&forecast_days=7UI → JSON mapping
| UI element | JSON key | Unit | Notes |
|---|---|---|---|
| Current Temperature | current.temperature_2m | °C | Air temperature at 2 m above ground. |
| Weather Icon / Condition | current.weather_code | WMO code | Map WMO codes (0–99) to your icon set. |
| Humidity | current.relative_humidity_2m | % | Relative humidity at 2 m. |
| Wind Speed | current.wind_speed_10m | mph | Wind speed at 10 m (unit forced via wind_speed_unit=mph). |
| UV Index | current.uv_index | — | Current UV index. |
| Visibility | current.visibility | m | Divide by 1609.34 for miles or by 1000 for km. |
| Today Max | daily.temperature_2m_max[0] | °C | First element of daily array = today. |
| Today Min | daily.temperature_2m_min[0] | °C | First element of daily array = today. |
| 7-Day Date | daily.time[i] | ISO date | Index 0..6 for the 7-day strip. |
| 7-Day Weather Icon | daily.weather_code[i] | WMO code | Same code mapping as current. |
| 7-Day High | daily.temperature_2m_max[i] | °C | High temperature for day i. |
| 7-Day Low | daily.temperature_2m_min[i] | °C | Low temperature for day i. |
Example request & response
GET https://api.open-meteo.com/v1/forecast?latitude=44.5353&longitude=14.4442¤t=temperature_2m,weather_code,relative_humidity_2m,wind_speed_10m,uv_index,visibility&daily=weather_code,temperature_2m_max,temperature_2m_min&wind_speed_unit=mph&precipitation_unit=inch&timezone=auto&forecast_days=7
{
"latitude": 44.5353,
"longitude": 14.4442,
"timezone": "Europe/Zagreb",
"current_units": {
"temperature_2m": "°C",
"relative_humidity_2m": "%",
"wind_speed_10m": "mp/h",
"uv_index": "",
"visibility": "m"
},
"current": {
"time": "2026-04-19T14:00",
"temperature_2m": 18.4,
"weather_code": 2,
"relative_humidity_2m": 62,
"wind_speed_10m": 7.3,
"uv_index": 4.1,
"visibility": 24140
},
"daily_units": {
"temperature_2m_max": "°C",
"temperature_2m_min": "°C"
},
"daily": {
"time": ["2026-04-19","2026-04-20","2026-04-21","2026-04-22","2026-04-23","2026-04-24","2026-04-25"],
"weather_code": [2, 1, 0, 3, 61, 2, 1],
"temperature_2m_max": [20.1, 21.5, 22.0, 19.8, 17.5, 19.2, 20.6],
"temperature_2m_min": [12.3, 13.1, 14.0, 12.9, 11.5, 12.8, 13.5]
}
}