# Spoteyo - ai.txt # Operational instructions for AI agents interacting with the Spoteyo platform. # For full project context see /llms.txt # Last updated: 2025-07 ## PURPOSE Spoteyo is a platform for discovering local offline events and activity venues. AI agents can search events, search places, retrieve details, and get autocomplete suggestions via a public REST API. ## PRIMARY ACTIONS ### 1. Search events and places - `POST /api/search/unified` - JSON body with filters. Returns mixed results (events + places). - This is the **preferred endpoint** for most queries. ### 2. Search events only - `GET /api/events?searchTerm=...&city=...&page=1&pageSize=20` - Query params. Returns paginated event list. - Use as fallback when unified search is not needed. ### 3. Search places only - `GET /api/places?...` - Query params. Returns paginated place list. - Use as fallback when unified search is not needed. ### 4. Get event details - `GET /api/events/slug/{slug}` - Returns full event data. Slug comes from search results. ### 5. Get place details - `GET /api/places/slug/{slug}` - Returns full place data. Slug comes from search results. ### 6. Get organizer profile - `GET /api/organizers/{slug}` - Returns public organizer info. Slug comes from event/place data. ### 7. Autocomplete / suggestions - `GET /api/search/suggest?term=...&scope=What&limit=10&contentType=0` - Scope: `What` (keyword) or `Where` (location). - ContentType: All=0, EventsOnly=1, PlacesOnly=2. ## API USAGE RULES - Use only public endpoints listed above. No authentication required. - Endpoints requiring JWT (event/place CRUD, checkout, admin) are off-limits unless the user explicitly provides authorization. - Rate limiting is enforced on `/api/search/*` and `/api/contact`. Space your requests. - Always send `Content-Type: application/json` for POST requests. - Max `pageSize` is 50. Default is 24. ## REQUEST STRATEGY ### Default: use unified search For most user queries, use `POST /api/search/unified` with this body structure: ```json { "query": "search text", "contentType": 0, "city": "Warszawa", "latitude": 52.23, "longitude": 21.01, "radiusKm": 25, "dateRange": "ThisWeek", "sort": 0, "page": 1, "pageSize": 24 } ``` All fields are optional. Omit fields you don't need. ### Key parameters | Parameter | Type | Values | |---|---|---| | `contentType` | int | All=0, EventsOnly=1, PlacesOnly=2 | | `dateRange` | string | Any, Today, Tomorrow, ThisWeek, Weekend, NextWeek, ThisMonth, Custom | | `sort` | int | Relevance=0, Date=1, Distance=2, Newest=3, Name=4 | | `kidFriendly` | bool | Filter places for children | | `environment` | string | Indoor, Outdoor, Both | | `priceRange` | string | Free, Budget, Moderate, Expensive | | `tags` | string[] | Filter by tags | | `onlineOnly` | bool | Only online events | ### Fallback chain 1. Try `POST /api/search/unified` with all available filters. 2. If no results → relax filters (remove dateRange, widen radiusKm, remove tags). 3. If no location available → use text-only query (omit lat/lng/radius). 4. For events specifically → fall back to `GET /api/events`. 5. For places specifically → fall back to `GET /api/places`. 6. For details → use `GET /api/events/slug/{slug}` or `GET /api/places/slug/{slug}`. ## RESPONSE INTERPRETATION ### Unified search response ``` { "items": [ { "type": 0, // 0 = Event, 1 = Place "event": { ... }, // present when type=0 "place": { ... }, // present when type=1 "relevanceScore": ..., "distanceKm": ... } ], "stats": { "totalEvents": N, "totalPlaces": N }, "total": N, "page": N, "pageSize": N, "totalPages": N, "hasNextPage": bool, "boundingBox": { ... } } ``` ### Reading an event - `startAt` / `endAt` - DateTimeOffset (ISO 8601). Check if event is in the future. - `status` - Only "Published" events are publicly visible. Ignore others. - `latitude` / `longitude` - May be null for online events. Check `isOnline`. - `price` - May be null (free event) or decimal. Currency depends on market region. - `maxParticipants` - Check if capacity is relevant to the user. - `slug` - Use for detail URL: `/api/events/slug/{slug}`. - `isRecurring` - If true, check `recurrenceTimeSlots` for schedule. - `organizer.slug` - Use for organizer profile lookup. ### Reading a place - `latitude` / `longitude` - Always present (required field). - `tags` - String array. Use for categorization. - `isKidFriendly` - Boolean. - `environment` - "Indoor", "Outdoor", or "Both". - `priceRange` - "Free", "Budget", "Moderate", "Expensive". - `openingHours` - Array. Check before assuming availability. - `slug` - Use for detail URL: `/api/places/slug/{slug}`. ## SAFE USAGE - Never perform write operations (POST/PUT/DELETE to CRUD endpoints) without explicit user consent and a valid JWT. - Never guess or fabricate data that the API did not return. - Never assume a place is open - check `openingHours` if present. - Never assume an event has available spots - check `maxParticipants`. - Never assume all events/places in a region are listed. Coverage varies. - Never assume prices are in a specific currency. Currency depends on market region (PL=PLN, DE=EUR). - Never request or handle user credentials, tokens, or personal data. ## DO / DO NOT ### DO: - Use filters to narrow results (dateRange, city, tags, location radius). - Respect pagination. Fetch next pages if needed. - Return only relevant results to the user. - Use slugs from search results for detail lookups. - Prefer `/api/search/unified` over individual endpoints. - Check `type` field in unified results to distinguish events from places. - Present dates in the user's local timezone when possible. ### DO NOT: - Scrape HTML pages. Always use the API. - Exceed rate limits. Space requests appropriately. - Assume data is complete or up-to-date. - Call authenticated endpoints without authorization. - Invent event/place data not returned by the API. - Expose internal API details to end users. ## INTENT → ACTION QUICK REFERENCE | User says | Action | |---|---| | "events near me" | `POST /api/search/unified` → `contentType=1`, pass user lat/lng + radiusKm | | "workshops this weekend" | `POST /api/search/unified` → `contentType=1, query="workshop", dateRange=Weekend` | | "places for kids" | `POST /api/search/unified` → `contentType=2, kidFriendly=true` | | "what's happening in Kraków" | `POST /api/search/unified` → `city="Kraków"` | | "outdoor places" | `POST /api/search/unified` → `contentType=2, environment="Outdoor"` | | "free events today" | `POST /api/search/unified` → `contentType=1, dateRange=Today` | | "tell me about [event name]" | Search → get slug → `GET /api/events/slug/{slug}` | | "show me [place name]" | Search → get slug → `GET /api/places/slug/{slug}` | | "who organizes this" | Get event/place → read `organizer.slug` → `GET /api/organizers/{slug}` | | "suggest something" | `GET /api/search/suggest?term=...&scope=What` | ## RELATED FILES - `/llms.txt` - Full project description, architecture, data model, and entity details. - `/sitemap.xml` - XML sitemap of all published content. - `/ai-sitemap.xml` - AI-optimized sitemap with content type annotations and structured data hints.