> ## Documentation Index
> Fetch the complete documentation index at: https://docs.flynet.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Restaurant explorer

> Brands, locations, and open hours for list or map UIs.

# Restaurant explorer

**Goal:** Power a discovery list or map screen from three read calls: brand → locations → hours.\
**Prep time:** \~10 minutes

## Off the shelf

The fastest path is `FlynetDiscoveryClient` from `@flynetdev/core` for the reads,
plus the discovery components from `@flynetdev/react` for the UI:
[`NearbyLocations`](/components), [`LocationCard`](/components), and
[`OpenHoursBadge`](/components). Discovery components are presentational — fetch
on the server with your API key and hand the result down as props, so the key
never reaches the browser.

```tsx app/explore.tsx theme={null}
import { FlynetDiscoveryClient, type models } from "@flynetdev/core";
import { NearbyLocations, OpenHoursBadge } from "@flynetdev/react";

export default async function Explore() {
  const discovery = new FlynetDiscoveryClient({ apiKey: process.env.API_KEY! });

  const { restaurants } = await discovery.restaurants.listRestaurants({ pageSize: 50 });

  // Find a brand that actually has at least one location.
  let locations: models.Location[] = [];
  for (const r of restaurants) {
    const res = await discovery.restaurants.listRestaurantLocations({ id: r.id, pageSize: 50 });
    if (res.locations.length > 0) {
      locations = res.locations;
      break;
    }
  }

  const openHours = locations[0]
    ? (await discovery.locations.listLocationOpenHours({ id: locations[0].id })).openHours
    : [];

  return (
    <>
      <NearbyLocations locations={locations} />
      {locations[0] && <OpenHoursBadge openHours={openHours} timeZone={locations[0].timeZone} />}
    </>
  );
}
```

`NearbyLocations` sorts venues by distance and renders each as a `LocationCard`;
`OpenHoursBadge` computes "open now" from the weekly hours and the location's
IANA time zone. The same client-side filtering caveat below still applies — the
components don't change what the server returns.

## From scratch

To see the three reads underneath, or work without React, call them directly.
All three are Discovery routes: they take the API key, not an OAuth bearer.

## What you'll use

* `GET /flynet/v1/restaurants` with `X-API-Key`
* `GET /flynet/v1/restaurants/{id}/locations` with `X-API-Key`
* `GET /flynet/v1/locations/{id}/open_hours` with `X-API-Key`

## Code

```typescript theme={null}
const base = "https://api.staging.blackbird.xyz/flynet/v1";
const h = { "X-API-Key": process.env.API_KEY! };

// 1. Brand-level list for your discovery surface
const restaurants = await (
  await fetch(`${base}/restaurants?page=0&page_size=50`, { headers: h })
).json();

// 2. Find a brand that actually has at least one location.
//    Some restaurants are brand entries without physical venues, with empty `locations`.
let firstLocationId: string | undefined;
for (const r of restaurants.restaurants) {
  const locs = await (
    await fetch(`${base}/restaurants/${r.id}/locations?page=0&page_size=50`, { headers: h })
  ).json();
  if (locs.locations.length > 0) {
    firstLocationId = locs.locations[0].id;
    break;
  }
}
if (!firstLocationId) throw new Error("No restaurant with locations in the first page");

// 3. Weekly hours for that location
const hours = await (
  await fetch(`${base}/locations/${firstLocationId}/open_hours`, { headers: h })
).json();

console.log(hours.open_hours);
```

```bash theme={null}
curl -sS "https://api.staging.blackbird.xyz/flynet/v1/restaurants?page=0&page_size=50" \
  -H "X-API-Key: $API_KEY"
```

<Warning>
  **Chef's warning:** Not every restaurant has locations. Brand entries without physical venues return `{ "locations": [], "pagination": {...} }`. If you naively index `locations[0]` you'll hit `undefined` and the chained call to `/locations/{id}/open_hours` will 400. Always check `locations.length` before indexing.
</Warning>

<Note>
  **Tasting note:** `open_hours` is not paginated: at most 7 entries, one per weekday. Missing days mean closed.
</Note>

<Warning>
  **Chef's warning:** "Open now" is computed client-side from `open_hours` + the location's IANA `time_zone`. `GET /locations` filter params (`restaurant`, `neighborhood`, `payments_enabled`, `is_club`) are accepted but not implemented for launch; filter client-side.
</Warning>

<Tip>
  **From the kitchen:** Filter out locations with `coordinate: { latitude: 0.0, longitude: 0.0 }` before placing markers; that value indicates missing geocoding.
</Tip>

<Note>
  **Tasting note:** Location objects also carry reservation-related fields describing whether and how a venue takes bookings. See the [API reference](/api-reference/introduction) for the per-field shapes; there's no standalone reservation resource at launch.
</Note>

**Next:** [Check-in feed](/recipes/mains/check-in-feed): activity overlay for the same venues.
