{% extends "base.html" %} {% block title %}API Key: {{ api_key.name }} — {{ club.name }}{% endblock %} {% block content %}

{{ club.name }} — {{ api_key.name }}

Back to API keys {% if api_key.is_active %} Revoke key {% endif %}
{% if new_raw_key %}
Save this key now — it will not be shown again.
{{ new_raw_key }}
{% endif %}
Prefix{{ api_key.prefix }}…
Status{% if api_key.is_active %}Active{% else %}Revoked{% endif %}
Created{{ api_key.created_at|date:"N j, Y g:i a" }}
Last used{{ api_key.last_used_at|date:"N j, Y g:i a"|default:"Never" }}

Track activity for this key in club history — search for {{ api_key.prefix }}.

Permissions
Add club members{% if api_key.can_add_club_members %}Enabled{% else %}Disabled{% endif %}
Read club member list{% if api_key.can_read_club_member_list %}Enabled{% else %}Disabled{% endif %}
Update club members{% if api_key.can_update_club_members %}Enabled{% else %}Disabled{% endif %}
BAP points and lots
Add points to members, and read the lots from this club's auctions
{% if api_key.can_add_bap_points %}Enabled{% else %}Disabled{% endif %}
Renew memberships{% if api_key.can_renew_memberships %}Enabled{% else %}Disabled{% endif %}
Example endpoints
Send this key in the X-API-Key header (or Authorization: Api-Key <key>).
For write requests, also include Content-Type: application/json and send a JSON body.
{% if api_key.can_add_club_members %}
Add club member: POST https://{{ site_domain }}/api/v1/clubs/{{ club.slug }}/members/
Example body: {"name":"Mike","email":"mike@example.com"}
{% endif %} {% if api_key.can_read_club_member_list %}
Read club member list: GET https://{{ site_domain }}/api/v1/clubs/{{ club.slug }}/members/
Filter by name: ?name=mike — or use the same text filter as the admin member list: ?filter=expired, ?filter=mike, etc. (tokens: discord, current, expired, expiring, never paid, or any name/email text)
{% endif %} {% if api_key.can_update_club_members %}
Update club member: PATCH https://{{ site_domain }}/api/v1/clubs/{{ club.slug }}/members/{{ example_member_id|default:"123" }}/
Example body: {"email":"mike@example.com"}
{% endif %} {% if api_key.can_add_bap_points %}
Add BAP points: POST https://{{ site_domain }}/api/v1/clubs/{{ club.slug }}/members/{{ example_member_id|default:"123" }}/bap-awards/
Example body: {"points":5,"notes":"Bred angelfish","email":"mike@example.com"}
Read lots from this club's auctions: GET https://{{ site_domain }}/api/v1/clubs/{{ club.slug }}/bap-lots/
Every lot in this club's auctions that ended in the date range, with who sold it, who bought it, and everything this site knows about whether it earns points — so an outside breeder award program can match them up on email and decide for itself. Lots that didn't sell are included too, with winner_name and winner_email blank.
Date range. The default is the last {{ bap_lot_default_days }} days. Ask for a different one with ?days=90, or with explicit dates: ?start=2026-01-01&end=2026-03-31. Dates are inclusive on both ends (end=2026-03-31 covers all of March 31st) and are read in the site's local time; full ISO 8601 timestamps work in place of a date, and start/end take precedence over days. The response echoes the exact range it used, in UTC.
Pulling repeatedly. Overlapping ranges are expected — key on lot_id, which is permanent and never reused, so a lot you've already handled is easy to skip. bap_award also tells you whether this site has already recorded points for the lot.
Example request:
curl "https://{{ site_domain }}/api/v1/clubs/{{ club.slug }}/bap-lots/?days={{ bap_lot_default_days }}" \
  -H "X-API-Key: {{ api_key.prefix }}.YOUR_SECRET"
