Submit a match result
const url = 'https://api.krkplaystennis.example/v1/matches';const options = { method: 'POST', headers: { 'Idempotency-Key': '5f0c6a2e-8d47-4b8e-9a51-2b3c7e1d9f04', Authorization: 'Bearer <token>', 'Content-Type': 'application/json' }, body: '{"competition_id":"cmp_6tw2hn8rxe","venue_id":"ven_7hk2p9wq4m","court_number":2,"played_at":"2026-10-10T18:00:00+02:00","player_ids":["plr_2b7xq9mhk4","plr_9fz3tr6wn1"],"status":"completed","score":{"sets":[{"games":[6,4]},{"games":[3,6]}],"match_tiebreak":[10,7]}}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://api.krkplaystennis.example/v1/matches \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --header 'Idempotency-Key: 5f0c6a2e-8d47-4b8e-9a51-2b3c7e1d9f04' \ --data '{ "competition_id": "cmp_6tw2hn8rxe", "venue_id": "ven_7hk2p9wq4m", "court_number": 2, "played_at": "2026-10-10T18:00:00+02:00", "player_ids": [ "plr_2b7xq9mhk4", "plr_9fz3tr6wn1" ], "status": "completed", "score": { "sets": [ { "games": [ 6, 4 ] }, { "games": [ 3, 6 ] } ], "match_tiebreak": [ 10, 7 ] } }'Submits the result of a singles match. Only the organizer of the match’s competition can submit it.
The API rates the match asynchronously. The response has rating_status: pending, and the status changes to rated when both players’ ratings are updated, usually within a few minutes. Walkovers aren’t rated and get rating_status: not_rated.
Send a unique Idempotency-Key with each new result. If a request fails because of a network error, retry it with the same key: the API returns the original response instead of creating a second match.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Header Parameters
Section titled “Header Parameters”A unique value for this result, such as a UUID. The API keeps each key for 24 hours.
Example
5f0c6a2e-8d47-4b8e-9a51-2b3c7e1d9f04Request Bodyrequired
Section titled “Request Bodyrequired”A match played to the end. Send the full score.
object
A competition ID. Starts with cmp_.
A venue ID. Starts with ven_.
The court number at the venue. Optional.
When the match started, with the UTC offset.
The two players. Every score pair uses this order.
The score, set by set. Every pair of numbers is in the same order as player_ids.
Valid set scores depend on the competition’s match_format.games_per_set. When match_format.final_set is match_tiebreak, a match tiebreak replaces the third set. In a retired match, the last set can be unfinished.
object
object
Two non-negative integers, in the same order as player_ids.
Tiebreak points, only for a set that ended 7-6.
Match tiebreak points, only when the competition’s match_format.final_set is match_tiebreak.
A match that ended when a player stopped. Send the score up to that point and the player who retired.
object
A competition ID. Starts with cmp_.
A venue ID. Starts with ven_.
The court number at the venue. Optional.
When the match started, with the UTC offset.
The two players. Every score pair uses this order.
The score, set by set. Every pair of numbers is in the same order as player_ids.
Valid set scores depend on the competition’s match_format.games_per_set. When match_format.final_set is match_tiebreak, a match tiebreak replaces the third set. In a retired match, the last set can be unfinished.
object
object
Two non-negative integers, in the same order as player_ids.
Tiebreak points, only for a set that ended 7-6.
Match tiebreak points, only when the competition’s match_format.final_set is match_tiebreak.
The player who stopped. Must be one of player_ids.
A match that wasn’t played. Send the player who advances. There’s no score.
object
A competition ID. Starts with cmp_.
A venue ID. Starts with ven_.
The court number at the venue. Optional.
When the match started, with the UTC offset.
The two players. Every score pair uses this order.
The player who advances. Must be one of player_ids.
Examples
Completed match decided in a match tiebreak
{ "competition_id": "cmp_6tw2hn8rxe", "venue_id": "ven_7hk2p9wq4m", "court_number": 2, "played_at": "2026-10-10T18:00:00+02:00", "player_ids": [ "plr_2b7xq9mhk4", "plr_9fz3tr6wn1" ], "status": "completed", "score": { "sets": [ { "games": [ 6, 4 ] }, { "games": [ 3, 6 ] } ], "match_tiebreak": [ 10, 7 ] }}Match ended by a retirement
{ "competition_id": "cmp_6tw2hn8rxe", "venue_id": "ven_3xr8tn5cva", "court_number": 1, "played_at": "2026-10-08T19:00:00+02:00", "player_ids": [ "plr_5jd8vk2lc7", "plr_8mq4ws1yd6" ], "status": "retired", "retired_player_id": "plr_8mq4ws1yd6", "score": { "sets": [ { "games": [ 6, 2 ] }, { "games": [ 2, 1 ] } ] }}Walkover
{ "competition_id": "cmp_6tw2hn8rxe", "venue_id": "ven_7hk2p9wq4m", "played_at": "2026-10-05T10:00:00+02:00", "player_ids": [ "plr_9fz3tr6wn1", "plr_5jd8vk2lc7" ], "status": "walkover", "winner_id": "plr_9fz3tr6wn1"}Responses
Section titled “Responses”The match is recorded. Rating happens asynchronously.
object
A match ID. Starts with mat_.
A competition ID. Starts with cmp_.
A venue ID. Starts with ven_.
The court number at the venue. null if the organizer didn’t send it.
When the match started.
The two players. Every score pair uses this order.
How the match ended:
completed: the match was played to the end.retired: a player stopped during the match. The match is rated on the games completed.walkover: a player didn’t show up or withdrew before the match. The match isn’t rated.
The score, set by set. Every pair of numbers is in the same order as player_ids.
Valid set scores depend on the competition’s match_format.games_per_set. When match_format.final_set is match_tiebreak, a match tiebreak replaces the third set. In a retired match, the last set can be unfinished.
object
object
Two non-negative integers, in the same order as player_ids.
Tiebreak points, only for a set that ended 7-6.
Match tiebreak points, only when the competition’s match_format.final_set is match_tiebreak.
A player ID. Starts with plr_.
Whether the match has affected ratings:
pending: the result is recorded, and the rating update hasn’t run yet.rated: both players’ ratings are updated.not_rated: the match doesn’t affect ratings, for example a walkover.
When the result was submitted.
Examples
Completed match waiting to be rated
{ "id": "mat_4nc8xq2vbt", "competition_id": "cmp_6tw2hn8rxe", "venue_id": "ven_7hk2p9wq4m", "court_number": 2, "played_at": "2026-10-10T18:00:00+02:00", "player_ids": [ "plr_2b7xq9mhk4", "plr_9fz3tr6wn1" ], "status": "completed", "score": { "sets": [ { "games": [ 6, 4 ] }, { "games": [ 3, 6 ] } ], "match_tiebreak": [ 10, 7 ] }, "winner_id": "plr_2b7xq9mhk4", "retired_player_id": null, "rating_status": "pending", "created_at": "2026-10-10T20:41:00Z"}Headers
Section titled “Headers”URL of the new match.
Example
https://api.krkplaystennis.example/v1/matches/mat_4nc8xq2vbtThe request is malformed, for example a query parameter has the wrong type.
An error, as RFC 9457 problem details.
object
A URL that identifies the error type and links to its documentation.
A short summary of the error type. It’s the same for every error of this type.
The HTTP status code.
An explanation of this occurrence of the error.
The request path that caused the error.
One item per invalid field or parameter. Only for invalid-request, invalid-score, and validation-failed.
object
A JSON Pointer to the invalid field in the request body, or #/ followed by the name of an invalid query parameter.
What’s wrong with the field and how to fix it.
Examples
Limit out of range
{ "type": "https://saverio-cirillo.com/krk-plays-tennis-api/errors/#invalid-request", "title": "Invalid request", "status": 400, "detail": "The request has 1 invalid parameter.", "instance": "/v1/players", "errors": [ { "pointer": "#/limit", "detail": "limit must be between 1 and 100." } ]}The credentials are missing, invalid, or expired.
An error, as RFC 9457 problem details.
object
A URL that identifies the error type and links to its documentation.
A short summary of the error type. It’s the same for every error of this type.
The HTTP status code.
An explanation of this occurrence of the error.
The request path that caused the error.
One item per invalid field or parameter. Only for invalid-request, invalid-score, and validation-failed.
object
A JSON Pointer to the invalid field in the request body, or #/ followed by the name of an invalid query parameter.
What’s wrong with the field and how to fix it.
Examples
Missing API key
{ "type": "https://saverio-cirillo.com/krk-plays-tennis-api/errors/#unauthenticated", "title": "Authentication required", "status": 401, "detail": "Send a valid API key in the X-API-Key header.", "instance": "/v1/players/plr_2b7xq9mhk4"}The access token can’t submit this result.
An error, as RFC 9457 problem details.
object
A URL that identifies the error type and links to its documentation.
A short summary of the error type. It’s the same for every error of this type.
The HTTP status code.
An explanation of this occurrence of the error.
The request path that caused the error.
One item per invalid field or parameter. Only for invalid-request, invalid-score, and validation-failed.
object
A JSON Pointer to the invalid field in the request body, or #/ followed by the name of an invalid query parameter.
What’s wrong with the field and how to fix it.
Examples
Token without the matches:write scope
{ "type": "https://saverio-cirillo.com/krk-plays-tennis-api/errors/#insufficient-scope", "title": "Insufficient scope", "status": 403, "detail": "This operation requires the matches:write scope.", "instance": "/v1/matches"}Competition run by another organizer
{ "type": "https://saverio-cirillo.com/krk-plays-tennis-api/errors/#not-competition-organizer", "title": "Not the competition organizer", "status": 403, "detail": "Only the organizer of competition cmp_1qv7mz3kpd can submit its results.", "instance": "/v1/matches"}The request conflicts with an earlier request or an existing match.
An error, as RFC 9457 problem details.
object
A URL that identifies the error type and links to its documentation.
A short summary of the error type. It’s the same for every error of this type.
The HTTP status code.
An explanation of this occurrence of the error.
The request path that caused the error.
One item per invalid field or parameter. Only for invalid-request, invalid-score, and validation-failed.
object
A JSON Pointer to the invalid field in the request body, or #/ followed by the name of an invalid query parameter.
What’s wrong with the field and how to fix it.
Examples
Idempotency key reused with a different body
{ "type": "https://saverio-cirillo.com/krk-plays-tennis-api/errors/#idempotency-conflict", "title": "Idempotency key conflict", "status": 409, "detail": "The Idempotency-Key 5f0c6a2e-8d47-4b8e-9a51-2b3c7e1d9f04 was already used with a different request body.", "instance": "/v1/matches"}Same players, competition, and start time
{ "type": "https://saverio-cirillo.com/krk-plays-tennis-api/errors/#duplicate-match", "title": "Duplicate match", "status": 409, "detail": "Match mat_4nc8xq2vbt already records these players in this competition at 2026-10-10T18:00:00+02:00.", "instance": "/v1/matches"}The request is well formed, but the result isn’t valid.
An error, as RFC 9457 problem details.
object
A URL that identifies the error type and links to its documentation.
A short summary of the error type. It’s the same for every error of this type.
The HTTP status code.
An explanation of this occurrence of the error.
The request path that caused the error.
One item per invalid field or parameter. Only for invalid-request, invalid-score, and validation-failed.
object
A JSON Pointer to the invalid field in the request body, or #/ followed by the name of an invalid query parameter.
What’s wrong with the field and how to fix it.
Examples
Same player on both sides
{ "type": "https://saverio-cirillo.com/krk-plays-tennis-api/errors/#validation-failed", "title": "Validation failed", "status": 422, "detail": "The request has 1 invalid field.", "instance": "/v1/matches", "errors": [ { "pointer": "/player_ids", "detail": "The two players must be different." } ]}Score that can't end a set
{ "type": "https://saverio-cirillo.com/krk-plays-tennis-api/errors/#invalid-score", "title": "Invalid score", "status": 422, "detail": "The score isn't valid for the competition's match format.", "instance": "/v1/matches", "errors": [ { "pointer": "/score/sets/0/games", "detail": "In this competition, a set ends at 6 games with a 2-game lead, at 7-5, or at 7-6 with a tiebreak. 6-5 isn't a final set score." } ]}You sent too many requests. Wait for the number of seconds in Retry-After, then retry.
An error, as RFC 9457 problem details.
object
A URL that identifies the error type and links to its documentation.
A short summary of the error type. It’s the same for every error of this type.
The HTTP status code.
An explanation of this occurrence of the error.
The request path that caused the error.
One item per invalid field or parameter. Only for invalid-request, invalid-score, and validation-failed.
object
A JSON Pointer to the invalid field in the request body, or #/ followed by the name of an invalid query parameter.
What’s wrong with the field and how to fix it.
Examples
Rate limit reached
{ "type": "https://saverio-cirillo.com/krk-plays-tennis-api/errors/#rate-limited", "title": "Rate limit reached", "status": 429, "detail": "This API key is limited to 60 requests per minute. Retry after 30 seconds.", "instance": "/v1/matches"}Headers
Section titled “Headers”Number of seconds to wait before you retry.
Example
30© 2026 Saverio Cirillo · KRKplaysTennis is a fictional API · Built with Starlight