fmlocal implements enough of the GameLift JSON 1.1 wire protocol that the standard AWS CLI works against it as long as you pass --endpoint-url. This document walks through a full matchmaking lifecycle using only aws gamelift commands.
-
fmlocal running locally (see Running locally).
-
awsCLI v2 installed. -
A stub set of credentials — fmlocal does not validate them, but the CLI refuses to run without them. The simplest option:
export AWS_ACCESS_KEY_ID=x export AWS_SECRET_ACCESS_KEY=x export AWS_REGION=us-east-1
All examples below assume the endpoint and region are set via flags for clarity. You can also set AWS_ENDPOINT_URL=http://localhost:9080 (CLI v2 ≥ 2.15) to drop the flag.
curl -s http://localhost:9080/healthz
# => okaws gamelift describe-matchmaking-configurations \
--endpoint-url http://localhost:9080 \
--region us-east-1You should see the configurations defined in your config.yaml (default and accept when using deploy/local/config.yaml).
aws gamelift describe-matchmaking-rule-sets \
--endpoint-url http://localhost:9080 \
--region us-east-1Each rule set's RuleSetBody is returned verbatim from the file configured under ruleSets[*].path.
aws gamelift validate-matchmaking-rule-set \
--endpoint-url http://localhost:9080 \
--region us-east-1 \
--rule-set-body "$(cat deploy/local/rulesets/1v1.json)"Returns {"Valid": true} when the body parses; otherwise a InvalidRequestException with a parse error.
The default configuration uses the 1v1 rule set (two teams of one, matched on skill). Post two tickets that should match:
aws gamelift start-matchmaking \
--endpoint-url http://localhost:9080 \
--region us-east-1 \
--configuration-name default \
--ticket-id ticket-alice \
--players '[
{
"PlayerId": "alice",
"PlayerAttributes": { "skill": { "N": 1500 } }
}
]'aws gamelift start-matchmaking \
--endpoint-url http://localhost:9080 \
--region us-east-1 \
--configuration-name default \
--ticket-id ticket-bob \
--players '[
{
"PlayerId": "bob",
"PlayerAttributes": { "skill": { "N": 1520 } }
}
]'Each call returns a MatchmakingTicket in status QUEUED. The ticker advances the matchmaker every tickInterval (500ms in the sample config).
aws gamelift describe-matchmaking \
--endpoint-url http://localhost:9080 \
--region us-east-1 \
--ticket-ids ticket-alice ticket-bobWithin a second or two, both tickets transition through SEARCHING → PLACING → COMPLETED. When acceptanceRequired: true (e.g. the accept configuration), tickets pause in REQUIRES_ACCEPTANCE until every player has accepted.
Using the accept configuration (1v1-accept rule set):
aws gamelift start-matchmaking \
--endpoint-url http://localhost:9080 \
--region us-east-1 \
--configuration-name accept \
--ticket-id accept-alice \
--players '[{"PlayerId":"alice","PlayerAttributes":{"skill":{"N":1500}}}]'
aws gamelift start-matchmaking \
--endpoint-url http://localhost:9080 \
--region us-east-1 \
--configuration-name accept \
--ticket-id accept-bob \
--players '[{"PlayerId":"bob","PlayerAttributes":{"skill":{"N":1520}}}]'Once both tickets move to REQUIRES_ACCEPTANCE:
aws gamelift accept-match \
--endpoint-url http://localhost:9080 \
--region us-east-1 \
--ticket-id accept-alice \
--player-ids alice \
--acceptance-type ACCEPT
aws gamelift accept-match \
--endpoint-url http://localhost:9080 \
--region us-east-1 \
--ticket-id accept-bob \
--player-ids bob \
--acceptance-type ACCEPTAfter both accepts, the tickets advance to PLACING and then COMPLETED. If any player sends --acceptance-type REJECT (or the acceptance window elapses), the proposal fails acceptance: the ticket that rejected or never responded moves to CANCELLED and emits MatchmakingCancelled, while every ticket whose players had all accepted returns to SEARCHING (re-emitting MatchmakingSearching) to be re-matched. A single match-level AcceptMatchCompleted reports the outcome (Rejected or TimedOut). This mirrors AWS — TIMED_OUT and MatchmakingFailed are not used for acceptance failures.
aws gamelift stop-matchmaking \
--endpoint-url http://localhost:9080 \
--region us-east-1 \
--ticket-id ticket-aliceThe ticket transitions to CANCELLED on the next tick; an empty JSON body is returned on success.
When fmlocal is run via docker compose up, lifecycle events are published to the fmlocal-events queue on the paired ElasticMQ container (SQS-compatible, on http://localhost:9324). The aws sqs subcommands work against it unchanged.
Confirm the queue exists:
aws sqs list-queues \
--endpoint-url http://localhost:9324 \
--region us-east-1The output should include http://localhost:9324/000000000000/fmlocal-events.
Check how many events are waiting (useful for spotting a stuck or absent publisher):
aws sqs get-queue-attributes \
--endpoint-url http://localhost:9324 \
--region us-east-1 \
--queue-url http://localhost:9324/000000000000/fmlocal-events \
--attribute-names ApproximateNumberOfMessages ApproximateNumberOfMessagesNotVisiblePull events off the queue. Run a couple of start-matchmaking calls first so something is actually there:
aws sqs receive-message \
--endpoint-url http://localhost:9324 \
--region us-east-1 \
--queue-url http://localhost:9324/000000000000/fmlocal-events \
--max-number-of-messages 10 \
--wait-time-seconds 1Each Body is an EventBridge envelope — see the event catalog and envelope shape in Event publishers. To make a receipt permanent (otherwise the message returns after the visibility timeout), delete it:
aws sqs delete-message \
--endpoint-url http://localhost:9324 \
--region us-east-1 \
--queue-url http://localhost:9324/000000000000/fmlocal-events \
--receipt-handle <handle-from-receive-message>To drain the queue between test runs:
aws sqs purge-queue \
--endpoint-url http://localhost:9324 \
--region us-east-1 \
--queue-url http://localhost:9324/000000000000/fmlocal-eventsElasticMQ also exposes a browser stats view at http://localhost:9325 showing queue names and message counts.
If you configured an sns_http publisher instead, point a local HTTP listener at its url — the envelope arrives inside the outer SNS message's Message field.
fmlocal maps domain errors to the GameLift-documented error codes:
| Scenario | HTTP | Error code |
|---|---|---|
Unknown X-Amz-Target |
400 | UnknownOperationException |
| Missing / malformed input | 400 | InvalidRequestException |
NotFound on ticket/config/rule set |
404 | NotFoundException |
| Invalid state transition | 400 | InvalidRequestException |
| Anything else | 500 | InternalServiceException |
StartMatchBackfill and StopMatchBackfill intentionally return UnsupportedOperation — fmlocal does not implement game-session placement.