Example response — newest first, all times UTC, and start/end echo back the range that was used:
{
  "start": "{{ example_bap_range_start }}",
  "end": "{{ example_bap_range_end }}",
  "count": 1,
  "results": [
    {
      "lot_id": 84213,
      "lot_number_display": "12",
      "lot_name": "6 Corydoras panda fry",
      "quantity": 6,
      "seller_name": "Mike Smith",
      "seller_email": "mike@example.com",
      "winner_name": "Dana Lee",
      "winner_email": "dana@example.com",
      "timestamp": "{{ example_bap_lot_timestamp }}",
      "sold": true,
      "category": "Catfish",
      "program": "BAP",
      "i_bred_this_fish": true,
      "donation": false,
      "custom_checkbox": false,
      "custom_checkbox_name": "",
      "bap_eligible": true,
      "bap_ineligible_reason": "",
      "bap_ineligible_reason_display": "",
      "bap_auto_reason": "",
      "bap_points_awarded": 5,
      "manually_approved": false,
      "bap_award": {
        "id": 991,
        "date": "{{ example_bap_award_date|date:"Y-m-d" }}",
        "points": 5,
        "hap_points": 0,
        "cap_points": 0,
        "notes": "",
        "auto_awarded": true
      }
    }
  ]
}
A lot that didn't sell looks the same, with "sold": false, blank winner fields, "bap_award": null, and (for a club that only gives points for sold lots) "bap_ineligible_reason": "not_sold".
Fields
{% include "auctions/partials/bap_lot_api_fields.html" %}
{% endif %} {% if api_key.can_renew_memberships %}
Renew a membership: POST https://{{ site_domain }}/api/v1/clubs/{{ club.slug }}/members/renew/
Send the member's details. They're matched by email (required) and added to the club if they're new, then their membership is renewed the same way the Renew button on the member list does: {% if club.membership_system == "january_first" %}the expiration moves to the next January 1st{% else %}the expiration moves a year forward, from the current expiration if it hasn't passed yet{% endif %}, the club history and treasurer's report record the renewal, and the member gets the renewal confirmation email{% if not club.send_membership_renewal_confirmation %} (currently turned off in this club's email settings){% endif %}. Any other field this key may write can ride along and is applied before the renewal; blank values are ignored instead of clearing what's on file.
Example:
curl -X POST https://{{ site_domain }}/api/v1/clubs/{{ club.slug }}/members/renew/ \
  -H "X-API-Key: {{ api_key.prefix }}.YOUR_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"email":"mike@example.com","first_name":"Mike","last_name":"Smith","phone_number":"555-0143"}'
Response — 200 for an existing member, 201 when one was created:
{
  "created": false,
  "id": {{ example_member_id|default:"123" }},
  "name": "Mike Smith",
  "email": "mike@example.com",
  "membership_number": 1234567890,
  "membership_last_paid": "{{ example_last_paid|date:"Y-m-d" }}",
  "membership_expiration_date": "{{ example_new_expiration|date:"Y-m-d" }}",
  "wallet_link": "https://{{ site_domain }}/clubs/{{ club.slug }}/member/<uuid>/",
  ...
}
{% endif %}
Field mappings

Map incoming field names to club member fields. Unmapped fields are passed through unchanged if they match an allowed field name.

Members store a single name field. If first_name and/or last_name are sent (or mapped) instead, they will be combined into name.

Field mapping applies to club member create and update payloads. BAP point requests always use the fixed points, date, and notes fields directly.

{% if field_mappings %} {% for mapping in field_mappings %} {% endfor %}
External field (incoming)Internal field
{{ mapping.external_field }} {{ mapping.internal_field }}
{% csrf_token %}
{% else %}

No mappings yet.

{% endif %} {% if api_key.is_active %}
{% csrf_token %}
Allowed mapped club member fields: {% for field in available_fields %}{{ field }}{% if not forloop.last %}, {% endif %}{% endfor %}
{% endif %}
{% endblock %}