JCJadranka Camp

Public API Specification

Read-only endpoints for mobile and web clients. No authentication required.

Base URL:<host>
Endpoints
GET/api/site-settings

Site-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.

{
  "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"
}
GET/api/news

Paginated 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.

NameTypeDescription
categorystringCategory slug (e.g. events, announcements).
camp_site_idintegerFilter by camp site id.
special_offer'1'When set to 1, returns only special-offer entries (alias for /api/news/special-offers).
pageinteger1-indexed page number. Defaults to 1. Page size is 20.
languagestringLanguage code (e.g. 'en', 'de'). Must be one of the configured languages. Defaults to the site default language.
{
  "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
  }
}
GET/api/news/special-offers

Paginated 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.

NameTypeDescription
camp_site_idintegerFilter by camp site id.
pageinteger1-indexed page number. Defaults to 1. Page size is 20.
languagestringLanguage code (e.g. 'en', 'de'). Must be one of the configured languages. Defaults to the site default language.
{
  "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 }
}
GET/api/notifications

Paginated 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.

NameTypeDescription
camp_site_idintegerFilter by camp site id.
pageinteger1-indexed page number. Defaults to 1. Page size is 20.
languagestringLanguage code (e.g. 'en', 'de'). Must be one of the configured languages. Defaults to the site default language.
{
  "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
  }
}
GET/api/text-info

Paginated 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.

NameTypeDescription
categorystringCategory slug.
camp_site_idintegerFilter by camp site id.
pageinteger1-indexed page number. Defaults to 1. Page size is 20.
languagestringLanguage code (e.g. 'en', 'de'). Must be one of the configured languages. Defaults to the site default language.
{
  "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
  }
}
GET/api/files/:id

Serves a file (image, etc.) by its id. The response is the raw file body with an immutable cache header.

NameTypeDescription
idintegerrequired File id (path parameter).
thumb'1'When set to 1, returns the thumbnail variant if available.
HTTP/1.1 200 OK
Content-Type: image/jpeg
Cache-Control: public, max-age=31536000, immutable

<binary file contents>
GET/api/camp-home

Returns 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.

NameTypeDescription
camp_site_idintegerrequired Camp site id (matches /api/site-settings camp_sites entries).
languagestringLanguage code. Must be one of the configured languages. Defaults to the site default language.
{
  "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
      }
    }
  ]
}
GET/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.

NameTypeDescription
*stringrequired 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.
HTTP/1.1 200 OK
Content-Type: image/jpeg
Cache-Control: public, max-age=3600

<binary file contents>
POST/api/devices/register

Idempotent 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.

NameTypeDescription
tokenstringrequired FCM registration token from Firebase Messaging.
platform'android'|'ios'|'web'required Platform that issued the token.
camp_site_idinteger | nullUser-selected camp site. Must reference an existing camp_sites row. Null clears the assignment.
languagestring | nullUser-selected language code. Must be one of the configured languages from /api/site-settings.
push_enabledbooleanPush-notifications opt-in. Defaults to true on first registration. Omit to preserve the existing value when re-registering for an audience refresh.
{
  "token": "fEX4...long-FCM-token...",
  "platform": "android",
  "camp_site_id": 1,
  "language": "hr",
  "push_enabled": true
}
{
  "id": 17,
  "token": "fEX4...long-FCM-token...",
  "platform": "android",
  "camp_site_id": 1,
  "language": "hr",
  "push_enabled": true
}
GET/api/weather

Cached 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.

NameTypeDescription
camp_site_idintegerCamp site id; uses the camp's preset coordinates. One of camp_site_id OR (lat & lng) is required.
latnumberLatitude (decimal degrees). Required if camp_site_id is not given.
lngnumberLongitude (decimal degrees). Required if camp_site_id is not given.
{
  "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]
    }
  }
}
External: Open-Meteo weather
GETapi.open-meteo.com/v1/forecast

Public, 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.

https://api.open-meteo.com/v1/forecast?latitude={LAT}&longitude={LON}&current=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
UI elementJSON keyUnitNotes
Current Temperaturecurrent.temperature_2m°CAir temperature at 2 m above ground.
Weather Icon / Conditioncurrent.weather_codeWMO codeMap WMO codes (0–99) to your icon set.
Humiditycurrent.relative_humidity_2m%Relative humidity at 2 m.
Wind Speedcurrent.wind_speed_10mmphWind speed at 10 m (unit forced via wind_speed_unit=mph).
UV Indexcurrent.uv_indexCurrent UV index.
Visibilitycurrent.visibilitymDivide by 1609.34 for miles or by 1000 for km.
Today Maxdaily.temperature_2m_max[0]°CFirst element of daily array = today.
Today Mindaily.temperature_2m_min[0]°CFirst element of daily array = today.
7-Day Datedaily.time[i]ISO dateIndex 0..6 for the 7-day strip.
7-Day Weather Icondaily.weather_code[i]WMO codeSame code mapping as current.
7-Day Highdaily.temperature_2m_max[i]°CHigh temperature for day i.
7-Day Lowdaily.temperature_2m_min[i]°CLow temperature for day i.
GET https://api.open-meteo.com/v1/forecast?latitude=44.5353&longitude=14.4442&current=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]
  }
}