Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: Lucra Forge Tournaments API
description: "See https://docs.lucrasports.com/lucra-sdk/sdks-and-apis for implementation details.\n\n---\n\n## Environments\n\n| Environment | Base URL |\n|-------------|----------|\n| Sandbox | `https://forge.sandbox.lucrasports.com` |\n| Production | `https://forge.lucrasports.com` |\n\nUse sandbox for development and testing. Production credentials are separate and should only be used in live environments.\n\n---\n\n## Authentication\n\nAll requests require an API key passed in the `X-Lucra-Api-Key` header. Keys are provisioned by the Lucra team.\n\n```bash\ncurl https://forge.sandbox.lucrasports.com/api/<endpoint> \\\n -H \"X-Lucra-Api-Key: <your-api-key>\"\n```\n\n> **Note:** Unlike the legacy API, query parameter and request body authentication are not supported.\n\n---\n\n## Rate Limiting\n\nAll API requests are rate-limited per API key using a fixed-window strategy. Each key is allowed up to **100 requests per 10-second window**.\n\nWhen the limit is exceeded, the API responds with **429 Too Many Requests**.\n"
version: '1.0'
contact: {}
servers:
- url: /
description: Current host
- url: https://forge.lucrasports.com
description: Production
- url: https://forge.sandbox.lucrasports.com
description: Sandbox
tags:
- name: Tournaments
description: "\nModular tournament management endpoints. Unlike the legacy API which returns everything in a single call,\nthe v2 API separates concerns into dedicated resources:\n\n| Resource | Path | Purpose |\n|----------|------|---------|\n| **Tournaments** | `/tournaments` | CRUD operations, cancel, complete |\n| **Leaderboard** | `/tournaments/:id/leaderboard` | Paginated participant rankings and scores |\n| **Rewards** | `/tournaments/:id/rewards` | Prize tier configuration and winner assignment |\n\n---\n\n## Key Differences from Legacy API\n\n### Separate Resources\nThe legacy `GET /pool-tournament/:id` returns the tournament, reward structure, and full user leaderboard in one response.\nThe v2 API splits these into three independent endpoints so clients only fetch what they need.\n\n### Update and Complete are Separate\nThe legacy complete endpoint accepts tournament field updates (title, fee, etc.) in the same request body as the completion action.\nIn v2, update the tournament first via `PATCH /tournaments/:id`, then complete via `POST /tournaments/:id/complete`.\n\n### Reward Assignment is Explicit\nThe legacy complete endpoint accepts a `paymentStructure` with `userId` to assign winners and complete in one step.\nIn v2, assign rewards first via `PUT /tournaments/:id/rewards`, then complete the tournament separately.\n\n---\n\n## Tournament Types\n\n### CASH_FIXED\nPrize pool is defined upfront. Values in the reward tiers represent absolute monetary amounts.\n\n### CASH_PERCENTAGE\nPrize pool is calculated from total entry fees. Values in reward tiers represent percentages that must sum to exactly 100.\n\n---\n\n## Sign-Up Window\n\nTournaments can optionally define a sign-up window using `signUpStart` and `signUpEnd`.\nWhen set, participants can only join during this window. If omitted, sign-ups follow the\ndefault behavior (open from creation until the tournament expires).\n\n| Constraint | Rule |\n|-----------|------|\n| `signUpEnd` ≤ `expiresAt` | Sign-ups must close before tournament expiration |\n| `signUpStart` < `signUpEnd` | Window must have a positive duration |\n\n---\n\n## Simplified Status Model\n\nThe v2 API exposes three statuses instead of the full internal status set:\n\n| Status | Meaning |\n|--------|---------|\n| `ACTIVE` | Tournament is open or in progress |\n| `COMPLETED` | Tournament is closed and rewards distributed |\n| `CANCELED` | Tournament was canceled and participants refunded |\n\n---\n\n## Pagination\n\nList endpoints (`GET /tournaments`, `GET /tournaments/:id/leaderboard`) support pagination via query parameters:\n\n| Parameter | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `limit` | number | 25 | Number of items per page (1–100) |\n| `offset` | number | 0 | Number of items to skip |\n\nThe response body is a **flat array** of items. Pagination metadata is returned in the `Link` HTTP header following [RFC 5988](https://tools.ietf.org/html/rfc5988).\n\n**Example response headers:**\n\n```\nLink: </api/tournaments?limit=25&offset=25>; rel=\"next\", </api/tournaments?limit=25&offset=0>; rel=\"first\"\n```\n\n**Available link relations:**\n\n| Rel | Description |\n|-----|-------------|\n| `next` | Next page of results (omitted on the last page) |\n| `prev` | Previous page of results (omitted on the first page) |\n| `first` | First page of results |\n\n**Parsing the Link header:**\n\n```typescript\nfunction parseLinkHeader(header: string): Record<string, string> {\n return Object.fromEntries(\n header.split(', ').map((part) => {\n const [url, rel] = part.split('; ');\n return [\n rel.replace('rel=\"', '').replace('\"', ''),\n url.slice(1, -1),\n ];\n }),\n );\n}\n\n// Usage\nconst links = parseLinkHeader(response.headers.link);\nif (links.next) {\n // fetch next page\n}\n```\n"
paths:
/api/tournaments:
post:
description: 'Create a new pool tournament.
Required fields: `title`, `type`, `buyInAmount`. The reward structure is managed separately via the Rewards endpoint after creation.
Returns the created tournament. Use the returned `id` for all subsequent operations.'
operationId: TournamentsApiController_createTournament
parameters: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/TournamentCreateDto'
responses:
'201':
description: Tournament created successfully
headers:
X-Request-Id:
description: Unique request identifier for tracing and debugging.
schema:
type: string
example: req_abc123
content:
application/json:
schema:
$ref: '#/components/schemas/TournamentResponseDto'
security:
- X-Lucra-Api-Key: []
summary: Create Tournament
tags:
- Tournaments
get:
description: 'Returns a paginated list of tournaments for the authenticated tenant.
Supports filtering by `status` (ACTIVE, COMPLETED, CANCELED) and `gameId`. Standard pagination via `limit` and `offset` query parameters.'
operationId: TournamentsApiController_getTournaments
parameters:
- name: gameId
required: false
in: query
description: Identifier of the game associated with the tournament
schema:
example: BASKETBALL
type:
- string
- 'null'
- name: status
required: false
in: query
description: Lifecycle status of the tournament
schema:
example: OPEN
type: string
enum:
- ACTIVE
- COMPLETED
- CANCELED
- name: limit
required: false
in: query
description: Number of items to return per page
schema:
minimum: 1
maximum: 100
default: 25
type: number
- name: offset
required: false
in: query
description: Number of items to skip before returning results
schema:
minimum: 0
default: 0
type: number
responses:
'200':
description: Tournament list retrieved successfully
headers:
X-Request-Id:
description: Unique request identifier for tracing and debugging.
schema:
type: string
example: req_abc123
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/TournamentResponseDto'
security:
- X-Lucra-Api-Key: []
summary: List Tournaments
tags:
- Tournaments
patch:
description: 'Resolve a tournament by identifiers and update its properties.
The `identifier` object must contain at least one of: `matchupMetadata`, `matchupId`, `gameId`, or `locationId`. The identifier must resolve to exactly one tournament — if zero or multiple match, the request fails.
All tournament update fields are optional — only provided fields are updated.'
operationId: TournamentsApiController_updateMatchingTournament
parameters: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/TournamentUpdateMatchingDto'
responses:
'200':
description: Tournament updated successfully
headers:
X-Request-Id:
description: Unique request identifier for tracing and debugging.
schema:
type: string
example: req_abc123
content:
application/json:
schema:
$ref: '#/components/schemas/TournamentResponseDto'
security:
- X-Lucra-Api-Key: []
summary: Update Tournament by Metadata
tags:
- Tournaments
/api/tournaments/complete:
post:
description: 'Resolve a tournament by identifiers and queue it for completion.
The `identifier` object must contain at least one of: `matchupMetadata`, `matchupId`, `gameId`, or `locationId`. The identifier must resolve to exactly one tournament.
## Payment structure
If `paymentStructure` is provided, each entry identifies the recipient via `userId`, `phoneNumber`, or `userMetadata`. Each entry must resolve to exactly one user, and every resolved user must be a confirmed participant.
If `paymentStructure` is omitted, the system auto-completes using current standings and existing reward tiers.
## Async processing
This endpoint returns `202 Accepted` immediately. The actual completion is processed asynchronously. Subscribe to the `TournamentCompleted` webhook event to be notified when completion finishes, to `TournamentComplianceLimitExceeded` when the tournament is placed on hold, and to `TournamentCompletionFailed` to be notified of processing errors. Failure events include a `failure` block with `code`, `reason`, `requestId`, `attempt`, and `willRetry`; `willRetry: false` indicates the request will not be retried.'
operationId: TournamentsApiController_completeMatchingTournament
parameters: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/TournamentCompleteMatchingDto'
responses:
'202':
description: ''
security:
- X-Lucra-Api-Key: []
summary: Complete Tournament by Metadata
tags:
- Tournaments
/api/tournaments/{id}:
patch:
description: 'Partially update tournament properties.
All fields are optional — only provided fields are updated. Some fields may have restrictions based on tournament status.'
operationId: TournamentsApiController_updateTournament
parameters:
- name: id
required: true
in: path
description: Tournament UUID
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/TournamentUpdateDto'
responses:
'200':
description: Tournament updated successfully
headers:
X-Request-Id:
description: Unique request identifier for tracing and debugging.
schema:
type: string
example: req_abc123
content:
application/json:
schema:
$ref: '#/components/schemas/TournamentResponseDto'
'404':
description: Tournament not found
headers:
X-Request-Id:
description: Unique request identifier for tracing and debugging.
schema:
type: string
example: req_abc123
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
security:
- X-Lucra-Api-Key: []
summary: Update Tournament
tags:
- Tournaments
get:
description: 'Retrieve tournament details by ID.
Returns tournament metadata and current participant count. For the full leaderboard, use `GET /tournaments/:id/leaderboard`. For the reward structure, use `GET /tournaments/:id/rewards`.'
operationId: TournamentsApiController_getTournament
parameters:
- name: id
required: true
in: path
description: Tournament UUID
schema:
type: string
responses:
'200':
description: Tournament details retrieved successfully
headers:
X-Request-Id:
description: Unique request identifier for tracing and debugging.
schema:
type: string
example: req_abc123
content:
application/json:
schema:
$ref: '#/components/schemas/TournamentResponseDto'
'404':
description: Tournament not found
headers:
X-Request-Id:
description: Unique request identifier for tracing and debugging.
schema:
type: string
example: req_abc123
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
security:
- X-Lucra-Api-Key: []
summary: Get Tournament
tags:
- Tournaments
/api/tournaments/{id}/cancel:
post:
description: 'Cancel a tournament and refund all participants.
- All participant entry fees are refunded
- Tournament status changes to `CANCELED`
- This action is irreversible'
operationId: TournamentsApiController_cancelTournament
parameters:
- name: id
required: true
in: path
description: Tournament UUID
schema:
type: string
responses:
'200':
description: Tournament cancelled successfully
headers:
X-Request-Id:
description: Unique request identifier for tracing and debugging.
schema:
type: string
example: req_abc123
content:
application/json:
schema:
$ref: '#/components/schemas/TournamentResponseDto'
'404':
description: Tournament not found
headers:
X-Request-Id:
description: Unique request identifier for tracing and debugging.
schema:
type: string
example: req_abc123
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
security:
- X-Lucra-Api-Key: []
summary: Cancel Tournament
tags:
- Tournaments
/api/tournaments/{id}/complete:
post:
description: 'Finalize a tournament and distribute payouts.
Before completing, ensure rewards are configured via `PUT /tournaments/:id/rewards`. If users are pre-assigned to rewards, the system will auto-complete using current leaderboard standings.
- Tournament status changes to `COMPLETED`
- Payout distribution is triggered
- This action is irreversible
**Unlike the legacy endpoint**, this does not accept tournament field updates or reward assignments in the request body. Update the tournament and assign rewards first using their respective endpoints.
The request is processed asynchronously — this endpoint returns `202 Accepted` immediately. Subscribe to the `TournamentCompleted` webhook event to be notified when completion finishes, to `TournamentComplianceLimitExceeded` when the tournament is placed on hold, and to `TournamentCompletionFailed` to be notified of processing errors. Failure events include a `failure` block with `code`, `reason`, `requestId`, `attempt`, and `willRetry`; `willRetry: false` indicates the request will not be retried.'
operationId: TournamentsApiController_completeTournament
parameters:
- name: id
required: true
in: path
description: Tournament UUID
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/TournamentCompleteDto'
responses:
'404':
description: Tournament not found
headers:
X-Request-Id:
description: Unique request identifier for tracing and debugging.
schema:
type: string
example: req_abc123
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
security:
- X-Lucra-Api-Key: []
summary: Complete Tournament
tags:
- Tournaments
/api/tournaments/scores:
post:
description: 'Submit scores for one or more users in one or more tournaments.
## How it works
1. **Find tournaments** matching your criteria (matchupId, matchupMetadata, gameId, or locationId)
2. **Resolve users** from the provided identifiers (userId, phoneNumber, or userMetadata)
3. **Filter tournaments** to only those where **all** submitted users are participants — if a tournament contains only some of the users, it is skipped
4. **Update scores** for each user in every matching tournament
## Async processing
This endpoint returns `202 Accepted` immediately. The actual score ingestion is processed asynchronously. Subscribe to the `TournamentEdited` webhook event to be notified when scores are updated, and to `ScoreIngestionFailed` to be notified of processing errors. Failure events include a `failure` block with `code`, `reason`, `requestId`, `attempt`, and `willRetry`; `willRetry: false` indicates the request will not be retried.
## Tournament matching
At least one matchup identifier is required. You can combine identifiers for precision:
- `matchupId` — direct UUID lookup (fastest, most precise)
- `matchupMetadata` — fuzzy metadata matching. Use `externalId` for deterministic matching
- `gameId` — filter by game identifier
- `locationId` — filter by location
## User matching
Each score entry requires at least one user identifier:
- `userId` — direct UUID lookup (fastest)
- `phoneNumber` — exact phone number match
- `userMetadata` — fuzzy metadata matching. Use `externalId` for deterministic matching.
## Multiple match behavior
Metadata matching can return multiple tournaments. Scores are updated across **all** tournaments where the criteria match and all users are participants. Use `matchupId` or `externalId` in metadata to target a single tournament.
## Important notes
- Tournaments have no auto-settlement — they must be closed manually via the complete endpoint
- `attemptFinished` marks the user''s attempt as complete but does not trigger tournament closure
- Partial failures are possible when updating across multiple tournaments; each tournament is processed independently'
operationId: TournamentsApiController_ingestScores
parameters: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/IngestUserScoreDto'
responses:
'202':
description: ''
security:
- X-Lucra-Api-Key: []
summary: Ingest Scores
tags:
- Tournaments
/api/tournaments/{id}/tags:
put:
operationId: TournamentsApiController_setTournamentTags
parameters:
- name: id
required: true
in: path
description: Tournament id
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/SetTournamentTagsDto'
responses:
'200':
description: ''
headers:
X-Request-Id:
description: Unique request identifier for tracing and debugging.
schema:
type: string
example: req_abc123
content:
application/json:
schema:
$ref: '#/components/schemas/TournamentTagsResponseDto'
'404':
description: Tournament not found
headers:
X-Request-Id:
description: Unique request identifier for tracing and debugging.
schema:
type: string
example: req_abc123
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
security:
- X-Lucra-Api-Key: []
summary: Set the tags assigned to a tournament.
tags:
- Tournaments
/api/tournaments/{id}/leaderboard:
get:
description: 'Returns the paginated leaderboard for a tournament, ordered by position.
Each entry includes the participant''s user info, current position, and best score. Supports standard pagination via `limit` and `offset` query parameters.
Use `positionOverride ?? position` to display a participant''s final ranking.'
operationId: TournamentLeaderboardController_getTournamentLeaderboard
parameters:
- name: id
required: true
in: path
description: Tournament UUID
schema:
type: string
- name: limit
required: false
in: query
description: Number of items to return per page
schema:
minimum: 1
maximum: 100
default: 25
type: number
- name: offset
required: false
in: query
description: Number of items to skip before returning results
schema:
minimum: 0
default: 0
type: number
responses:
'200':
description: Tournament leaderboard retrieved successfully
headers:
X-Request-Id:
description: Unique request identifier for tracing and debugging.
schema:
type: string
example: req_abc123
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/TournamentLeaderboardEntryResponseDto'
'404':
description: Tournament not found
headers:
X-Request-Id:
description: Unique request identifier for tracing and debugging.
schema:
type: string
example: req_abc123
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
security:
- X-Lucra-Api-Key: []
summary: Get Tournament Leaderboard
tags:
- Tournaments
/api/tournaments/rewards:
patch:
description: 'Replace the reward tier structure for a tournament located via matchup identifiers (`matchupId`, `matchupMetadata`, `gameId`, `locationId`).
This is a full replacement — all existing tiers are removed and replaced with the provided list.
- For `CASH_FIXED` tournaments, `value` is an absolute monetary amount
- For `CASH_PERCENTAGE` tournaments, `value` is a percentage (must sum to 100)
- Each tier may optionally assign a winner via `userId`, `phoneNumber`, or `userMetadata`'
operationId: TournamentRewardsController_updateMatching
parameters: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/TournamentRewardsUpdateMatchingDto'
responses:
'200':
description: Tournament reward structure updated successfully
headers:
X-Request-Id:
description: Unique request identifier for tracing and debugging.
schema:
type: string
example: req_abc123
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/TournamentRewardResponseDto'
'404':
description: Tournament not found
headers:
X-Request-Id:
description: Unique request identifier for tracing and debugging.
schema:
type: string
example: req_abc123
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
security:
- X-Lucra-Api-Key: []
summary: Update Tournament Rewards by Metadata
tags:
- Tournaments
/api/tournaments/{id}/rewards:
put:
description: 'Replace the reward tier structure for a tournament.
This is a full replacement — all existing tiers are removed and replaced with the provided list.
- For `CASH_FIXED` tournaments, `value` is an absolute monetary amount
- For `CASH_PERCENTAGE` tournaments, `value` is a percentage (must sum to 100)
- Optionally assign a `userId` to a tier to designate the winner for that position
Use this endpoint to assign winners before completing the tournament.'
operationId: TournamentRewardsController_updateTournamentRewards
parameters:
- name: id
required: true
in: path
description: Tournament UUID
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/TournamentRewardsUpdateDto'
responses:
'200':
description: Tournament reward structure updated successfully
headers:
X-Request-Id:
description: Unique request identifier for tracing and debugging.
schema:
type: string
example: req_abc123
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/TournamentRewardResponseDto'
'404':
description: Tournament not found
headers:
X-Request-Id:
description: Unique request identifier for tracing and debugging.
schema:
type: string
example: req_abc123
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
security:
- X-Lucra-Api-Key: []
summary: Update Tournament Rewards
tags:
- Tournaments
get:
description: 'Returns the current reward tier structure for a tournament.
Each tier includes the configured value and the calculated net/fee/total amounts based on the current pool.'
operationId: TournamentRewardsController_getTournamentRewards
parameters:
- name: id
required: true
in: path
description: Tournament UUID
schema:
example: 123e4567-e89b-12d3-a456-426614174000
type: string
responses:
'200':
description: Tournament reward structure retrieved successfully
headers:
X-Request-Id:
description: Unique request identifier for tracing and debugging.
schema:
type: string
example: req_abc123
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/TournamentRewardResponseDto'
'404':
description: Tournament not found
headers:
X-Request-Id:
description: Unique request identifier for tracing and debugging.
schema:
type: string
example: req_abc123
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
security:
- X-Lucra-Api-Key: []
summary: Get Tournament Rewards
tags:
- Tournaments
components:
schemas:
CompleteMatchingPaymentRowDto:
type: object
properties:
position:
type: number
description: Prize tier position. Repeat the same position across rows to mark a tie — tied rows split the combined tier value.
example: 1
value:
type: number
description: Prize value for this position
example: 500
userId:
type: string
description: UUID of the user receiving this reward
phoneNumber:
type: string
description: User's phone number for lookup
example: '+15551234567'
userMetadata:
type: object
description: Metadata key-value pairs to match user
example:
externalId: ext-123
required:
- position
- value
TournamentRewardsUpdateDto:
type: object
properties:
tiers:
description: Array of reward tiers to replace the current structure
type: array
items:
$ref: '#/components/schemas/TournamentRewardTierUpdateDto'
required:
- tiers
TournamentRewardTierUpdateDto:
type: object
properties:
position:
type: number
description: Sequential prize tier position (starting from 1)
example: 1
endPosition:
type: number
description: TENANT_REWARD only. End of an inclusive place range starting at `position`. Omit for a single place.
example: 3
value:
type: number
description: Prize value — absolute amount for CASH_FIXED, percentage for CASH_PERCENTAGE
example: 500
userId:
type: string
description: UUID of the user to assign as winner for this position
example: 123e4567-e89b-12d3-a456-426614174000
catalogRewardId:
type: string
description: TENANT_REWARD only. UUID of an existing reward catalog item to assign to this place. Mutually exclusive with `reward`.
example: 123e4567-e89b-12d3-a456-426614174000
reward:
description: TENANT_REWARD only. Inline catalog reward definition to create and assign. Mutually exclusive with `catalogRewardId`.
allOf:
- $ref: '#/components/schemas/NewRewardDto'
required:
- position
IngestUserScoreDto:
type: object
properties:
userScores:
description: Array of user scores to submit
type: array
items:
$ref: '#/components/schemas/IngestUserScoreEntryDto'
matchupId:
type: string
description: Matchup UUID. One of matchupId, gameId, or matchupMetadata is required.
example: a1b2c3d4-e5f6-7890-abcd-ef1234567890
matchupMetadata:
type: object
description: Metadata key-value pairs to match matchups. One of matchupId, gameId, or matchupMetadata is required.
gameId:
type: string
description: Game identifier to filter matchups. One of matchupId, gameId, or matchupMetadata is required.
example: BASKETBALL
locationId:
type: string
description: Location UUID to filter matchups. Applies to tournaments only.
example: a1b2c3d4-e5f6-7890-abcd-ef1234567890
required:
- userScores
NewRewardDto:
type: object
properties:
type:
type: string
description: Reward catalog type
example: UNIQUE_DISCOUNT_CODE
enum:
# --- truncated at 32 KB (56 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/lucra-sports/refs/heads/main/openapi/lucra-sports-tournaments-api-openapi.yml