# Your first match

Create a Roblox matchmaking search with the BirdHouse Luau SDK and handle the matched group in a server script.

Source: https://birdhouse.tech/docs/quickstart/

This guide creates a two-player match from two solo tickets. Run the integration in a server script; each searching party gets its own session.

Not on Roblox? There is no SDK for other engines. A game server or backend you control runs the same search over HTTP: see [Use it from any game server](https://birdhouse.tech/docs/http-api/#use-it-from-any-game-server).

> **Availability.** Matchmaking is in early access. These examples are written for SDK version 0.4.0. To run them you need SDK package access and a service endpoint, both arranged with the team; this page does not confirm that a package is published or a service is hosted. [Apply for early access](https://birdhouse.tech/?product=matchmaking#signup).

## Before you begin

You need the [Roblox SDK installed](https://birdhouse.tech/docs/installation/), HTTP requests enabled in Roblox Studio, and a service base URL supplied for your deployment. The examples use `https://matchmaking.example.com` as a placeholder.

Every client names one environment, such as `dev`. Use an environment that is enabled on your service endpoint, and install the package version listed in your access instructions.

## Create a client

Use one client for an environment. The default heartbeat interval is five seconds.

```luau
local ServerScriptService = game:GetService("ServerScriptService")
local MatchmakingClient = require(ServerScriptService.ServerPackages.Matchmaking)

local client = MatchmakingClient.new({
    baseUrl = "https://matchmaking.example.com",
    environment = "dev",
    heartbeatSeconds = 5,
})

client.Error:Connect(function(problem)
    warn(problem.code, problem.message)
end)
```

The base URL must not include `/api/progress`, a query, or a fragment. The SDK appends the endpoint. There is no token or custom-header option in this version.

## Start a search

Use the player's actual Roblox `UserId` as a string. The following function creates one solo ticket; call it for each player who intentionally joins the queue.

```luau
local function searchForPlayer(player: Player)
    local session = client:CreateSession({
        players = {
            { id = tostring(player.UserId) },
        },
        meta = { mode = "duel" },
        constraints = {
            type = "and",
            constraints = {
                { type = "playerCount", min = 2, max = 2 },
                { type = "fieldEqualsDeep", field = "mode" },
            },
        },
    })

    session.Matched:Connect(function(match)
        print("Match ready", match.id)
        -- Reserve a server and hand off the matched players in your game.
        -- Retain session.Match while retrying any game-level handoff.
    end)

    session.Failed:Connect(function(problem)
        warn("Search ended", problem.code, problem.message)
    end)

    session.StateChanged:Connect(function(state)
        print("Search state", state)
        -- Remove your registry entry on matched, canceled, or failed.
    end)

    return session
end
```

`CreateSession` does not yield. It validates and copies the options immediately, then defers network work so listeners connected right away can receive the outcome. Invalid options throw before a session is registered.

## Receive a match

Start compatible searches for two different players in the same environment. Each ticket supplies one player, and `playerCount` requires two players across the complete group.

When a match is ready, `Matched` receives its ID, environment, and member tickets. Both searches observe the shared match. BirdHouse groups the players; your game decides how to coordinate server allocation and teleportation.

> A single two-player party will not satisfy this example by itself. The matcher combines at least two tickets, even if one party already fills the target size.

## Cancel a search

```luau
session:Cancel()
```

Cancellation is idempotent. An unsent session can finish locally as `canceled`. Once submission has started, it enters `canceling` while the SDK asks the service to cancel.

Keep your `Matched` handler connected until a terminal outcome. Cancellation cannot undo an already published match. See the complete [ticket lifecycle](https://birdhouse.tech/docs/lifecycle/).

## Handle shutdown

```luau
game:BindToClose(function()
    client:Destroy()
end)
```

`Destroy()` immediately abandons active sessions and reports `client_destroyed`. It does not drain or gracefully cancel remote tickets. For ordinary queue exits, call `Cancel()` and wait for the outcome.

## Make it your own

Add a [skill window or role composition](https://birdhouse.tech/docs/constraints/), maintain a party-to-session registry, and handle player departure in your game. Keep development traffic separate with [environments](https://birdhouse.tech/docs/environments/), then review [limits and guarantees](https://birdhouse.tech/docs/limits/) before shipping.
