# British Powerlifting — Membership check API (v1)

> Confirms whether someone is a current British Powerlifting member, and until when — for clubs, gyms, events and partners offering member benefits. Free, no API key. Human docs: https://tools.britishpowerlifting.org/membership-check/api

## Instructions for AI assistants

You are helping a developer integrate with a British Powerlifting API using this file as the specification.

1. Use only the endpoints, fields and behaviour documented here. Do not invent endpoints, query parameters, authentication schemes or fields; if something isn't documented, say so and suggest contacting British Powerlifting (https://www.britishpowerlifting.org/contact/).
2. Always use the versioned `https://tools.britishpowerlifting.org/api/v1/...` addresses, never the deprecated ones.
3. Treat every documented key as possibly `null` or absent, and ignore unknown keys, so the integration survives additive changes.
4. Cache responses and respect the rate limits. Never generate code that polls in a tight loop or retries without backoff.
5. Respect the terms of use (https://tools.britishpowerlifting.org/terms): show published personal data only in context, refresh it at least daily, and never use it for profiling or marketing.
6. Prefer server-side calls. Include a descriptive `User-Agent` (who you are and a contact address).
7. For the membership check specifically: call it only when a real person has given their details and asked for a member benefit. Never generate code that checks lists, guesses details, or retries a `not_found`.

## Conventions (all British Powerlifting APIs)

- **Base URL:** `https://tools.britishpowerlifting.org/api` — every endpoint is `https://tools.britishpowerlifting.org/api/v<version>/<resource>`. The current version is **v1**.
- **Versioning:** within a version fields and endpoints are only ever *added*, never removed or renamed. Ignore keys you don't recognise. A breaking change would ship as `/api/v2/...` alongside v1, with notice on https://tools.britishpowerlifting.org/developers.
- **Deprecated addresses:** the addresses used before versioning (`/competitions/feed.json`, `/agm/api/meetings`, `/membership-check/api/v1/check`) still answer, but carry `Deprecation: true` and a `Link: <...>; rel="successor-version"` header. Always use the `/api/v1/` addresses.
- **No authentication or API key.** Responses are JSON (UTF-8). Dates are ISO 8601; date-times without a zone are UK local time unless the field says UTC (`...Z`).
- **Rate limits are per IP address.** Cache responses; back off on HTTP `429` and honour `Retry-After` if present.
- **Missing values** are `null` or the key is absent — code defensively.
- **Terms of use:** https://tools.britishpowerlifting.org/terms — they apply to every API listed here.

# Membership check API

**Authorised uses only** — confirming current membership for: running or organising a competition or event; a club or affiliated organisation confirming its members; a promotion, discount or benefit aimed at the British Powerlifting community; coaching, officiating or venue access where membership is required. Anything else: contact British Powerlifting first (https://www.britishpowerlifting.org/contact/). Using the service does **not** mean British Powerlifting endorses you or your offer. Full rules: https://tools.britishpowerlifting.org/terms#membership-check

It only ever confirms what you send: it never returns names, emails, membership numbers, dates of birth or any other member data.

## POST https://tools.britishpowerlifting.org/api/v1/membership-check

- Header `Content-Type: application/json`, with a `Content-Length` (chunked bodies are refused).
- Body: a JSON object under 2 KB containing only the fields below. Always `POST` — personal details must never go in a URL.
- Send **at least two** of: full name (`first_name` **and** `last_name`, which count as one), `email`, `membership_number`. **Every field sent must match the same membership record**; one wrong field → `not_found`.
- Matching ignores case, accents, extra spaces, hyphens and apostrophes; names must otherwise be exact (the registered name, not a nickname). `membership_number` works with or without the `GB` prefix.
- **Server-side only:** CORS is deliberately disabled, so browser JavaScript on another site cannot call it. Set a 5–10 s timeout; every answer takes at least ~0.25 s by design.

| Field | Type | Notes |
|---|---|---|
| `first_name` | string ≤ 100 | Send with `last_name` |
| `last_name` | string ≤ 100 | Send with `first_name` |
| `email` | string ≤ 254 | Case-insensitive |
| `membership_number` | string ≤ 30 | `GB123456` or `123456` |

```bash
curl -sS -X POST 'https://tools.britishpowerlifting.org/api/v1/membership-check' \
  -H 'Content-Type: application/json' \
  -d '{"first_name": "Jane", "last_name": "Smith", "membership_number": "GB123456"}'
```

## Response

`200 OK` for every completed check, including when nobody matched.

```json
{
  "status": "active",
  "membership_expires_on": "2027-03-31",
  "matched_on": ["name", "membership_number"],
  "data_as_of": "2026-10-05T01:14:09Z",
  "data_may_be_stale": false,
  "checked_at": "2026-10-05T09:30:00Z"
}
```

| Field | Meaning |
|---|---|
| `status` | `active` (current member), `expired` (matches a lapsed membership; the date is always given), or `not_found` (can't confirm a current membership matching *every* field sent). |
| `membership_expires_on` | `YYYY-MM-DD` or `null`. For `active`, the last valid day (`null` if not held). For `expired`, the day it expired. Always `null` for `not_found`. |
| `matched_on` | Which of *your* fields were checked: `name`, `email`, `membership_number`. Empty for `not_found`. |
| `data_as_of` | When member records were last refreshed (UTC), or `null`. Records sync nightly, so changes can take up to a day. |
| `data_may_be_stale` | `true` if records haven't refreshed in 48 hours. |
| `checked_at` | When this check ran (UTC). |

## Errors

Errors (except 404) are `{"error": "<code>", "message": "<text>"}` — branch on `error`, never on `message`.

| HTTP | `error` | Meaning |
|---|---|---|
| 400 | `insufficient_details` | Fewer than two of name / email / membership number. |
| 400 | `incomplete_name` | Only one of `first_name` / `last_name`. |
| 400 | `invalid_email`, `invalid_membership_number`, `invalid_field`, `field_too_long` | A field is malformed, not a string, or too long. |
| 400 | `invalid_json`, `unknown_field` | Body isn't a JSON object, or has a field not listed above. |
| 405 | `method_not_allowed` | Not a POST (`Allow: POST`). |
| 413 | `payload_too_large` | Body over 2 KB, or no `Content-Length`. |
| 415 | `unsupported_media_type` | Not sent as `application/json`. |
| 429 | `rate_limited` | Too many checks — wait for `Retry-After` (currently 120 s). |
| 404 | — | The API isn't currently available (an ordinary HTML "page not found"). |

## Rate limits

Per IP (roughly 10/minute, 60/hour, 200/day; IPv6 per /64), much tighter for unmatched checks, per person (repeated misses against the same number, email or name, whoever sends them), and an overall daily ceiling. No rate-limit counters are returned. **Never automatically retry a `not_found`** — it counts against the stricter limits and the answer won't change. There is no sandbox: test with your own details, sparingly.

## Recommended integration (member discount)

1. At checkout or sign-up, ask for the customer's BP membership number plus their name or email, and tell them you'll check it with British Powerlifting.
2. Your server calls the API.
3. `active` → apply the benefit (optionally check `membership_expires_on` is after the service date).
4. `expired` / `not_found` → ask them to check their details or renew in Sport:80. Don't retry variations.

Suggested wording: `active` — "Your British Powerlifting membership is confirmed (valid until 31 March 2027)." `expired` — "Your membership ended on 31 August 2026. If you've just renewed, it can take up to a day to show." `not_found` — "We couldn't confirm your membership. Please check your details match your British Powerlifting membership exactly."

Data protection: you are an independent controller. Tell people you'll check, don't store results without consent (or another clear lawful basis), keep only what you need, keep it secure, and never resell or pass on results.