DEVELOPER QUICKSTART / 15 MINUTES

Sports Picks API Quickstart for Node.js and Python

Connect a licensed server-side application to PowerHouse, request entitled sports intelligence, follow cursor pagination, and handle record lifecycle without exposing a production credential.

SHORT ANSWER

A server-to-server integration takes four steps.

  1. Store the customer API key in a server-only secret.
  2. Send it as an Authorization bearer credential.
  3. Process data, page, and requestId from each response.
  4. Reconcile updates using stable IDs and lifecycle fields.

The public endpoint is https://www.powerhouseaffiliates.com/api/v1. Production access is licence-scoped: a valid key does not grant every sport, field, result, channel, or redistribution right.

STEP 01 / SECURITY

1. Store the API key on your server

PowerHouse issues a revocable credential during technical onboarding. Store it in your deployment platform’s encrypted secret manager and expose it only to the server process making the request. Never place it in browser JavaScript, a mobile bundle, analytics, logs, screenshots, tickets, or a public repository.

.env — local development only
POWERHOUSE_API_KEY=ph_live_YOUR_KEY

STEP 02 / cURL

2. Make your first cURL request

Request a small upcoming page first. The sport value must match an entitled sport, and the response can contain fewer records than the requested limit when licence or quality gates exclude source rows.

Terminal
export POWERHOUSE_API_KEY="ph_live_YOUR_KEY"

curl --fail-with-body \
  --header "Authorization: Bearer ${POWERHOUSE_API_KEY}" \
  --header "Accept: application/json" \
  "https://www.powerhouseaffiliates.com/api/v1/picks?sport=Soccer&status=upcoming&limit=10"

A successful response returns data, page.nextCursor, page.hasMore, and a requestId. Keep the request ID with operational logs; it is the safest reference when investigating a delivery.

STEP 03 / NODE.JS

3. Connect with Node.js

This example uses the native fetch implementation available in current Node.js releases, so it needs no HTTP client dependency. Run it in a server process, background job, or protected function—not a Client Component.

get-picks.mjs
const apiKey = process.env.POWERHOUSE_API_KEY;

if (!apiKey) throw new Error("POWERHOUSE_API_KEY is required");

const url = new URL(
  "https://www.powerhouseaffiliates.com/api/v1/picks"
);
url.search = new URLSearchParams({
  sport: "Soccer",
  status: "upcoming",
  limit: "10",
}).toString();

const response = await fetch(url, {
  headers: {
    Accept: "application/json",
    Authorization: `Bearer ${apiKey}`,
  },
});

const body = await response.json();
if (!response.ok) {
  throw new Error(
    `${body.error?.code ?? response.status}: ${body.error?.message}`
  );
}

console.log(body.data);
console.log("Request ID:", body.requestId);

Do not log the complete request headers. Log the returned request ID, response status, route, record count, and elapsed time instead. Treat every response as private customer data and apply the retention rules in the active licence.

STEP 04 / PYTHON

4. Connect with Python

The Python example uses requests, an explicit timeout, and the same response contract as Node.js. Install the dependency with python -m pip install requests.

get_picks.py
import os
import requests

api_key = os.environ["POWERHOUSE_API_KEY"]
response = requests.get(
    "https://www.powerhouseaffiliates.com/api/v1/picks",
    headers={
        "Accept": "application/json",
        "Authorization": f"Bearer {api_key}",
    },
    params={
        "sport": "Soccer",
        "status": "upcoming",
        "limit": 10,
    },
    timeout=15,
)

body = response.json()
if not response.ok:
    error = body.get("error", {})
    raise RuntimeError(
        f"{error.get('code', response.status_code)}: "
        f"{error.get('message', 'Request failed')}"
    )

print(body["data"])
print("Request ID:", body["requestId"])

In production, reuse a requests.Session, set a bounded retry policy for transient failures, and send the key through the Authorization header on every request. Never place the key in the query string.

STEP 05 / PAGINATION

5. Follow cursor pagination

List routes cap each page at 200 records. Continue while page.nextCursor is non-null. A cursor is opaque: store or forward it unchanged, do not decode it, and do not assume it remains valid after changing filters.

Node.js pagination loop
let cursor;

do {
  const url = new URL(
    "https://www.powerhouseaffiliates.com/api/v1/picks"
  );
  url.searchParams.set("limit", "200");
  if (cursor) url.searchParams.set("cursor", cursor);

  const response = await fetch(url, {
    headers: { Authorization: `Bearer ${process.env.POWERHOUSE_API_KEY}` },
  });
  const body = await response.json();
  if (!response.ok) throw new Error(body.error?.message);

  await publishLicensedRecords(body.data);
  cursor = body.page.nextCursor;
} while (cursor);

Process each page idempotently using the stable record id. If a job fails after publication but before checkpointing the cursor, replaying the page should update the same records rather than create duplicates.

STEP 06 / OPERATIONS

6. Handle lifecycle and errors

Sports intelligence changes state after initial publication. Consumers should reconcile status, result, settledAt, and publishedAt against the stable ID. Never infer settlement from the event date alone.

StateContract meaningConsumer action
upcomingThe licensed record belongs to a future event.Create or update the pre-event publishing surface.
liveThe associated event is in progress.Follow the customer’s approved live-publication policy.
settledThe record has a final result.Reconcile result and settledAt against the stable record ID.
voidThe record is no longer eligible for normal settlement.Remove or label the record according to editorial policy.
Error envelope
{
  "error": {
    "code": "INVALID_FILTERS",
    "message": "One or more filters are invalid",
    "requestId": "req_..."
  }
}
StatusTypical codeRecommended handling
400INVALID_FILTERSCorrect the query. Do not retry the same request unchanged.
401UNAUTHORIZEDCheck the server-side key and Authorization header.
403LICENCE OR CHANNELConfirm the active licence, sport, channel, and entitlement.
404NOT_FOUNDTreat the opaque record ID as unavailable to this licence.
429RATE_LIMITEDBack off with jitter before retrying.
503TEMPORARILY UNAVAILABLERetry with bounded exponential backoff.

Error and success responses include a request ID. Retry only transient conditions, cap the number of attempts, add jitter, and surface persistent delivery failures to an operator.

PRODUCTION CHECKLIST

Move from a successful request to a reliable product.

  • Keep credentials server-only and rotate them after exposure.
  • Validate responses against the published JSON Schema.
  • Use stable IDs for idempotent upserts and corrections.
  • Persist request IDs without persisting bearer credentials.
  • Respect licensed sports, channels, fields, and retention.
  • Test 400, 401, 403, 404, 429, and 503 behavior.
  • Agree polling cadence and operational ownership during the pilot.

Availability is subject to schedule, market availability, model quality gates, and the customer licence. The API delivers structured sports intelligence; it does not guarantee inventory volume or sporting outcomes.

KEEP BUILDING

Use the maintained contract resources.

GitHub integration examples API playgroundField dictionaryJSON Schema

READY FOR LICENSED ACCESS?

Map the API to one real publishing workflow.

Bring your target sports, fields, channels, territory, and delivery cadence. PowerHouse will define the licence and technical pilot.

Book a technical demo