# HTTP API

Create, confirm, and cancel matchmaking tickets from any game server through one batched JSON endpoint.

Source: https://birdhouse.tech/docs/http-api/

The sole matchmaking endpoint is `POST /api/progress`. Send JSON containing 1–100 actions. The response contains one result per action in the same order.

## Use it from any game server

The API is plain HTTP and JSON, so any server runtime can call it: a dedicated server for a Unity, Unreal Engine, or Godot game, a custom game server, a lobby service, or another backend you control. The [Roblox SDK](https://birdhouse.tech/docs/roblox-sdk/) is a client for this same endpoint, and it is the only SDK today.

Access-token authentication is unavailable in this phase. Call the API only from servers you control, never from player devices.

A search from your own server follows four steps:

1. **Create.** Generate a GUID, keep it, and send a `create` action with the environment, the party's players, metadata, and rules. The environment is required on every create.
2. **Confirm on a timer.** Send a `confirm` action for each waiting ticket. A waiting ticket expires five minutes after creation or its last successful confirm; the Roblox SDK confirms every five seconds by default. One request can carry up to 100 actions.
3. **Handle the match.** When a result's `state` is `matched`, read `match.tickets` for the complete group and stop sending actions for that ticket. Every member ticket receives the same match. Server allocation and player handoff are your game's work.
4. **Cancel on exit.** Send `cancel` when the party leaves the queue. If the result is `matched`, the match won the race and your server should handle it.

```sh
curl -X POST https://matchmaking.example.com/api/progress \
  -H "Content-Type: application/json" \
  -d '{"actions":[{"type":"confirm","id":"b5e44619-c4b3-4c1d-a68b-83c48dbdf820"}]}'
```

`matchmaking.example.com` is a placeholder. A service base URL is arranged with the team during [early access](https://birdhouse.tech/?product=matchmaking#signup).

## Request contract

```http
POST /api/progress
Content-Type: application/json
```

```json
{
  "actions": [
    {
      "type": "create",
      "id": "b5e44619-c4b3-4c1d-a68b-83c48dbdf820",
      "ref": "party-42",
      "options": {
        "environment": "dev",
        "players": [{ "id": "123456789" }],
        "meta": { "mode": "duel" },
        "constraints": {
          "type": "and",
          "constraints": [
            { "type": "playerCount", "min": 2, "max": 2 },
            { "type": "fieldEqualsDeep", "field": "mode" }
          ]
        }
      }
    }
  ]
}
```

Use a fresh ID for every new search. The ID above is illustrative; do not reuse it across real requests.

Request validation happens before actions execute. A validation failure rejects the entire request. Once execution begins, a batch is not an all-or-nothing transaction: different ticket IDs can execute concurrently, while actions sharing an ID execute in request order.

## Create an action

| Field | Required | Meaning |
| --- | --- | --- |
| `type` | Yes | `"create"` |
| `id` | No | Client-generated uppercase ULID or hyphenated GUID. Omit for a server-generated ULID. |
| `ref` | No | Optional string echoed for create correlation. It is not an idempotency key. |
| `options.environment` | Yes | A key in the deployment's configured environment allowlist. |
| `options.players` | Yes | Player objects with string `id` and optional object `meta`. IDs have a maximum of 32 UTF-16 code units. |
| `options.constraints` | No | A [constraint tree](https://birdhouse.tech/docs/constraints/). |
| `options.meta` | No | Object containing ticket metadata. |

Use nonempty, unique player IDs and nonempty parties; the Roblox SDK enforces those additional integration checks. There is no SDK for other runtimes; the HTTP contract can be used directly from any server runtime.

If the supplied ID already exists, create returns the existing ticket's state without overwriting it, refreshing its TTL, or recording another creation. That protection lasts only while the ticket exists.

## Confirm a ticket

```json
{
  "actions": [
    { "type": "confirm", "id": "b5e44619-c4b3-4c1d-a68b-83c48dbdf820" }
  ]
}
```

Confirm reads the current state and refreshes a waiting ticket's five-minute TTL. Repeat while waiting. When matched, it returns the match within the fixed ten-minute result-retention window without extending that window.

## Cancel a ticket

```json
{
  "actions": [
    { "type": "cancel", "id": "b5e44619-c4b3-4c1d-a68b-83c48dbdf820" }
  ]
}
```

Cancellation returns `canceling` when accepted, `matched` if publication won the race, or `not_found` when the ticket is absent. Cancellation cannot undo a published match.

## Read the response

```json
{
  "results": [
    {
      "ref": "party-42",
      "id": "b5e44619-c4b3-4c1d-a68b-83c48dbdf820",
      "state": "matching",
      "ticket": {
        "id": "b5e44619-c4b3-4c1d-a68b-83c48dbdf820",
        "environment": "dev",
        "players": [{ "id": "123456789" }],
        "createdAt": 1790640000000,
        "status": 0,
        "meta": { "mode": "duel" }
      },
      "match": null
    }
  ]
}
```

This abbreviated example omits the echoed constraints and optional tracing field.

| Result field | Type | Meaning |
| --- | --- | --- |
| `ref` | `string?` | Correlation value for a create action, when supplied. |
| `id` | `string \| null` | Ticket identifier. |
| `state` | `string` | `matching`, `matched`, `canceling`, or `not_found`. |
| `ticket` | `Ticket \| null` | Current ticket, or null when absent. |
| `match` | `Match \| null` | Shared match when matched, otherwise null. |

Ticket `status` is numeric: `0` means matching, `1` matched, and `2` canceling. `createdAt` is in epoch milliseconds. A match has an `id`, an optional legacy-compatible `environment`, and a `tickets` array.

Stop sending actions after receiving a valid matched result. There is no `ack` action and no per-match acknowledgement tracking.

## Retry safely

Generate and retain the ticket ID before submitting. After a lost or ambiguous create response, reconcile with `confirm` or `cancel` for that ID. Do not blindly create another search.

IDs must never be reused after deletion or expiry: the service keeps no tombstone or permanent deduplication record. Omitting the ID and retrying creation can produce duplicate tickets.

A missing confirmation after uncertain creation does not prove the original request cannot still complete. Keep ticket identity available for investigation and account for potentially outstanding work before starting a replacement.

## Authentication and service URLs

Use your deployment's supplied base URL. This documentation does not specify a public API hostname. Access-token authentication is unavailable in this phase, and the Roblox SDK exposes no custom-header option.

Operational endpoints are `GET /livez`, `GET /readyz`, and `GET /metrics`. They are described in [monitoring](https://birdhouse.tech/docs/monitoring/); they do not create or manage matchmaking tickets.
