Skip to content

Get an access token

To submit match results with POST /matches, you need an OAuth 2.0 access token with the matches:write scope. You get the token from the authorization server with the client credentials grant, then send it in the Authorization header.

Only competition organizers get client credentials, and a token can only submit results for competitions its organizer runs. To read data, use an API key instead.

  • A client ID and a client secret for your organizer account. With the local mock, any non-empty values work.
  • KRK_API_URL set to the API base URL. For the mock, that’s http://127.0.0.1:4010.
  1. Store the authorization server URL and your client credentials in environment variables:

    Terminal window
    export KRK_AUTH_URL=http://127.0.0.1:4010
    export KRK_CLIENT_ID=your-client-id
    export KRK_CLIENT_SECRET=your-client-secret

    For the mock, the authorization server runs at the same address as the API. In production, KRK_AUTH_URL is https://auth.krkplaystennis.example.

    To check the variables, run echo $KRK_AUTH_URL. The output is the URL you set.

  2. Request a token. Send your client ID and client secret with HTTP Basic authentication, and the grant type and scope as form fields:

    Terminal window
    curl --silent "$KRK_AUTH_URL/oauth/token" \
    --user "$KRK_CLIENT_ID:$KRK_CLIENT_SECRET" \
    --data grant_type=client_credentials \
    --data scope=matches:write

    The response has status 200 and contains the token:

    {
    "access_token": "eyJhbGciOiJSUzI1NiJ9.fictional-token-for-docs.c2lnbmF0dXJl",
    "token_type": "Bearer",
    "expires_in": 3600,
    "scope": "matches:write"
    }
  3. Copy the access_token value from the response in step 2, and store it in an environment variable:

    Terminal window
    export KRK_ACCESS_TOKEN='<access_token from step 2>'

    To check it, run echo $KRK_ACCESS_TOKEN. The output is the token.

  4. Send the token as Authorization: Bearer <token> to submit a match result. Each new result also needs a unique Idempotency-Key:

    Terminal window
    curl --silent "$KRK_API_URL/matches" \
    --header "Authorization: Bearer $KRK_ACCESS_TOKEN" \
    --header "Idempotency-Key: $(uuidgen)" \
    --header "Content-Type: application/json" \
    --data '{
    "competition_id": "cmp_6tw2hn8rxe",
    "venue_id": "ven_7hk2p9wq4m",
    "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]
    }
    }'

    The response has status 201 and the new match, with "rating_status": "pending". The API rates the match a few minutes later.

A token expires expires_in seconds after you get it, which is 1 hour. There’s no refresh token. When a token expires, the API returns status 401, and you request a new token with the same client credentials.

To avoid failed requests, keep the token and its expiry time in memory, and request a new token a minute before it expires.

Status error or type Cause Fix
401 from /oauth/token invalid_client The client ID or client secret is wrong. Check both values. Don’t send the client secret as a form field: use HTTP Basic.
400 from /oauth/token invalid_scope The scope doesn’t exist. Request matches:write, the only scope.
401 from /matches unauthenticated The token is missing or expired. Request a new token.
403 from /matches insufficient-scope The token doesn’t have the matches:write scope. Request a token with scope=matches:write.
403 from /matches not-competition-organizer Another organizer runs the competition. Submit results only for your own competitions.

The token endpoint follows the OAuth 2.0 standard (RFC 6749), so its errors have an error field. The rest of the API uses problem details, with a type field.

© 2026 Saverio Cirillo · KRKplaysTennis is a fictional API · Built with Starlight