{"openapi":"3.1.0","info":{"title":"OpenScoreboard Tournament Management API","version":"1.0.0","description":"Create and manage broadcasts, rosters, and tournament resources. OAuth access tokens are intended for production integrations; personal osb_test keys are available for limited development use. Every response includes OpenScoreboard-Request-ID for support correlation. Authenticated responses include RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset headers; HTTP 429 responses also include Retry-After."},"servers":[{"url":"/api/v1"}],"components":{"headers":{"RequestID":{"description":"Stable identifier for correlating this request with partner logs and the owner's private integration activity.","schema":{"type":"string"}}},"parameters":{"RequestID":{"name":"OpenScoreboard-Request-ID","in":"header","required":false,"description":"Optional partner correlation ID. Use 8-120 letters, numbers, periods, underscores, colons, or hyphens.","schema":{"type":"string","minLength":8,"maxLength":120,"pattern":"^[A-Za-z0-9._:-]+$"}}},"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"OAuth osb_token or personal developer osb_test credential."}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{}}}}},"OAuthTokenRequest":{"type":"object","required":["grant_type","client_id","client_secret"],"properties":{"grant_type":{"type":"string","enum":["authorization_code","refresh_token"]},"client_id":{"type":"string"},"client_secret":{"type":"string"},"code":{"type":"string"},"code_verifier":{"type":"string","minLength":43,"maxLength":128,"pattern":"^[A-Za-z0-9._~-]+$","description":"Required when the authorization request supplied a PKCE S256 code challenge."},"redirect_uri":{"type":"string","format":"uri"},"refresh_token":{"type":"string"}}},"ConnectionInfo":{"type":"object","required":["appID","connectionID","scopes","tokenExpiresOn"],"properties":{"appID":{"type":"string"},"appName":{"type":"string"},"approvedOn":{"type":"string","format":"date-time"},"connectionID":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}},"tokenExpiresOn":{"type":"string","format":"date-time"}}},"BroadcastWrite":{"type":"object","required":["name","tables"],"properties":{"name":{"type":"string"},"description":{"type":"string"},"defaultPlayerListID":{"type":"string","description":"Optional roster-backed player list applied to every court."},"defaultScoreboardID":{"type":"string"},"scoreboardIDs":{"type":"array","items":{"type":"string"}},"tables":{"type":"array","minItems":1,"maxItems":50,"items":{"type":"object","required":["label"],"properties":{"id":{"type":"string","description":"Existing broadcast court ID. Omit when adding a court."},"label":{"type":"string"},"scoreboardIDs":{"type":"array","items":{"type":"string"}}}}}}},"BroadcastPatch":{"type":"object","minProperties":1,"description":"Partial broadcast update. Supplying tables replaces the broadcast court configuration.","properties":{"name":{"type":"string"},"description":{"type":"string"},"defaultPlayerListID":{"type":"string"},"defaultScoreboardID":{"type":"string"},"scoreboardIDs":{"type":"array","items":{"type":"string"}},"tables":{"$ref":"#/components/schemas/BroadcastWrite/properties/tables"}}},"Broadcast":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"createdOn":{"type":"string","format":"date-time"},"updatedOn":{"type":"string","format":"date-time"},"defaultPlayerListID":{"type":"string"},"defaultScoreboardID":{"type":"string"},"scoreboardIDs":{"type":"array","items":{"type":"string"}},"tableCount":{"type":"integer","minimum":0},"outputCount":{"type":"integer","minimum":0},"tables":{"type":"array","description":"Broadcast courts. Internal table and dynamic URL identifiers are intentionally omitted.","items":{"type":"object","required":["id","label","scoreboardIDs"],"properties":{"id":{"type":"string"},"label":{"type":"string"},"scoreboardIDs":{"type":"array","items":{"type":"string"}},"outputs":{"type":"array","items":{"type":"object","required":["scoreboardID"],"properties":{"scoreboardID":{"type":"string"}}}}}}}}},"RosterWrite":{"type":"object","required":["name","rosterType"],"properties":{"name":{"type":"string"},"description":{"type":"string"},"rosterType":{"type":"string","enum":["individual","team"]},"selfRegistrationEnabled":{"type":"boolean","description":"Available for individual rosters."},"teamLogoURL":{"type":"string","format":"uri"},"teamJerseyColor":{"type":"string"}}},"RosterPatch":{"type":"object","minProperties":1,"properties":{"name":{"type":"string"},"description":{"type":"string"},"rosterType":{"type":"string","enum":["individual","team"],"description":"Accepted only when unchanged from the current type."},"selfRegistrationEnabled":{"type":"boolean"},"teamLogoURL":{"type":"string","format":"uri"},"teamJerseyColor":{"type":"string"}}},"Roster":{"type":"object","required":["id","name","rosterType"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"rosterType":{"type":"string","enum":["individual","team"]},"playerCount":{"type":"integer","minimum":0},"selfRegistrationEnabled":{"type":"boolean"},"teamLogoURL":{"type":"string"},"teamJerseyColor":{"type":"string"},"createdOn":{"type":"string","format":"date-time"},"updatedOn":{"type":"string","format":"date-time"}}},"ExternalPlayer":{"type":"object","required":["externalID"],"properties":{"externalID":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"firstNameInitial":{"type":"boolean"},"lastNameInitial":{"type":"boolean"},"country":{"type":"string"},"clubName":{"type":"string"},"gender":{"type":"string"},"imageURL":{"type":"string"},"jerseyColor":{"type":"string"},"ranking":{"type":["number","string"]},"rating":{"type":["number","string"]}}},"PlayerListSync":{"type":"object","required":["externalID","name","players"],"properties":{"externalID":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"players":{"type":"array","items":{"$ref":"#/components/schemas/ExternalPlayer"}}}},"TeamSync":{"type":"object","required":["externalID","name","players"],"properties":{"externalID":{"type":"string"},"name":{"type":"string"},"logoURL":{"type":"string"},"jerseyColor":{"type":"string"},"players":{"type":"array","items":{"$ref":"#/components/schemas/ExternalPlayer"}}}},"PlayerListPatch":{"type":"object","additionalProperties":false,"anyOf":[{"required":["upsert"]},{"required":["removeExternalIDs"]},{"required":["name"]},{"required":["description"]}],"properties":{"upsert":{"type":"array","maxItems":500,"description":"Complete replacement records for mapped players, keyed by each player's externalID.","items":{"$ref":"#/components/schemas/ExternalPlayer"}},"removeExternalIDs":{"type":"array","maxItems":500,"uniqueItems":true,"description":"Mapped player external IDs to remove. Unmapped account-managed players are never removed.","items":{"type":"string"}},"description":{"type":"string","maxLength":800},"name":{"type":"string","minLength":1,"maxLength":120}}},"TeamPatch":{"type":"object","additionalProperties":false,"anyOf":[{"required":["upsert"]},{"required":["removeExternalIDs"]},{"required":["name"]},{"required":["jerseyColor"]},{"required":["logoURL"]}],"properties":{"upsert":{"type":"array","maxItems":500,"description":"Complete replacement records for mapped players, keyed by each player's externalID.","items":{"$ref":"#/components/schemas/ExternalPlayer"}},"removeExternalIDs":{"type":"array","maxItems":500,"uniqueItems":true,"description":"Mapped player external IDs to remove. Unmapped account-managed players are never removed.","items":{"type":"string"}},"jerseyColor":{"type":"string","maxLength":120},"logoURL":{"type":"string","maxLength":1000},"name":{"type":"string","minLength":1,"maxLength":120}}},"MatchPlayer":{"type":"object","properties":{"externalID":{"type":"string"},"manualName":{"type":"string"}}},"MatchParticipants":{"type":"object","additionalProperties":false,"properties":{"playerA":{"$ref":"#/components/schemas/MatchPlayer"},"playerA2":{"$ref":"#/components/schemas/MatchPlayer"},"playerB":{"$ref":"#/components/schemas/MatchPlayer"},"playerB2":{"$ref":"#/components/schemas/MatchPlayer"}}},"MatchResource":{"type":"object","required":["externalID","id","participants","scheduledMatchID","status","tableID"],"properties":{"bestOf":{"type":"number"},"completedOn":{"type":"string"},"eventName":{"type":"string"},"externalID":{"type":"string"},"gameScores":{"type":"array","items":{"type":"object","properties":{"a":{"type":"number"},"b":{"type":"number"}}}},"id":{"type":"string"},"isActive":{"type":"boolean"},"isComplete":{"type":"boolean"},"isDoubles":{"type":"boolean"},"matchDetails":{"type":"string"},"matchRound":{"type":"string"},"participants":{"$ref":"#/components/schemas/MatchParticipants"},"playerA":{"type":"string","description":"Legacy side A display string."},"playerA2":{"type":"string"},"playerB":{"type":"string","description":"Legacy side B display string."},"playerB2":{"type":"string"},"playerListExternalID":{"type":"string"},"queuePosition":{"type":["number","null"]},"scheduledMatchID":{"type":"string"},"scheduledOn":{"type":"string"},"score":{"type":"object","properties":{"a":{"type":"number"},"b":{"type":"number"}}},"startTime":{"type":"string"},"status":{"type":"string"},"tableID":{"type":"string"}}},"MatchSync":{"type":"object","required":["externalID","tableID","playerListExternalID"],"properties":{"externalID":{"type":"string"},"tableID":{"type":"string"},"playerListExternalID":{"type":"string"},"eventName":{"type":"string"},"matchRound":{"type":"string"},"matchDetails":{"type":"string"},"startTime":{"type":"string","format":"date-time"},"bestOf":{"type":["number","string"]},"isDoubles":{"type":"boolean"},"playerA":{"$ref":"#/components/schemas/MatchPlayer"},"playerA2":{"$ref":"#/components/schemas/MatchPlayer"},"playerB":{"$ref":"#/components/schemas/MatchPlayer"},"playerB2":{"$ref":"#/components/schemas/MatchPlayer"}}},"MatchBatchSync":{"type":"object","required":["matches"],"properties":{"matches":{"type":"array","minItems":1,"maxItems":100,"items":{"$ref":"#/components/schemas/MatchSync"}}}},"MatchBatchAssignment":{"type":"object","required":["matches"],"properties":{"matches":{"type":"array","minItems":1,"maxItems":100,"items":{"type":"object","required":["externalID","tableID"],"additionalProperties":false,"properties":{"externalID":{"type":"string"},"tableID":{"type":"string"}}}}}},"MatchBatchCancellation":{"type":"object","required":["externalIDs"],"properties":{"externalIDs":{"type":"array","minItems":1,"maxItems":100,"uniqueItems":true,"items":{"type":"string"}}}},"MatchAssignment":{"type":"object","required":["tableID"],"additionalProperties":false,"properties":{"tableID":{"type":"string"}}},"TablePlayerListAssignment":{"type":"object","required":["playerListExternalID"],"additionalProperties":false,"properties":{"playerListExternalID":{"type":["string","null"]}}},"TablePlayerListAssignmentBatch":{"type":"object","required":["tables"],"additionalProperties":false,"properties":{"tables":{"type":"array","minItems":1,"maxItems":100,"items":{"type":"object","required":["tableID","playerListExternalID"],"additionalProperties":false,"properties":{"tableID":{"type":"string"},"playerListExternalID":{"type":["string","null"]}}}}}},"WebhookConfiguration":{"type":"object","properties":{"events":{"type":"array","uniqueItems":true,"items":{"type":"string","enum":["match.started","match.score_updated","match.completed","match.result_updated"]}},"url":{"type":"string","format":"uri"}}},"WebhookDelivery":{"type":"object","required":["attemptCount","eventID","eventType","payload","status"],"properties":{"attemptCount":{"type":"integer","minimum":0},"attemptedOn":{"type":"string"},"createdOn":{"type":"string"},"deliveredOn":{"type":"string"},"eventID":{"type":"string","pattern":"^[a-f0-9]{40}$"},"eventType":{"type":"string"},"lastError":{"type":"string"},"nextAttemptOn":{"type":"string"},"payload":{"type":"object","additionalProperties":true},"status":{"type":"string","enum":["pending","delivering","delivered","failed"]},"webhookCreatedOn":{"type":"string"}}},"WebhookRetry":{"type":"object","required":["eventID"],"additionalProperties":false,"properties":{"eventID":{"type":"string","pattern":"^[a-f0-9]{40}$"}}}}},"paths":{"/health":{"get":{"summary":"Check API availability","responses":{"200":{"description":"Healthy"}}}},"/oauth/token":{"post":{"summary":"Exchange an authorization code or rotating refresh token for credentials","description":"Supports grant_type authorization_code and refresh_token. Authorization codes created with an optional PKCE S256 challenge require the matching code_verifier. The registered client secret remains required for both PKCE and legacy exchanges. Refresh tokens are single-use and replaced on every successful refresh.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthTokenRequest"}},"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/OAuthTokenRequest"}}}},"responses":{"200":{"description":"Access and refresh tokens issued"},"400":{"description":"Invalid, expired, revoked, or previously consumed grant"},"401":{"description":"Invalid application client credentials"}}}},"/connection":{"get":{"summary":"Inspect the active connection and token grant","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Connection identity, scopes, and token expiry"}}},"delete":{"summary":"Disconnect this approved installation and revoke all of its tokens","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Connection revoked"}}}},"/broadcasts":{"get":{"summary":"List broadcasts","security":[{"bearerAuth":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Broadcasts owned by the connected account"}}},"post":{"summary":"Create a broadcast and its managed courts","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BroadcastWrite"}}}},"responses":{"201":{"description":"Broadcast created"}}}},"/broadcasts/{broadcastID}":{"parameters":[{"name":"broadcastID","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"Read a broadcast","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Broadcast workspace"},"404":{"description":"Broadcast not found"}}},"patch":{"summary":"Update a broadcast and managed courts","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BroadcastPatch"}}}},"responses":{"200":{"description":"Broadcast updated"}}},"delete":{"summary":"Delete a broadcast and its managed output links","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Broadcast deleted"}}}},"/rosters":{"get":{"summary":"List unified rosters","security":[{"bearerAuth":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Individual and team rosters"}}},"post":{"summary":"Create a roster and managed backing record","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RosterWrite"}}}},"responses":{"201":{"description":"Roster created"}}}},"/rosters/{rosterID}":{"parameters":[{"name":"rosterID","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"Read a unified roster","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Roster and managed source details"},"404":{"description":"Roster not found"}}},"patch":{"summary":"Update roster settings and managed source details","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RosterPatch"}}}},"responses":{"200":{"description":"Roster updated"}}},"delete":{"summary":"Delete a roster and its managed backing record","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Roster deleted"}}}},"/player-lists":{"get":{"summary":"List player lists","security":[{"bearerAuth":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Player lists"}}},"put":{"summary":"Idempotently synchronize a player list","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlayerListSync"}}}},"responses":{"200":{"description":"Player list synchronized"}}}},"/player-lists/{externalID}":{"get":{"summary":"Read a synchronized player list by partner external ID","security":[{"bearerAuth":[]}],"parameters":[{"name":"externalID","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Player list and players"},"404":{"description":"Player list not found"}}},"patch":{"summary":"Incrementally upsert or remove integration-managed players","description":"Updates only players mapped by this connection and preserves manually managed player-list entries.","security":[{"bearerAuth":[]}],"parameters":[{"name":"externalID","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlayerListPatch"}}}},"responses":{"200":{"description":"Player-list roster updated"},"400":{"description":"Invalid or conflicting roster patch"},"404":{"description":"Player list not found"}}}},"/teams":{"get":{"summary":"List teams","security":[{"bearerAuth":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Teams"}}},"put":{"summary":"Idempotently synchronize a team","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamSync"}}}},"responses":{"200":{"description":"Team synchronized"}}}},"/teams/{externalID}":{"get":{"summary":"Read a synchronized team by partner external ID","security":[{"bearerAuth":[]}],"parameters":[{"name":"externalID","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Team and roster"},"404":{"description":"Team not found"}}},"patch":{"summary":"Incrementally upsert or remove integration-managed roster players","description":"Updates only players mapped by this connection and preserves manually managed team members.","security":[{"bearerAuth":[]}],"parameters":[{"name":"externalID","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamPatch"}}}},"responses":{"200":{"description":"Team roster updated"},"400":{"description":"Invalid or conflicting roster patch"},"404":{"description":"Team not found"}}}},"/tables":{"get":{"summary":"List scoring tables","security":[{"bearerAuth":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Tables"}}}},"/tables/bulk":{"patch":{"summary":"Assign or clear synchronized player lists on up to 100 tables","description":"Returns ordered per-table results. Independent table writes use bounded concurrency and invalid assignments do not roll back successful tables.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TablePlayerListAssignmentBatch"}}}},"responses":{"200":{"description":"Every table assignment succeeded"},"207":{"description":"Some table assignments failed; inspect each result"},"400":{"description":"Malformed batch or duplicate table ID"}}}},"/tables/{tableID}":{"get":{"summary":"Read a table and this connection's scheduled matches","security":[{"bearerAuth":[]}],"parameters":[{"name":"tableID","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Table and integration-managed queue"},"404":{"description":"Table not found"}}},"patch":{"summary":"Assign or clear a synchronized player list on a table","security":[{"bearerAuth":[]}],"parameters":[{"name":"tableID","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TablePlayerListAssignment"}}}},"responses":{"200":{"description":"Table updated"}}}},"/matches":{"get":{"summary":"List integration-managed matches","security":[{"bearerAuth":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Matches"}}},"put":{"summary":"Idempotently create or replace a pending scheduled match","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatchSync"}}}},"responses":{"200":{"description":"Match synchronized"},"409":{"description":"Active or completed match is locked"}}},"post":{"summary":"Alias for idempotent match synchronization","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Match synchronized"}}}},"/matches/{externalID}":{"get":{"summary":"Get a match by partner external ID","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Match with legacy display strings and connection-scoped participant identities","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/MatchResource"}}}}}}}},"patch":{"summary":"Move or replace a pending match by partner external ID","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/MatchAssignment"},{"$ref":"#/components/schemas/MatchSync"}]}}}},"responses":{"200":{"description":"Match moved or updated"},"409":{"description":"Active or completed match is locked"}}},"delete":{"summary":"Cancel a pending match","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Match deleted"},"409":{"description":"Active or completed match is locked"}}}},"/matches/bulk":{"post":{"summary":"Synchronize up to 100 matches with per-table request ordering and per-match results","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatchBatchSync"}}}},"responses":{"200":{"description":"Every match synchronized"},"207":{"description":"Some matches failed; inspect each result"}}},"patch":{"summary":"Move up to 100 pending matches to owned tables","description":"Preserves request order within each destination table and returns per-match results.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatchBatchAssignment"}}}},"responses":{"200":{"description":"Every pending match moved or was already assigned to its requested table"},"207":{"description":"Some matches failed; inspect each result"}}}},"/matches/bulk/cancel":{"post":{"summary":"Cancel up to 100 pending matches by external ID","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatchBatchCancellation"}}}},"responses":{"200":{"description":"Every pending match cancelled"},"207":{"description":"Some matches failed; inspect each result"}}}},"/webhooks":{"get":{"summary":"List persisted webhook delivery statuses","security":[{"bearerAuth":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Webhook deliveries"}}}},"/webhooks/{eventID}":{"get":{"summary":"Read one persisted webhook delivery and its exact event payload","security":[{"bearerAuth":[]}],"parameters":[{"name":"eventID","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-f0-9]{40}$"}}],"responses":{"200":{"description":"Webhook delivery","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WebhookDelivery"}}}}}},"400":{"description":"Invalid webhook event ID"},"404":{"description":"Webhook delivery not found"}}}},"/webhooks/configuration":{"get":{"summary":"Read webhook URL and event subscriptions","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Webhook configuration"}}},"patch":{"summary":"Update webhook URL or event subscriptions","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookConfiguration"}}}},"responses":{"200":{"description":"Webhook configuration updated"}}}},"/webhooks/retry":{"post":{"summary":"Retry a persisted webhook delivery","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookRetry"}}}},"responses":{"200":{"description":"Delivery attempted"},"400":{"description":"Invalid webhook event ID"},"404":{"description":"Webhook delivery not found"}}}},"/webhooks/test":{"post":{"summary":"Persist and send a signed integration.test webhook","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Test delivered on the first attempt"},"202":{"description":"First attempt failed and the test was queued for retry"},"409":{"description":"Webhook destination is not configured"}}}},"/webhooks/secret/rotate":{"post":{"summary":"Rotate the connection webhook signing secret","description":"Immediately replaces the current signing secret. Retrying with the same Idempotency-Key returns the same current secret; a previously completed older key is rejected after another rotation. Pending deliveries and retries use the new secret.","security":[{"bearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200}}],"responses":{"200":{"description":"Signing secret rotated; store webhook_secret securely because it cannot be read again"},"400":{"description":"Idempotency-Key is missing or invalid"},"404":{"description":"Active connection not found"},"409":{"description":"Idempotency-Key was used for an older rotation"}}}}}}