erlc-v2 1.0.1 → 1.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/README.md +272 -51
- package/index.d.ts +432 -292
- package/package.json +5 -2
- package/src/Client.js +467 -399
- package/src/api/LocalApiServer.js +535 -0
- package/src/events/Poller.js +14 -0
- package/src/events/diff.js +10 -1
- package/src/util/constants.js +11 -0
- package/src/util/errors.js +1 -1
- package/src/util/normalize.js +1 -0
- package/src/util/vehicleSearch.js +120 -0
- package/src/util/webhook.js +204 -0
package/README.md
CHANGED
|
@@ -4,15 +4,23 @@ JavaScript client for the ER:LC API v2.
|
|
|
4
4
|
|
|
5
5
|
Built for Node 18+.
|
|
6
6
|
|
|
7
|
-
##
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
## New Features
|
|
8
|
+
|
|
9
|
+
- `client.commands.execute()` now uses `/v2/server/command`
|
|
10
|
+
- emergency calls are supported
|
|
11
|
+
- vehicle lookup helpers are built in
|
|
12
|
+
- you can start a small local API with `api: { port: 3001 }`
|
|
13
|
+
- event webhooks are supported and signature-checked before they fire events
|
|
14
|
+
|
|
15
|
+
## Stable Release
|
|
16
|
+
|
|
17
|
+
This wrapper is on a stable release track (`1.0.0+`).
|
|
10
18
|
|
|
11
19
|
## Responsibility and API Safety
|
|
12
20
|
|
|
13
|
-
|
|
21
|
+
Use your keys like a normal person. If you spam requests, ignore rate limits, or build dumb abuse tools and PRC or Cloudflare blocks you, that is on you.
|
|
14
22
|
|
|
15
|
-
This project is provided as-is.
|
|
23
|
+
This project is provided as-is. Keep an eye on your own integration and follow the PRC API rules.
|
|
16
24
|
|
|
17
25
|
## Install
|
|
18
26
|
|
|
@@ -27,7 +35,9 @@ const { Client } = require("erlc-v2");
|
|
|
27
35
|
|
|
28
36
|
const client = new Client({
|
|
29
37
|
serverKey: "YOUR_SERVER_KEY",
|
|
30
|
-
|
|
38
|
+
polling: {
|
|
39
|
+
enabled: true,
|
|
40
|
+
},
|
|
31
41
|
});
|
|
32
42
|
|
|
33
43
|
client.on("disconnect", ({ reason, error }) => {
|
|
@@ -39,11 +49,14 @@ async function main() {
|
|
|
39
49
|
players: true,
|
|
40
50
|
staff: true,
|
|
41
51
|
queue: true,
|
|
52
|
+
vehicles: true,
|
|
53
|
+
emergencyCalls: true,
|
|
42
54
|
});
|
|
43
55
|
|
|
44
56
|
console.log("Server:", snapshot.name);
|
|
45
57
|
console.log("Players:", `${snapshot.currentPlayers}/${snapshot.maxPlayers}`);
|
|
46
|
-
console.log("
|
|
58
|
+
console.log("Vehicles:", snapshot.vehicles.length);
|
|
59
|
+
console.log("Emergency calls:", snapshot.emergencyCalls.length);
|
|
47
60
|
}
|
|
48
61
|
|
|
49
62
|
main()
|
|
@@ -73,23 +86,33 @@ new Client({
|
|
|
73
86
|
enabled?: boolean, // default: true
|
|
74
87
|
ttlMs?: number, // default: 1500
|
|
75
88
|
maxSize?: number, // default: 500
|
|
76
|
-
provider?: "memory" | "redis", // default: auto
|
|
77
|
-
redisUrl?: string,
|
|
89
|
+
provider?: "memory" | "redis", // default: auto
|
|
90
|
+
redisUrl?: string,
|
|
78
91
|
redisPrefix?: string, // default: "erlc-v2:cache"
|
|
79
|
-
redisClient?: object,
|
|
92
|
+
redisClient?: object,
|
|
80
93
|
},
|
|
81
94
|
rateLimit?: {
|
|
82
95
|
enabled?: boolean, // default: true
|
|
83
|
-
strictSerial?: boolean, // default: true
|
|
96
|
+
strictSerial?: boolean, // default: true
|
|
84
97
|
bucketLimit?: number, // default: 1
|
|
85
98
|
totalLimit?: number, // default: 1
|
|
86
99
|
unauthLimit?: number, // default: 3
|
|
87
100
|
},
|
|
88
101
|
polling?: {
|
|
89
102
|
enabled?: boolean, // default: true
|
|
90
|
-
intervalMs?: number, // default: 2500
|
|
103
|
+
intervalMs?: number, // default: 2500
|
|
91
104
|
bypassCache?: boolean, // default: true
|
|
92
105
|
},
|
|
106
|
+
api?: {
|
|
107
|
+
enabled?: boolean, // default: false unless port is set
|
|
108
|
+
host?: string, // default: "127.0.0.1"
|
|
109
|
+
port?: number, // required if you want the local API server
|
|
110
|
+
path?: string, // default: "/erlc"
|
|
111
|
+
webhookPath?: string, // default: `${path}/events`
|
|
112
|
+
publicUrl?: string, // optional, used to build webhookUrl in client.api.info()
|
|
113
|
+
token?: string, // optional bearer token for built-in routes
|
|
114
|
+
logRequests?: boolean, // default: true, logs route/webhook hits to the console
|
|
115
|
+
},
|
|
93
116
|
});
|
|
94
117
|
```
|
|
95
118
|
|
|
@@ -146,6 +169,7 @@ const { Client } = require("erlc-v2");
|
|
|
146
169
|
Core:
|
|
147
170
|
|
|
148
171
|
- `await client.server.fetch(flags, requestOptions?)`
|
|
172
|
+
- `await client.commands.execute(command)`
|
|
149
173
|
- `client.destroy()`
|
|
150
174
|
- `client.cache.clear()`
|
|
151
175
|
|
|
@@ -159,9 +183,13 @@ Convenience methods:
|
|
|
159
183
|
- `await client.logs.joins()`
|
|
160
184
|
- `await client.logs.commands()`
|
|
161
185
|
- `await client.logs.modCalls()`
|
|
186
|
+
- `await client.logs.emergencyCalls()`
|
|
162
187
|
- `await client.vehicles.list()`
|
|
188
|
+
- `await client.vehicles.search(filters)`
|
|
189
|
+
- `await client.vehicles.findByPlate(plate)`
|
|
190
|
+
- `await client.vehicles.findByOwner(owner)`
|
|
191
|
+
- `await client.vehicles.findOne(filters)`
|
|
163
192
|
- `await client.queue.get()`
|
|
164
|
-
- `await client.commands.execute(":h Hey everyone!")` (v1 endpoint)
|
|
165
193
|
|
|
166
194
|
### Fetch Flags
|
|
167
195
|
|
|
@@ -172,6 +200,7 @@ Convenience methods:
|
|
|
172
200
|
- `killLogs` -> `KillLogs`
|
|
173
201
|
- `commandLogs` -> `CommandLogs`
|
|
174
202
|
- `modCalls` -> `ModCalls`
|
|
203
|
+
- `emergencyCalls` -> `EmergencyCalls`
|
|
175
204
|
- `vehicles` -> `Vehicles`
|
|
176
205
|
|
|
177
206
|
### Request Options
|
|
@@ -180,12 +209,63 @@ Convenience methods:
|
|
|
180
209
|
- `cacheTtlMs?: number`
|
|
181
210
|
- `dedupe?: boolean`
|
|
182
211
|
|
|
183
|
-
|
|
212
|
+
## Vehicle Search Helpers
|
|
213
|
+
|
|
214
|
+
Find one exact plate:
|
|
215
|
+
|
|
216
|
+
```js
|
|
217
|
+
const car = await client.vehicles.findByPlate("LINCOLN7");
|
|
218
|
+
|
|
219
|
+
if (car) {
|
|
220
|
+
console.log(car.Owner, car.Name, car.Plate);
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Search across plate, owner, name, color, and texture:
|
|
225
|
+
|
|
226
|
+
```js
|
|
227
|
+
const matches = await client.vehicles.search({
|
|
228
|
+
query: "lincoln",
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const ownerCars = await client.vehicles.findByOwner("lando");
|
|
232
|
+
|
|
233
|
+
const blackTahoes = await client.vehicles.search({
|
|
234
|
+
name: "tahoe",
|
|
235
|
+
color: "black",
|
|
236
|
+
});
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Exact matching is supported too:
|
|
184
240
|
|
|
185
|
-
|
|
241
|
+
```js
|
|
242
|
+
const exact = await client.vehicles.findOne({
|
|
243
|
+
plate: "A12BCD",
|
|
244
|
+
owner: "SomePlayer",
|
|
245
|
+
exact: true,
|
|
246
|
+
});
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Emergency Calls
|
|
250
|
+
|
|
251
|
+
```js
|
|
252
|
+
const calls = await client.logs.emergencyCalls();
|
|
253
|
+
|
|
254
|
+
for (const call of calls) {
|
|
255
|
+
console.log(call.CallNumber, call.Team, call.Description);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
client.on("emergencyCall", ({ emergencyCall }) => {
|
|
259
|
+
console.log("New emergency call:", emergencyCall.Description);
|
|
260
|
+
});
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Command Execution
|
|
264
|
+
|
|
265
|
+
`client.commands.execute(command)` sends a POST request to `/v2/server/command`.
|
|
186
266
|
Command execution is FIFO-queued client-side, so commands run one-at-a-time in order.
|
|
187
267
|
|
|
188
|
-
Blocked by client policy
|
|
268
|
+
Blocked by client policy:
|
|
189
269
|
|
|
190
270
|
- `:view`
|
|
191
271
|
- `:to`
|
|
@@ -205,10 +285,156 @@ Blocked by client policy (request will be rejected before hitting the API):
|
|
|
205
285
|
Example:
|
|
206
286
|
|
|
207
287
|
```js
|
|
208
|
-
await client.commands.execute(":h Hey everyone!");
|
|
209
|
-
|
|
288
|
+
const result = await client.commands.execute(":h Hey everyone!");
|
|
289
|
+
console.log(result.message);
|
|
210
290
|
```
|
|
211
291
|
|
|
292
|
+
## Built-in Local API Server
|
|
293
|
+
|
|
294
|
+
If you want the wrapper to expose a small HTTP server, it can do that too.
|
|
295
|
+
|
|
296
|
+
```js
|
|
297
|
+
const client = new Client({
|
|
298
|
+
serverKey: process.env.ERLC_SERVER_KEY,
|
|
299
|
+
api: {
|
|
300
|
+
port: 3001,
|
|
301
|
+
host: "127.0.0.1",
|
|
302
|
+
path: "/erlc",
|
|
303
|
+
publicUrl: "https://hooks.example.com",
|
|
304
|
+
token: process.env.ERLC_LOCAL_API_TOKEN,
|
|
305
|
+
},
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
client.api.info();
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
If `api.port` is set, the local API auto-starts with the client. You can also call `await client.api.start()` yourself.
|
|
312
|
+
|
|
313
|
+
By default it logs incoming route hits and verified webhook payloads to the console.
|
|
314
|
+
|
|
315
|
+
If you want to react to ER:LC webhooks in your own code, use:
|
|
316
|
+
|
|
317
|
+
- `client.onWebhook(...)`
|
|
318
|
+
- `client.onWebhookEmergencyCall(...)`
|
|
319
|
+
|
|
320
|
+
Those only fire after the webhook signature checks out.
|
|
321
|
+
|
|
322
|
+
Built-in routes:
|
|
323
|
+
|
|
324
|
+
- `GET /erlc`
|
|
325
|
+
- `GET /erlc/health`
|
|
326
|
+
- `GET /erlc/server`
|
|
327
|
+
- `GET /erlc/players`
|
|
328
|
+
- `GET /erlc/vehicles`
|
|
329
|
+
- `GET /erlc/vehicles/:plate`
|
|
330
|
+
- `GET /erlc/emergency-calls`
|
|
331
|
+
- `POST /erlc/command`
|
|
332
|
+
- `POST /erlc/events`
|
|
333
|
+
|
|
334
|
+
`/erlc/vehicles` accepts query params like `search`, `plate`, `owner`, `name`, `color`, `texture`, `exact`, and `limit`.
|
|
335
|
+
|
|
336
|
+
If you set `api.token`, send either:
|
|
337
|
+
|
|
338
|
+
- `Authorization: Bearer YOUR_TOKEN`
|
|
339
|
+
- `X-API-Token: YOUR_TOKEN`
|
|
340
|
+
|
|
341
|
+
## Event Webhook Support
|
|
342
|
+
|
|
343
|
+
The built-in API can take ER:LC event webhooks and verify the signatures for you.
|
|
344
|
+
|
|
345
|
+
```js
|
|
346
|
+
const client = new Client({
|
|
347
|
+
serverKey: process.env.ERLC_SERVER_KEY,
|
|
348
|
+
api: {
|
|
349
|
+
port: 3001,
|
|
350
|
+
publicUrl: "https://hooks.example.com",
|
|
351
|
+
},
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
client.onWebhook((payload) => {
|
|
355
|
+
console.log("Webhook type:", payload.type);
|
|
356
|
+
console.log("Event name:", payload.event);
|
|
357
|
+
console.log("Command:", payload.command);
|
|
358
|
+
console.log("Args:", payload.args);
|
|
359
|
+
console.log("Origin:", payload.origin);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
client.onWebhookEmergencyCall((payload) => {
|
|
363
|
+
console.log("Event:", payload.event);
|
|
364
|
+
console.log("Origin:", payload.origin);
|
|
365
|
+
console.log("Data:", payload.data);
|
|
366
|
+
// react to emergency calls here
|
|
367
|
+
});
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
Useful flattened webhook fields:
|
|
371
|
+
|
|
372
|
+
- `payload.type`
|
|
373
|
+
- `payload.event`
|
|
374
|
+
- `payload.origin`
|
|
375
|
+
- `payload.server`
|
|
376
|
+
- `payload.eventTimestamp`
|
|
377
|
+
- `payload.data`
|
|
378
|
+
- `payload.command`
|
|
379
|
+
- `payload.args`
|
|
380
|
+
- `payload.argument`
|
|
381
|
+
- `payload.entry`
|
|
382
|
+
- `payload.events`
|
|
383
|
+
|
|
384
|
+
This webhook is for custom `;` commands and emergency calls. It is not for normal `:` commands from the command endpoint.
|
|
385
|
+
|
|
386
|
+
For custom `;` commands, `payload.command`, `payload.args`, and `payload.origin` are usually the fields you want.
|
|
387
|
+
|
|
388
|
+
If your public URL is `https://hooks.example.com` and your API path is the default, set this in your ER:LC server settings:
|
|
389
|
+
|
|
390
|
+
```txt
|
|
391
|
+
https://hooks.example.com/erlc/events
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
If you want the longer request shape with type information, use:
|
|
395
|
+
|
|
396
|
+
```txt
|
|
397
|
+
https://hooks.example.com/erlc/events?long=true
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
## Domain, Hosting, and Reverse Proxy Notes
|
|
401
|
+
|
|
402
|
+
The event webhook has to hit a public HTTPS URL. A local port by itself is not enough.
|
|
403
|
+
|
|
404
|
+
Important:
|
|
405
|
+
|
|
406
|
+
- Most Discord bot hosts are bad for this because they do not let you expose your own API cleanly.
|
|
407
|
+
- If your host does not allow inbound HTTP traffic, PRC will never reach your webhook.
|
|
408
|
+
- You need something public in front of your wrapper.
|
|
409
|
+
|
|
410
|
+
Common setups:
|
|
411
|
+
|
|
412
|
+
- Buy a domain and point it at a VPS.
|
|
413
|
+
- Run the wrapper on a VPS and put NGINX or Caddy in front of it.
|
|
414
|
+
- Run it somewhere private and use Cloudflare Tunnel.
|
|
415
|
+
|
|
416
|
+
Common places people use for domains:
|
|
417
|
+
|
|
418
|
+
- Cloudflare Registrar: `https://www.cloudflare.com/products/registrar/`
|
|
419
|
+
- Namecheap: `https://www.namecheap.com/`
|
|
420
|
+
- Porkbun: `https://porkbun.com/`
|
|
421
|
+
|
|
422
|
+
Common places people use for public hosting or a VPS:
|
|
423
|
+
|
|
424
|
+
- DigitalOcean: `https://www.digitalocean.com/`
|
|
425
|
+
- Hetzner: `https://www.hetzner.com/`
|
|
426
|
+
- Railway: `https://railway.com/`
|
|
427
|
+
- Render: `https://render.com/`
|
|
428
|
+
- Fly.io: `https://fly.io/`
|
|
429
|
+
|
|
430
|
+
Common reverse proxy or edge options:
|
|
431
|
+
|
|
432
|
+
- NGINX: `https://nginx.org/`
|
|
433
|
+
- Caddy: `https://caddyserver.com/`
|
|
434
|
+
- Cloudflare Tunnel: `https://www.cloudflare.com/products/tunnel/`
|
|
435
|
+
|
|
436
|
+
Those are just examples. Use whatever actually gives you inbound HTTPS and a process you control.
|
|
437
|
+
|
|
212
438
|
## Map Rendering
|
|
213
439
|
|
|
214
440
|
Render an ER:LC map (`3121x3121`) with player markers that use Roblox avatars.
|
|
@@ -216,7 +442,6 @@ Render an ER:LC map (`3121x3121`) with player markers that use Roblox avatars.
|
|
|
216
442
|
```js
|
|
217
443
|
const result = await client.map.render();
|
|
218
444
|
|
|
219
|
-
// png buffer
|
|
220
445
|
console.log(result.buffer);
|
|
221
446
|
console.log(result.players.length);
|
|
222
447
|
```
|
|
@@ -231,16 +456,6 @@ const fallBlank = await client.map.render({
|
|
|
231
456
|
type: "blank",
|
|
232
457
|
});
|
|
233
458
|
|
|
234
|
-
const fallPostals = await client.map.render({
|
|
235
|
-
season: "fall",
|
|
236
|
-
type: "postals",
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
const winterBlank = await client.map.render({
|
|
240
|
-
season: "winter", // alias: "snow"
|
|
241
|
-
type: "blank",
|
|
242
|
-
});
|
|
243
|
-
|
|
244
459
|
const winterPostals = await client.map.render({
|
|
245
460
|
season: "winter",
|
|
246
461
|
type: "postals",
|
|
@@ -251,7 +466,7 @@ Use your own map image URL:
|
|
|
251
466
|
|
|
252
467
|
```js
|
|
253
468
|
const customMap = await client.map.render({
|
|
254
|
-
mapUrl: "https://example.com/my-map.png",
|
|
469
|
+
mapUrl: "https://example.com/my-map.png",
|
|
255
470
|
});
|
|
256
471
|
```
|
|
257
472
|
|
|
@@ -268,15 +483,15 @@ Options:
|
|
|
268
483
|
|
|
269
484
|
- `userId?: number | string`
|
|
270
485
|
- `userIds?: Array<number | string>`
|
|
271
|
-
- `players?: any[]`
|
|
272
|
-
- `mapUrl?: string`
|
|
273
|
-
- `season?: string`
|
|
274
|
-
- `type?: string`
|
|
275
|
-
- `mapSeason?: string`
|
|
276
|
-
- `mapType?: string`
|
|
486
|
+
- `players?: any[]`
|
|
487
|
+
- `mapUrl?: string`
|
|
488
|
+
- `season?: string`
|
|
489
|
+
- `type?: string`
|
|
490
|
+
- `mapSeason?: string`
|
|
491
|
+
- `mapType?: string`
|
|
277
492
|
- `coordinateBounds?: { minX, maxX, minY, maxY, invertY? }`
|
|
278
|
-
- `clampToMap?: boolean`
|
|
279
|
-
- `robloxHeadshotSize?: string`
|
|
493
|
+
- `clampToMap?: boolean`
|
|
494
|
+
- `robloxHeadshotSize?: string`
|
|
280
495
|
- `marker?: { outerRadius, innerRadius, tipLength, tipWidth, fillColor, shadow }`
|
|
281
496
|
|
|
282
497
|
Map size is fixed to `3121x3121`.
|
|
@@ -285,10 +500,10 @@ Result shape:
|
|
|
285
500
|
|
|
286
501
|
- `buffer` (`image/png`)
|
|
287
502
|
- `map` (`{ url, season, type, width, height }`)
|
|
288
|
-
- `players`
|
|
289
|
-
- `skipped`
|
|
290
|
-
- `requestedUserIds`
|
|
291
|
-
- `unmatchedUserIds`
|
|
503
|
+
- `players`
|
|
504
|
+
- `skipped`
|
|
505
|
+
- `requestedUserIds`
|
|
506
|
+
- `unmatchedUserIds`
|
|
292
507
|
|
|
293
508
|
## Events
|
|
294
509
|
|
|
@@ -301,11 +516,14 @@ Result shape:
|
|
|
301
516
|
- `queueUpdate`
|
|
302
517
|
- `staffUpdate`
|
|
303
518
|
- `modCall`
|
|
519
|
+
- `emergencyCall`
|
|
304
520
|
- `commandLog`
|
|
305
|
-
- `logCommand`
|
|
306
|
-
- `serverUpdate`
|
|
521
|
+
- `logCommand`
|
|
522
|
+
- `serverUpdate`
|
|
523
|
+
- `webhook`
|
|
524
|
+
- `webhookEmergencyCall`
|
|
307
525
|
- `error`
|
|
308
|
-
- `disconnect`
|
|
526
|
+
- `disconnect`
|
|
309
527
|
|
|
310
528
|
Alias event names are also supported with `client.on(...)`:
|
|
311
529
|
|
|
@@ -318,9 +536,13 @@ Alias event names are also supported with `client.on(...)`:
|
|
|
318
536
|
- `onQueueUpdate`
|
|
319
537
|
- `onStaffUpdate`
|
|
320
538
|
- `onModCall`
|
|
539
|
+
- `onEmergencyCall`
|
|
321
540
|
- `onCommandLog`
|
|
322
541
|
- `onLogCommand`
|
|
323
|
-
- `onServerUpdate`
|
|
542
|
+
- `onServerUpdate`
|
|
543
|
+
- `onApiRequest`
|
|
544
|
+
- `onWebhook`
|
|
545
|
+
- `onWebhookEmergencyCall`
|
|
324
546
|
- `onError`
|
|
325
547
|
- `onDisconnect`
|
|
326
548
|
|
|
@@ -350,10 +572,9 @@ Requests are automatically bucketed using API response headers:
|
|
|
350
572
|
- `X-RateLimit-Remaining`
|
|
351
573
|
- `X-RateLimit-Reset`
|
|
352
574
|
|
|
353
|
-
On `429`, the client blocks the affected bucket until retry time
|
|
575
|
+
On `429`, the client blocks the affected bucket until retry time or reset.
|
|
354
576
|
|
|
355
|
-
By default, requests are
|
|
356
|
-
This is intentionally conservative for anti-abuse/rate-limit safety.
|
|
577
|
+
By default, requests are serialized (`strictSerial: true`) so this client does not spray parallel requests at the API.
|
|
357
578
|
|
|
358
579
|
## Errors
|
|
359
580
|
|
|
@@ -372,10 +593,10 @@ The client normalizes errors into classes:
|
|
|
372
593
|
|
|
373
594
|
Terminal key errors (`2002`, `2004`) trigger disconnect and stop polling.
|
|
374
595
|
|
|
375
|
-
Repeated `403` responses can also trigger disconnect (reason:
|
|
596
|
+
Repeated `403` responses can also trigger disconnect (`reason: "unauthorized"`).
|
|
376
597
|
|
|
377
598
|
## Notes
|
|
378
599
|
|
|
379
600
|
- API base URL: `https://api.policeroleplay.community`
|
|
380
|
-
- `
|
|
601
|
+
- `server-key` is required for requests
|
|
381
602
|
- `Authorization` is optional (`globalKey`)
|