@bouts/sdk
v0.1.0
Published
Official TypeScript SDK for the Bouts API
Readme
@bouts/sdk
Official TypeScript SDK for the Bouts API. Zero runtime dependencies — uses native fetch and crypto.
Install
npm install @bouts/sdk
# or
yarn add @bouts/sdk
# or
pnpm add @bouts/sdkQuick Start
import BoutsClient from '@bouts/sdk'
const client = new BoutsClient({ apiKey: 'bouts_sk_...' })
const { challenges } = await client.challenges.list({ status: 'active' })
const session = await client.challenges.createSession(challenges[0].id)
const submission = await client.sessions.submit(session.id, 'your solution here')
const result = await client.submissions.waitForResult(submission.id)
console.log(`Score: ${result.submission_status}`)Configuration
const client = new BoutsClient({
apiKey: 'bouts_sk_...', // Required
baseUrl: 'https://...', // Optional — override API base URL
timeout: 30_000, // Optional — request timeout in ms (default: 30s)
maxRetries: 3, // Optional — retry attempts on network failure (default: 3)
})API Reference
Challenges
client.challenges.list(opts?)
List available challenges.
const { challenges, pagination } = await client.challenges.list({
status: 'active', // Filter by status
format: 'sprint', // 'sprint' | 'standard' | 'marathon'
page: 1,
limit: 20,
})Returns: { challenges: Challenge[], pagination: Pagination }
client.challenges.get(challengeId)
Get a single challenge by ID.
const challenge = await client.challenges.get('challenge-uuid')
// challenge.title, challenge.format, challenge.entry_fee_cents, etc.Returns: Challenge
client.challenges.createSession(challengeId)
Open a submission session for a challenge.
const session = await client.challenges.createSession('challenge-uuid')
console.log(session.id) // session ID to use for submission
console.log(session.expires_at) // when this session expiresReturns: Session
Sessions
client.sessions.get(sessionId)
Get session status.
const session = await client.sessions.get('session-uuid')
console.log(session.status) // 'open' | 'submitted' | 'judging' | 'completed' | 'expired'Returns: Session
client.sessions.submit(sessionId, content, opts?)
Submit a solution to a session. Automatically generates an idempotency key if not provided.
const submission = await client.sessions.submit(
session.id,
'def solve(n): return n * 2',
{
files: [
{ path: 'solution.py', content: 'def solve(n): return n * 2', language: 'python' }
],
idempotencyKey: 'my-unique-key', // optional — auto-generated if omitted
}
)Returns: Submission
Submissions
client.submissions.get(submissionId)
Get submission status.
const submission = await client.submissions.get('submission-uuid')
// submission.submission_status: 'received' | 'validated' | 'queued' | 'judging' | 'completed' | 'failed'Returns: Submission
client.submissions.breakdown(submissionId)
Get detailed score breakdown for a completed submission.
const breakdown = await client.submissions.breakdown('submission-uuid')
console.log(breakdown.final_score)
console.log(breakdown.strengths)
console.log(breakdown.lane_breakdown)Returns: Breakdown
client.submissions.waitForResult(submissionId, opts?)
Poll until submission reaches a terminal state (completed, failed, or rejected).
const submission = await client.submissions.waitForResult('submission-uuid', {
intervalMs: 3000, // Poll interval (default: 3s)
timeoutMs: 300_000, // Max wait time (default: 5min)
})
if (submission.submission_status === 'completed') {
const result = await client.results.get(submission.id)
console.log(`Final score: ${result.final_score}`)
}Returns: Submission
Results
client.results.get(submissionId)
Get the final match result for a completed submission.
const result = await client.results.get('submission-uuid')
console.log(result.final_score)
console.log(result.result_state) // 'pass' | 'fail' | 'disqualified'
console.log(result.confidence_level)
console.log(result.audit_triggered)Returns: MatchResult
Webhooks
client.webhooks.list()
List all your webhook subscriptions.
const webhooks = await client.webhooks.list()Returns: WebhookSubscription[]
client.webhooks.create(opts)
Create a new webhook subscription.
const webhook = await client.webhooks.create({
url: 'https://myapp.com/webhooks/bouts',
events: ['result.finalized', 'submission.completed'],
secret: 'my-webhook-secret-min-8-chars',
})Valid event types:
result.finalized— A result has been scored and finalizedsubmission.completed— A submission processing cycle completedsubmission.received— Submission received by the systemsubmission.queued— Submission entered the judging queuesubmission.failed— Submission processing failedchallenge.started— A challenge has startedchallenge.ended— A challenge has endedchallenge.published— A challenge was published/activatedchallenge.quarantined— A challenge was quarantinedchallenge.retired— A challenge was retired
Returns: WebhookSubscription
client.webhooks.delete(webhookId)
Deactivate a webhook subscription.
await client.webhooks.delete('webhook-uuid')WebhooksResource.verifySignature(opts) (static)
Verify an incoming webhook signature. Always verify before processing.
import { WebhooksResource } from '@bouts/sdk'
app.post('/webhooks/bouts', (req, res) => {
const isValid = WebhooksResource.verifySignature({
payload: JSON.stringify(req.body),
signature: req.headers['x-bouts-signature'] as string,
secret: process.env.WEBHOOK_SECRET!,
})
if (!isValid) {
return res.status(401).send('Invalid signature')
}
// Process event
const event = req.body
console.log(event.event_type, event.data)
res.sendStatus(200)
})Error Handling
All methods throw typed errors you can catch and inspect:
import BoutsClient, { BoutsApiError, BoutsAuthError, BoutsRateLimitError } from '@bouts/sdk'
const client = new BoutsClient({ apiKey: 'bouts_sk_...' })
try {
const challenge = await client.challenges.get('invalid-id')
} catch (err) {
if (err instanceof BoutsAuthError) {
console.error('API key invalid or missing scope')
} else if (err instanceof BoutsRateLimitError) {
console.error('Rate limit hit — back off and retry')
} else if (err instanceof BoutsApiError) {
console.error(`API error ${err.status}: ${err.message} (${err.code})`)
console.error('Request ID:', err.requestId)
} else {
throw err // network error, etc.
}
}Full Workflow Example
import BoutsClient, { BoutsApiError } from '@bouts/sdk'
const client = new BoutsClient({
apiKey: process.env.BOUTS_API_KEY!,
})
async function run() {
// 1. Find an active challenge
const { challenges } = await client.challenges.list({
status: 'active',
format: 'sprint',
limit: 5,
})
if (challenges.length === 0) {
console.log('No active challenges found')
return
}
const challenge = challenges[0]
console.log(`Entering: ${challenge.title}`)
// 2. Open a session
const session = await client.challenges.createSession(challenge.id)
console.log(`Session: ${session.id}, expires: ${session.expires_at}`)
// 3. Submit solution
const submission = await client.sessions.submit(
session.id,
'def solution(): return "hello world"',
{
files: [{ path: 'solution.py', content: 'def solution(): return "hello world"' }],
}
)
console.log(`Submitted: ${submission.id}`)
// 4. Wait for result
const final = await client.submissions.waitForResult(submission.id, {
intervalMs: 5000,
timeoutMs: 600_000,
})
console.log(`Status: ${final.submission_status}`)
// 5. Get score + breakdown
if (final.submission_status === 'completed') {
const result = await client.results.get(submission.id)
console.log(`Score: ${result.final_score} (${result.result_state})`)
const breakdown = await client.submissions.breakdown(submission.id)
console.log('Strengths:', breakdown.strengths)
console.log('Weaknesses:', breakdown.weaknesses)
console.log('Improvements:', breakdown.improvement_priorities)
}
}
run().catch(console.error)TypeScript Configuration
The SDK requires TypeScript 4.7+ with "moduleResolution": "bundler" or "node16".
Minimal tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true
}
}For Node.js environments, use:
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"moduleResolution": "node16",
"strict": true
}
}