# Quickstart

# Quickstart

## 1. Get credentials from your club admin

Only a **club admin** can create API credentials, in the panel under **Settings → API access**. Ask them to:

1. Open **Settings → API access** and click **Create client**.
2. Give it a name (e.g. "Club website"), pick scopes (`events:read`, `results:read`), optionally restrict
   caller IPs, accept the [API Terms](/docs/api-terms) the first time.
3. Copy the **client ID** and **client secret** shown on the confirmation screen — the secret is shown
   **once** and cannot be retrieved again (only rotated, which invalidates the old one).

Store both as environment variables (`SAIL_CLUB_CLIENT_ID`, `SAIL_CLUB_CLIENT_SECRET`). Never commit them,
never send them to a browser.

## 2. Get a token

```prompt
Exchange a Sail Club API client_id/client_secret for a bearer token: POST
https://sail-club-server.cloud.run/api/v1/oauth/token as application/x-www-form-urlencoded, with
grant_type=client_credentials and the client_id/client_secret sent as HTTP Basic auth. Cache the returned
access_token in memory for its expires_in seconds; do not fetch a new one per call.
```

#### Request

```bash
curl -s -X POST https://sail-club-server.cloud.run/api/v1/oauth/token \
  -u "$SAIL_CLUB_CLIENT_ID:$SAIL_CLUB_CLIENT_SECRET" \
  -d grant_type=client_credentials
```

```javascript
const creds = Buffer.from(`${process.env.SAIL_CLUB_CLIENT_ID}:${process.env.SAIL_CLUB_CLIENT_SECRET}`).toString('base64');
const res = await fetch('https://sail-club-server.cloud.run/api/v1/oauth/token', {
  method: 'POST',
  headers: { Authorization: `Basic ${creds}`, 'Content-Type': 'application/x-www-form-urlencoded' },
  body: 'grant_type=client_credentials',
});
const { access_token, expires_in } = await res.json();
```

```python
import os, requests

res = requests.post(
    "https://sail-club-server.cloud.run/api/v1/oauth/token",
    auth=(os.environ["SAIL_CLUB_CLIENT_ID"], os.environ["SAIL_CLUB_CLIENT_SECRET"]),
    data={"grant_type": "client_credentials"},
)
token = res.json()["access_token"]
```

```csharp
using var client = new HttpClient { BaseAddress = new Uri("https://sail-club-server.cloud.run") };
var clientId = Environment.GetEnvironmentVariable("SAIL_CLUB_CLIENT_ID");
var clientSecret = Environment.GetEnvironmentVariable("SAIL_CLUB_CLIENT_SECRET");
var authHeader = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{clientId}:{clientSecret}"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authHeader);
var res = await client.PostAsync("/api/v1/oauth/token",
    new FormUrlEncodedContent(new Dictionary<string, string> { ["grant_type"] = "client_credentials" }));
var body = await res.Content.ReadFromJsonAsync<JsonElement>();
var token = body.GetProperty("access_token").GetString();
```

```php
<?php
$clientId = getenv('SAIL_CLUB_CLIENT_ID');
$clientSecret = getenv('SAIL_CLUB_CLIENT_SECRET');
$ch = curl_init('https://sail-club-server.cloud.run/api/v1/oauth/token');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_USERPWD => "$clientId:$clientSecret",
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => 'grant_type=client_credentials',
]);
$token = json_decode(curl_exec($ch), true)['access_token'];
```

Full reference: [`POST /api/v1/oauth/token`](/docs/api/token).

## 3. Make your first call

```bash
curl -s https://sail-club-server.cloud.run/api/v1/events \
  -H "Authorization: Bearer $TOKEN"
```

You'll get back the club's published events, the same list its public results pages show — see
[List events](/docs/api/events) for the full shape.

## 4. Next

- [Authentication](/docs/authentication) — token lifetime, refresh pattern, secret handling.
- [Caching & freshness](/docs/caching) — don't poll harder than the data actually changes.
- [Errors](/docs/errors) — what every `code` means and how to react to it.
- [Build with AI](/docs/build-with-ai) — hand a coding agent the [full-integration prompt](/docs/overview#full-integration-prompt).
