pitchapi 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +123 -0
- package/dist/index.cjs +397 -0
- package/dist/index.d.cts +2021 -0
- package/dist/index.d.ts +2021 -0
- package/dist/index.js +360 -0
- package/package.json +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PitchAPI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# pitchapi — TypeScript SDK
|
|
2
|
+
|
|
3
|
+
Typed TypeScript/JavaScript client for the [PitchAPI](https://pitchapi.dev)
|
|
4
|
+
football-data API. Match results, shots, xG, lineups, momentum, events, player
|
|
5
|
+
stats, head-to-head, and advanced analytics derived from the event feed.
|
|
6
|
+
|
|
7
|
+
- Zero dependencies — built on the platform `fetch` (Node 18+, Bun, Deno, edge runtimes)
|
|
8
|
+
- Response types generated from the API's OpenAPI description
|
|
9
|
+
- ESM and CommonJS builds, with type declarations for both
|
|
10
|
+
- Typed errors mapped from the API's stable `error.code`
|
|
11
|
+
- Automatic retries on `429`/`5xx` with `Retry-After` honoured
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install pitchapi
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick start
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { PitchAPI } from "pitchapi";
|
|
23
|
+
|
|
24
|
+
const client = new PitchAPI({ apiKey: "pk_live_..." });
|
|
25
|
+
|
|
26
|
+
const day = await client.date.get("2026-08-27");
|
|
27
|
+
for (const m of day.matches) {
|
|
28
|
+
console.log(m.home_team.name, m.score_home, "-", m.score_away, m.away_team.name);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const match = await client.matches.get("m_4DP2fy");
|
|
32
|
+
const shots = await client.matches.shots(match.id);
|
|
33
|
+
const adv = await client.matches.advanced(match.id); // team rollups
|
|
34
|
+
const net = await client.matches.advancedNetwork(match.id); // pass networks
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The key can also come from the `PITCHAPI_API_KEY` environment variable, in
|
|
38
|
+
which case `new PitchAPI()` needs no arguments. Keep the key server-side: never
|
|
39
|
+
ship it in browser code.
|
|
40
|
+
|
|
41
|
+
## Namespaces
|
|
42
|
+
|
|
43
|
+
| Namespace | Methods |
|
|
44
|
+
|---|---|
|
|
45
|
+
| `client.date` | `get(date, { status })` — `date` is a `YYYY-MM-DD` string or a `Date` (read in UTC) |
|
|
46
|
+
| `client.matches` | `get`, `shots`, `shot`, `events`, `lineups`, `momentum`, `stats`, `players`, `player`, `playersHalves`, `playerHalves`, `playerShots`, `h2h`, `advanced`, `advancedNetwork`, `advancedPlayers`, `advancedPlayer`, `heatmaps(id, { frame })` |
|
|
47
|
+
| `client.leagues` | `list`, `get`, `matches(id, { season, status })` |
|
|
48
|
+
| `client.teams` | `get` |
|
|
49
|
+
| `client.players` | `get` |
|
|
50
|
+
|
|
51
|
+
Every method takes an optional last argument `{ signal }` to abort it. Every
|
|
52
|
+
response type is exported by name:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import type { Match, Shot, AdvancedTeams } from "pitchapi";
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Upcoming fixtures
|
|
59
|
+
|
|
60
|
+
Match listings return played matches by default. Pass `status` to reach
|
|
61
|
+
scheduled ones — `"upcoming"` for fixtures that have not kicked off, `"all"`
|
|
62
|
+
for both. An upcoming match carries a kickoff `time_utc` and a `status`, but
|
|
63
|
+
its scores are `null` until it is played.
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
const { matches } = await client.date.get("2026-09-01", { status: "upcoming" });
|
|
67
|
+
|
|
68
|
+
// The season is resolved first, so asking for upcoming fixtures of a finished
|
|
69
|
+
// campaign is an empty list, not next season's.
|
|
70
|
+
await client.leagues.matches("l_0bfbkO", { season: "2025/2026", status: "all" });
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Lineups for a fixture may be a pre-match prediction. `confirmed` is the flag to
|
|
74
|
+
branch on.
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
const lineups = await client.matches.lineups("m_4DP2fy");
|
|
78
|
+
if (lineups.home.confirmed) {
|
|
79
|
+
console.log(lineups.home.formation, lineups.home.starters.map((p) => p.name));
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Errors
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import { NotFoundError, RateLimitError } from "pitchapi";
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
await client.matches.advanced("m_unprocessed");
|
|
90
|
+
} catch (e) {
|
|
91
|
+
if (e instanceof NotFoundError) {
|
|
92
|
+
// RESOURCE_NOT_FOUND, ANALYTICS_UNAVAILABLE or HALVES_UNAVAILABLE
|
|
93
|
+
console.log(e.code, e.requestId);
|
|
94
|
+
} else if (e instanceof RateLimitError) {
|
|
95
|
+
console.log("retry after", e.retryAfter, "s");
|
|
96
|
+
} else {
|
|
97
|
+
throw e;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Every error extends `PitchAPIError` and carries `code`, `status` and
|
|
103
|
+
`requestId` where the API supplied them.
|
|
104
|
+
|
|
105
|
+
## Configuration
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
new PitchAPI({
|
|
109
|
+
apiKey: "pk_live_...",
|
|
110
|
+
baseUrl: "https://api.pitchapi.dev", // override for tests / proxies
|
|
111
|
+
timeoutMs: 30_000, // per attempt
|
|
112
|
+
maxRetries: 2, // 429/5xx + network errors; 0 disables
|
|
113
|
+
fetch: customFetch, // defaults to globalThis.fetch
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## License
|
|
118
|
+
|
|
119
|
+
This client library is released under the MIT License.
|
|
120
|
+
|
|
121
|
+
The licence covers the SDK source only. Access to the PitchAPI service and the
|
|
122
|
+
football data it returns is governed separately by the PitchAPI terms of
|
|
123
|
+
service — an MIT-licensed client does not grant any right to the data.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
APIConnectionError: () => APIConnectionError,
|
|
24
|
+
AuthError: () => AuthError,
|
|
25
|
+
InvalidParameterError: () => InvalidParameterError,
|
|
26
|
+
NotFoundError: () => NotFoundError,
|
|
27
|
+
PitchAPI: () => PitchAPI,
|
|
28
|
+
PitchAPIError: () => PitchAPIError,
|
|
29
|
+
PlanUpgradeRequiredError: () => PlanUpgradeRequiredError,
|
|
30
|
+
RateLimitError: () => RateLimitError,
|
|
31
|
+
ServerError: () => ServerError,
|
|
32
|
+
SubscriptionSuspendedError: () => SubscriptionSuspendedError,
|
|
33
|
+
VERSION: () => VERSION
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(index_exports);
|
|
36
|
+
|
|
37
|
+
// src/errors.ts
|
|
38
|
+
var PitchAPIError = class extends Error {
|
|
39
|
+
constructor(message, options = {}) {
|
|
40
|
+
super(message, options.cause === void 0 ? void 0 : { cause: options.cause });
|
|
41
|
+
this.name = new.target.name;
|
|
42
|
+
this.code = options.code;
|
|
43
|
+
this.status = options.status;
|
|
44
|
+
this.requestId = options.requestId;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
var APIConnectionError = class extends PitchAPIError {
|
|
48
|
+
};
|
|
49
|
+
var InvalidParameterError = class extends PitchAPIError {
|
|
50
|
+
};
|
|
51
|
+
var AuthError = class extends PitchAPIError {
|
|
52
|
+
};
|
|
53
|
+
var SubscriptionSuspendedError = class extends PitchAPIError {
|
|
54
|
+
};
|
|
55
|
+
var PlanUpgradeRequiredError = class extends PitchAPIError {
|
|
56
|
+
};
|
|
57
|
+
var NotFoundError = class extends PitchAPIError {
|
|
58
|
+
};
|
|
59
|
+
var RateLimitError = class extends PitchAPIError {
|
|
60
|
+
constructor(message, options = {}) {
|
|
61
|
+
super(message, options);
|
|
62
|
+
this.retryAfter = options.retryAfter;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
var ServerError = class extends PitchAPIError {
|
|
66
|
+
};
|
|
67
|
+
var CODE_MAP = {
|
|
68
|
+
INVALID_PARAMETER: InvalidParameterError,
|
|
69
|
+
UNAUTHORIZED: AuthError,
|
|
70
|
+
SUBSCRIPTION_SUSPENDED: SubscriptionSuspendedError,
|
|
71
|
+
PLAN_UPGRADE_REQUIRED: PlanUpgradeRequiredError,
|
|
72
|
+
RESOURCE_NOT_FOUND: NotFoundError,
|
|
73
|
+
ANALYTICS_UNAVAILABLE: NotFoundError,
|
|
74
|
+
HALVES_UNAVAILABLE: NotFoundError,
|
|
75
|
+
RATE_LIMIT_EXCEEDED: RateLimitError,
|
|
76
|
+
INTERNAL_SERVER_ERROR: ServerError
|
|
77
|
+
};
|
|
78
|
+
function errorClassFor(code, status) {
|
|
79
|
+
if (code !== void 0 && Object.prototype.hasOwnProperty.call(CODE_MAP, code)) {
|
|
80
|
+
return CODE_MAP[code];
|
|
81
|
+
}
|
|
82
|
+
if (status === 400) return InvalidParameterError;
|
|
83
|
+
if (status === 401) return AuthError;
|
|
84
|
+
if (status === 403) return PlanUpgradeRequiredError;
|
|
85
|
+
if (status === 404) return NotFoundError;
|
|
86
|
+
if (status === 429) return RateLimitError;
|
|
87
|
+
if (status >= 500) return ServerError;
|
|
88
|
+
return PitchAPIError;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/version.ts
|
|
92
|
+
var VERSION = "0.1.1";
|
|
93
|
+
|
|
94
|
+
// src/transport.ts
|
|
95
|
+
var DEFAULT_BASE_URL = "https://api.pitchapi.dev";
|
|
96
|
+
var API_PREFIX = "/v1";
|
|
97
|
+
var RETRY_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
|
|
98
|
+
var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
99
|
+
function isServerRuntime() {
|
|
100
|
+
return typeof globalThis.document === "undefined";
|
|
101
|
+
}
|
|
102
|
+
function buildHeaders(apiKey) {
|
|
103
|
+
const headers = {
|
|
104
|
+
"X-API-KEY": apiKey,
|
|
105
|
+
Accept: "application/json"
|
|
106
|
+
};
|
|
107
|
+
if (isServerRuntime()) headers["User-Agent"] = `pitchapi-js/${VERSION}`;
|
|
108
|
+
return headers;
|
|
109
|
+
}
|
|
110
|
+
function parseRetryAfter(value) {
|
|
111
|
+
if (!value) return void 0;
|
|
112
|
+
const n = Number.parseInt(value, 10);
|
|
113
|
+
return Number.isFinite(n) && n >= 0 ? n : void 0;
|
|
114
|
+
}
|
|
115
|
+
function backoffMs(attempt, retryAfter) {
|
|
116
|
+
if (retryAfter !== void 0) return retryAfter * 1e3;
|
|
117
|
+
const base = Math.min(2 ** attempt, 8);
|
|
118
|
+
return Math.random() * base * 1e3;
|
|
119
|
+
}
|
|
120
|
+
function buildQuery(params) {
|
|
121
|
+
if (!params) return "";
|
|
122
|
+
const qs = new URLSearchParams();
|
|
123
|
+
for (const [k, v] of Object.entries(params)) {
|
|
124
|
+
if (v !== void 0) qs.set(k, v);
|
|
125
|
+
}
|
|
126
|
+
const s = qs.toString();
|
|
127
|
+
return s ? `?${s}` : "";
|
|
128
|
+
}
|
|
129
|
+
function unwrap(status, payload, requestId, retryAfter) {
|
|
130
|
+
if (status >= 200 && status < 300) {
|
|
131
|
+
if (payload !== null && typeof payload === "object" && "data" in payload) {
|
|
132
|
+
return payload.data;
|
|
133
|
+
}
|
|
134
|
+
throw new PitchAPIError("malformed success response: missing 'data' envelope", { status, requestId });
|
|
135
|
+
}
|
|
136
|
+
let code;
|
|
137
|
+
let message = `HTTP ${status}`;
|
|
138
|
+
if (payload !== null && typeof payload === "object" && "error" in payload) {
|
|
139
|
+
const err = payload.error;
|
|
140
|
+
if (err !== null && typeof err === "object") {
|
|
141
|
+
const e = err;
|
|
142
|
+
if (typeof e.code === "string") code = e.code;
|
|
143
|
+
if (typeof e.message === "string") message = e.message;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const Cls = errorClassFor(code, status);
|
|
147
|
+
if (Cls === RateLimitError) {
|
|
148
|
+
throw new RateLimitError(message, { code, status, requestId, retryAfter });
|
|
149
|
+
}
|
|
150
|
+
if (Cls === ServerError && code === void 0) message = `server error (HTTP ${status})`;
|
|
151
|
+
throw new Cls(message, { code, status, requestId });
|
|
152
|
+
}
|
|
153
|
+
var Transport = class {
|
|
154
|
+
constructor(opts) {
|
|
155
|
+
this.opts = opts;
|
|
156
|
+
this.headers = buildHeaders(opts.apiKey);
|
|
157
|
+
this.sleep = opts.sleep ?? defaultSleep;
|
|
158
|
+
}
|
|
159
|
+
async get(path, query, signal) {
|
|
160
|
+
const url = this.opts.baseUrl + API_PREFIX + path + buildQuery(query);
|
|
161
|
+
let attempt = 0;
|
|
162
|
+
for (; ; ) {
|
|
163
|
+
let response;
|
|
164
|
+
try {
|
|
165
|
+
response = await this.fetchOnce(url, signal);
|
|
166
|
+
} catch (err) {
|
|
167
|
+
if (signal?.aborted) throw err;
|
|
168
|
+
if (attempt < this.opts.maxRetries) {
|
|
169
|
+
await this.sleep(backoffMs(attempt, void 0));
|
|
170
|
+
attempt++;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
throw new APIConnectionError(`request failed: ${describe(err)}`, { cause: err });
|
|
174
|
+
}
|
|
175
|
+
const status = response.status;
|
|
176
|
+
const retryAfter = parseRetryAfter(response.headers.get("Retry-After"));
|
|
177
|
+
if (attempt < this.opts.maxRetries && RETRY_STATUSES.has(status)) {
|
|
178
|
+
await response.body?.cancel().catch(() => void 0);
|
|
179
|
+
await this.sleep(backoffMs(attempt, retryAfter));
|
|
180
|
+
attempt++;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
const requestId = response.headers.get("X-Request-ID") ?? void 0;
|
|
184
|
+
let payload = null;
|
|
185
|
+
try {
|
|
186
|
+
payload = await response.json();
|
|
187
|
+
} catch {
|
|
188
|
+
}
|
|
189
|
+
return unwrap(status, payload, requestId, retryAfter);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
async fetchOnce(url, signal) {
|
|
193
|
+
const timeout = AbortSignal.timeout(this.opts.timeoutMs);
|
|
194
|
+
const combined = signal ? anySignal([signal, timeout]) : timeout;
|
|
195
|
+
return this.opts.fetch(url, { method: "GET", headers: this.headers, signal: combined });
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
function anySignal(signals) {
|
|
199
|
+
const any = AbortSignal.any;
|
|
200
|
+
if (any) return any(signals);
|
|
201
|
+
const controller = new AbortController();
|
|
202
|
+
for (const s of signals) {
|
|
203
|
+
if (s.aborted) {
|
|
204
|
+
controller.abort(s.reason);
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
s.addEventListener("abort", () => controller.abort(s.reason), { once: true });
|
|
208
|
+
}
|
|
209
|
+
return controller.signal;
|
|
210
|
+
}
|
|
211
|
+
function describe(err) {
|
|
212
|
+
return err instanceof Error ? err.message : String(err);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// src/client.ts
|
|
216
|
+
function env(name) {
|
|
217
|
+
const proc = globalThis.process;
|
|
218
|
+
return proc?.env?.[name] || void 0;
|
|
219
|
+
}
|
|
220
|
+
function formatDate(value) {
|
|
221
|
+
return typeof value === "string" ? value : value.toISOString().slice(0, 10);
|
|
222
|
+
}
|
|
223
|
+
var seg = encodeURIComponent;
|
|
224
|
+
var Resource = class {
|
|
225
|
+
constructor(t) {
|
|
226
|
+
this.t = t;
|
|
227
|
+
}
|
|
228
|
+
request(path, opts, query) {
|
|
229
|
+
return this.t.get(path, query, opts?.signal);
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
var DateResource = class extends Resource {
|
|
233
|
+
/**
|
|
234
|
+
* Every match on a day. `date` is a `YYYY-MM-DD` string or a `Date`, which
|
|
235
|
+
* is read in UTC. An upcoming fixture carries a kickoff `time_utc` and a
|
|
236
|
+
* `status` but no score yet.
|
|
237
|
+
*/
|
|
238
|
+
get(date, opts) {
|
|
239
|
+
return this.request(`/date/${seg(formatDate(date))}`, opts, { status: opts?.status });
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
var MatchesResource = class extends Resource {
|
|
243
|
+
/** Match summary — league, teams, score, referee, stadium. */
|
|
244
|
+
get(matchId, opts) {
|
|
245
|
+
return this.request(`/matches/${seg(matchId)}`, opts);
|
|
246
|
+
}
|
|
247
|
+
/** Every shot, grouped by period, with pitch coordinates and xG. */
|
|
248
|
+
shots(matchId, opts) {
|
|
249
|
+
return this.request(`/matches/${seg(matchId)}/shots`, opts);
|
|
250
|
+
}
|
|
251
|
+
/** A single shot by its opaque ID. */
|
|
252
|
+
shot(matchId, shotId, opts) {
|
|
253
|
+
return this.request(`/matches/${seg(matchId)}/shots/${seg(shotId)}`, opts);
|
|
254
|
+
}
|
|
255
|
+
/** Match events (goals, cards, subs, ...). */
|
|
256
|
+
events(matchId, opts) {
|
|
257
|
+
return this.request(`/matches/${seg(matchId)}/events`, opts);
|
|
258
|
+
}
|
|
259
|
+
/** Starting lineups and substitutes. Before kickoff the XI may be predicted. */
|
|
260
|
+
lineups(matchId, opts) {
|
|
261
|
+
return this.request(`/matches/${seg(matchId)}/lineups`, opts);
|
|
262
|
+
}
|
|
263
|
+
/** Possession-momentum timeline. */
|
|
264
|
+
momentum(matchId, opts) {
|
|
265
|
+
return this.request(`/matches/${seg(matchId)}/momentum`, opts);
|
|
266
|
+
}
|
|
267
|
+
/** Team-level match statistics. */
|
|
268
|
+
stats(matchId, opts) {
|
|
269
|
+
return this.request(`/matches/${seg(matchId)}/stats`, opts);
|
|
270
|
+
}
|
|
271
|
+
/** Per-player match stat lines. */
|
|
272
|
+
players(matchId, opts) {
|
|
273
|
+
return this.request(`/matches/${seg(matchId)}/players`, opts);
|
|
274
|
+
}
|
|
275
|
+
/** One player's match stat line. */
|
|
276
|
+
player(matchId, playerId, opts) {
|
|
277
|
+
return this.request(`/matches/${seg(matchId)}/players/${seg(playerId)}`, opts);
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Every player's stat line for the first and the second half. Throws
|
|
281
|
+
* `NotFoundError` (code `HALVES_UNAVAILABLE`) for a match without them.
|
|
282
|
+
*/
|
|
283
|
+
playersHalves(matchId, opts) {
|
|
284
|
+
return this.request(`/matches/${seg(matchId)}/players/halves`, opts);
|
|
285
|
+
}
|
|
286
|
+
/** One player's stat line for each half they played. */
|
|
287
|
+
playerHalves(matchId, playerId, opts) {
|
|
288
|
+
return this.request(`/matches/${seg(matchId)}/players/${seg(playerId)}/halves`, opts);
|
|
289
|
+
}
|
|
290
|
+
/** A single player's shots in the match. */
|
|
291
|
+
playerShots(matchId, playerId, opts) {
|
|
292
|
+
return this.request(`/matches/${seg(matchId)}/players/${seg(playerId)}/shots`, opts);
|
|
293
|
+
}
|
|
294
|
+
/** Head-to-head history for the two sides. */
|
|
295
|
+
h2h(matchId, opts) {
|
|
296
|
+
return this.request(`/matches/${seg(matchId)}/h2h`, opts);
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Team advanced rollups (PPDA, field tilt, xG timeline). Throws
|
|
300
|
+
* `NotFoundError` (code `ANALYTICS_UNAVAILABLE`) for a match not yet rated.
|
|
301
|
+
*/
|
|
302
|
+
advanced(matchId, opts) {
|
|
303
|
+
return this.request(`/matches/${seg(matchId)}/advanced`, opts);
|
|
304
|
+
}
|
|
305
|
+
/** Pass networks for both sides. */
|
|
306
|
+
advancedNetwork(matchId, opts) {
|
|
307
|
+
return this.request(`/matches/${seg(matchId)}/advanced/network`, opts);
|
|
308
|
+
}
|
|
309
|
+
/** Player-level advanced rollups. */
|
|
310
|
+
advancedPlayers(matchId, opts) {
|
|
311
|
+
return this.request(`/matches/${seg(matchId)}/advanced/players`, opts);
|
|
312
|
+
}
|
|
313
|
+
/** One player's advanced rollup. */
|
|
314
|
+
advancedPlayer(matchId, playerId, opts) {
|
|
315
|
+
return this.request(`/matches/${seg(matchId)}/advanced/players/${seg(playerId)}`, opts);
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Binned activity for both teams and every player who touched the ball.
|
|
319
|
+
* Cells are sparse `[cell_x, cell_y, actions]` triples on a 16x12 grid.
|
|
320
|
+
*/
|
|
321
|
+
heatmaps(matchId, opts) {
|
|
322
|
+
return this.request(`/matches/${seg(matchId)}/heatmaps`, opts, { frame: opts?.frame });
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
var LeaguesResource = class extends Resource {
|
|
326
|
+
/** All leagues, each with its accepted seasons and coverage. */
|
|
327
|
+
list(opts) {
|
|
328
|
+
return this.request("/leagues", opts);
|
|
329
|
+
}
|
|
330
|
+
/** League detail. */
|
|
331
|
+
get(leagueId, opts) {
|
|
332
|
+
return this.request(`/leagues/${seg(leagueId)}`, opts);
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Matches in a league. `season` is `"2025/2026"` for a fall-spring league
|
|
336
|
+
* and `"2026"` for a calendar-year one; omitted, the latest season with
|
|
337
|
+
* data. The season is resolved before `status` applies, so `upcoming` on a
|
|
338
|
+
* finished season is an empty list.
|
|
339
|
+
*/
|
|
340
|
+
matches(leagueId, opts) {
|
|
341
|
+
return this.request(`/leagues/${seg(leagueId)}/matches`, opts, {
|
|
342
|
+
season: opts?.season,
|
|
343
|
+
status: opts?.status
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
var TeamsResource = class extends Resource {
|
|
348
|
+
/** Team profile. */
|
|
349
|
+
get(teamId, opts) {
|
|
350
|
+
return this.request(`/teams/${seg(teamId)}`, opts);
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
var PlayersResource = class extends Resource {
|
|
354
|
+
/** Player profile. */
|
|
355
|
+
get(playerId, opts) {
|
|
356
|
+
return this.request(`/players/${seg(playerId)}`, opts);
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
var PitchAPI = class {
|
|
360
|
+
constructor(options = {}) {
|
|
361
|
+
const apiKey = options.apiKey ?? env("PITCHAPI_API_KEY");
|
|
362
|
+
if (!apiKey) {
|
|
363
|
+
throw new Error("an API key is required: pass { apiKey } or set the PITCHAPI_API_KEY environment variable");
|
|
364
|
+
}
|
|
365
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
366
|
+
if (!fetchImpl) {
|
|
367
|
+
throw new Error("no global fetch: use Node 18+ or pass { fetch }");
|
|
368
|
+
}
|
|
369
|
+
const transport = new Transport({
|
|
370
|
+
apiKey,
|
|
371
|
+
baseUrl: (options.baseUrl ?? env("PITCHAPI_BASE_URL") ?? DEFAULT_BASE_URL).replace(/\/+$/, ""),
|
|
372
|
+
timeoutMs: options.timeoutMs ?? 3e4,
|
|
373
|
+
maxRetries: options.maxRetries ?? 2,
|
|
374
|
+
// Bound to globalThis: some runtimes throw on an unbound fetch.
|
|
375
|
+
fetch: options.fetch ?? fetchImpl.bind(globalThis)
|
|
376
|
+
});
|
|
377
|
+
this.date = new DateResource(transport);
|
|
378
|
+
this.matches = new MatchesResource(transport);
|
|
379
|
+
this.leagues = new LeaguesResource(transport);
|
|
380
|
+
this.teams = new TeamsResource(transport);
|
|
381
|
+
this.players = new PlayersResource(transport);
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
385
|
+
0 && (module.exports = {
|
|
386
|
+
APIConnectionError,
|
|
387
|
+
AuthError,
|
|
388
|
+
InvalidParameterError,
|
|
389
|
+
NotFoundError,
|
|
390
|
+
PitchAPI,
|
|
391
|
+
PitchAPIError,
|
|
392
|
+
PlanUpgradeRequiredError,
|
|
393
|
+
RateLimitError,
|
|
394
|
+
ServerError,
|
|
395
|
+
SubscriptionSuspendedError,
|
|
396
|
+
VERSION
|
|
397
|
+
});
|