# @fieldfunded/sdk

Official TypeScript SDK for the **FieldFunded Sports API** — real-time odds, scores, and settlements for 30+ sports & esports.

[![npm](https://img.shields.io/npm/v/@fieldfunded/sdk)](https://www.npmjs.com/package/@fieldfunded/sdk)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue)](https://www.typescriptlang.org)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](./LICENSE)

## Requirements

- **Node.js 18+**, Deno, Bun, or any modern browser with `fetch()` support
- No native dependencies — works in serverless environments (AWS Lambda, Vercel, Cloudflare Workers)

## Features

- **25 typed methods** covering every API endpoint
- **Zero dependencies** — uses native `fetch()`
- **Full TypeScript support** — intellisense, autocompletion, type safety
- **Auto-retry** on 429/500 errors with exponential backoff
- **Dual-mode auth** — works with RapidAPI or direct API keys
- **Works everywhere** — Node.js 18+, Deno, Bun, browsers

## Install

```bash
npm install @fieldfunded/sdk
```

## Quick Start

```typescript
import { FieldFundedSDK } from '@fieldfunded/sdk';

// RapidAPI mode (default)
const client = new FieldFundedSDK({
    apiKey: 'YOUR_RAPIDAPI_KEY',
});

// Direct API mode
const client = new FieldFundedSDK({
    apiKey: 'YOUR_API_KEY',
    baseUrl: 'https://api.fieldfunded.com/v1',
});
```

## Usage Examples

### List Sports & Leagues

```typescript
const sports = await client.getSports();
console.log(sports.sports); // [{ key: 'soccer', name: 'Soccer', active_events: 142, is_esport: false }, ...]

const leagues = await client.getLeagues({ sport: 'soccer' });
console.log(leagues.leagues);
// [{ slug: 'premier-league', name: 'Premier League', country: 'England', active_events: 10, live_events: 3 }, ...]
```

### Get Events (with filters)

```typescript
// All live soccer in England
const events = await client.getEvents({
    sport: 'soccer',
    country: 'england',
    status: 'live',
});

// Games starting in the next 2 hours
const upcoming = await client.getEvents({
    starts_within: '2h',
    sport: 'basketball',
});

// Events on a specific date
const tomorrow = await client.getEvents({
    date: '2026-04-16',
    league: 'premier-league',
});

// American odds format
const events = await client.getEvents({
    sport: 'soccer',
    odds_format: 'american',
});
```

### Event Detail

```typescript
const detail = await client.getEvent('12345678');
console.log(detail.markets);     // Full markets array (1,000+ on major events)
console.log(detail.scoreboard);  // Period/set/map scores
console.log(detail.sport_data);  // Serving indicators, power play, etc.
```

### Live Scores (lightweight polling)

```typescript
// Optimized endpoint — use for frequent polling
const scores = await client.getScores({ sport: 'soccer' });
for (const game of scores.scores) {
    console.log(`${game.home_team} ${game.score.home}-${game.score.away} ${game.away_team}`);
}
```

### Search by Team Name

```typescript
const results = await client.search('Barcelona', { limit: 5 });
console.log(results.events); // All upcoming/live Barcelona games
```

### Batch Events (max 20 IDs)

```typescript
const batch = await client.getEventsBatch(['123', '456', '789']);
console.log(batch.events);     // Record<id, EventDetail> — full detail for each found event
console.log(batch.found);      // Number of events found (e.g. 2)
console.log(batch.requested);  // Number of IDs you sent (e.g. 3)
```

### Bet Check

```typescript
const result = await client.checkBet({
    selections: [{
        event_id: '123',
        market: 'Match Winner',
        outcome: 'Home',
        odds: 1.85,
        stake: 10,           // currency-agnostic — any numeric amount
        market_id: 340168,   // primary — recommended for 100% accuracy
        selection_id: 1824650, // primary — recommended for 100% accuracy
    }],
});
console.log(result.results[0].result);  // 'won' | 'lost' | 'refund' | 'pending'
console.log(result.results[0].payout);  // 18.50 (when won, = stake × odds)
```

### Parlay / Accumulator Check

```typescript
const parlay = await client.checkParlay({
    legs: [
        { event_id: '123', market: 'Match Winner', outcome: 'Home', odds: 1.85, market_id: 340168, selection_id: 1824650 },
        { event_id: '456', market: 'Over/Under 2.5', outcome: 'Over', odds: 1.92 },
    ],
    stake: 10,
});
console.log(parlay.payout.combined_odds);
console.log(parlay.payout.payout);
```

### Settlements & Results (Fully Automatic)

[!] **Settlement is fully automatic.** Markets resolve without developer intervention as soon as a game ends. No manual trigger needed — just call `getSettlements()` periodically (e.g. every 60 seconds) to retrieve the results.

[!] Poll `getSettlements()` regularly after the game ends to capture all market resolutions as they arrive gradually. Core markets settle in seconds; niche markets in minutes. Obscure leagues may take longer. For permanent score data, use `getEventResult()`.

```typescript
// Check settlements every 60 seconds (e.g. via setInterval or a scheduled task)
const settlements = await client.getSettlements({ limit: 10 });
for (const s of settlements.settlements) {
    console.log(`${s.event_id}: ${s.settlement_status} — ${s.resolved_markets_count} markets resolved`);
}

// Get official result for a specific event (score-only, available automatically)
const result = await client.getEventResult('12345678');
console.log(`Status: ${result.status}`); // 'ended', 'walkover', 'retired', etc.
if (result.score) {
    console.log(`Final Score: ${result.score.home}-${result.score.away}`);
}
// For market-level resolutions (won/lost/refund), use getSettlements()
const settlements = await client.getSettlements({ event_id: '12345678' });

// Outright/futures settlements (also automatic)
const outrightSettlements = await client.getOutrightSettlements({ sport: 'soccer' });
```

### Outrights / Futures

```typescript
// List tournaments that have outright markets
const outrights = await client.getOutrights({ sport_id: '124' }); 

// Get selections and odds for a specific tournament (supports odds_format)
const detail = await client.getOutright('12345', { odds_format: 'american' });
console.log(detail.events[0].markets[0].selections[0].odds);
```

### Usage & Quota

```typescript
const usage = await client.getUsage();
console.log(`${usage.current.this_month} / ${usage.limits.per_month} requests used`);
console.log(`${usage.current.month_remaining} remaining`);

// Per-endpoint breakdown
const endpoints = await client.getUsageEndpoints();
console.log(endpoints.endpoints); // [{ path: '/events', count: 142 }, { path: '/live', count: 58 }, ...]

// Active warnings
const warnings = await client.getUsageWarnings();
if (warnings.warnings.length > 0) {
    console.warn('[!]', warnings.warnings[0].message);
}
```

### System Health (quota-free)

These endpoints do not count against your monthly quota. They have their own fixed rate limits: **2 req/s** and **120 req/h**, equal for all plans.

```typescript
const ping = await client.ping();       // { pong: true, timestamp: 1234567890 }
const health = await client.health();   // System health, data freshness
const status = await client.status();   // Live/prematch counts
```

## Error Handling

```typescript
import {
    FieldFundedSDK,
    RateLimitError,
    AuthenticationError,
    NotFoundError,
    BadRequestError,
    ServiceUnavailableError,
} from '@fieldfunded/sdk';

try {
    const events = await client.getEvents();
} catch (err) {
    if (err instanceof RateLimitError) {
        console.log(`Rate limited. Retry in ${err.retryAfter}s`);
    } else if (err instanceof ServiceUnavailableError) {
        console.log(`Service degraded (${err.component}). Retry in ${err.retryAfter}s`);
    } else if (err instanceof AuthenticationError) {
        console.log('Invalid API key');
    } else if (err instanceof NotFoundError) {
        console.log('Event not found');
    } else if (err instanceof BadRequestError) {
        console.log(`Invalid request: ${err.message}`);
    }
}
```

## Configuration

```typescript
const client = new FieldFundedSDK({
    apiKey: 'YOUR_KEY',         // Required
    host: 'custom-host.com',    // RapidAPI host (optional)
    baseUrl: 'https://...',     // Direct API URL (overrides host)
    timeout: 20000,             // Request timeout in ms (default: 20000)
    retries: 2,                 // Retries on 429/500 (default: 1)
});

// Note: The default timeout is set to 20s to accommodate server-side index construction on high-volume endpoints (like /settlements). Most requests return in <200ms.
```

## All Methods

| Method | Endpoint | Description |
|--------|----------|-------------|
| `ping()` | `GET /v1/ping` | Latency check (quota-free) |
| `health()` | `GET /v1/health` | System health (quota-free) |
| `status()` | `GET /v1/status` | API status (quota-free) |
| `getUsage()` | `GET /v1/usage` | Quota & consumption stats |
| `getUsageEndpoints()` | `GET /v1/usage/endpoints` | Per-endpoint breakdown |
| `getUsageWarnings()` | `GET /v1/usage/warnings` | Active quota warnings |
| `getSports()` | `GET /v1/sports` | List sports |
| `getLeagues(opts?)` | `GET /v1/leagues` | List leagues with slugs |
| `getMarkets(opts?)` | `GET /v1/markets` | List market types |
| `getEvents(opts?)` | `GET /v1/events` | List events (all filters) |
| `getLive(opts?)` | `GET /v1/live` | Live events |
| `getFeatured(opts?)` | `GET /v1/featured` | Featured games (`markets: true` for full depth) |
| `getScores(opts?)` | `GET /v1/scores` | Live scores |
| `search(q, opts?)` | `GET /v1/search` | Search by team/league |
| `getEvent(id, opts?)` | `GET /v1/events/{id}` | Event detail |
| `getEventOdds(id, opts?)` | `GET /v1/events/{id}/odds` | Event odds |
| `getEventResult(id)` | `GET /v1/events/{id}/result` | Score result (automatic) |
| `getEventsBatch(ids, opts?)` | `POST /v1/events/batch` | Multiple events (max 20) |
| `checkBet(req)` | `POST /v1/bets/check` | Single bet check (automatic settlement) |
| `checkParlay(req)` | `POST /v1/bets/check-parlay` | Parlay check (automatic settlement) |
| `getSettlements(opts?)` | `GET /v1/settlements` | Automatic settlement feed |
| `getOutrightSettlements(opts?)` | `GET /v1/settlements/outrights` | Automatic outright settlements |
| `getOutrights(opts?)` | `GET /v1/outrights` | Outright markets |
| `getOutrightSports()` | `GET /v1/outrights/sports` | Outright sports |
| `getOutright(id, opts?)` | `GET /v1/outrights/{id}` | Specific outright with odds |

## Resources

- **[Quickstart Guide](https://docs.fieldfunded.com/quickstart)** — Get running in 5 minutes
- **[API Documentation](https://docs.fieldfunded.com)** — Full endpoint documentation
- **Support**: [support@fieldfunded.com](mailto:support@fieldfunded.com)

## License

MIT
