Developer overview

What you can build on the club API, the base URL, and a quickstart from key to first call.

Fencing Office has one API for everything a club does: look up members, classes, bookings, check-ins and payments, and make changes like booking a class, checking someone in or refunding an invoice. The same API powers the assistant in the app and the MCP server that Claude and ChatGPT use, so anything you can ask the assistant to do, your code can do too.

Ways in

  • REST API: GET /api/actions lists what your key may run, and POST /api/actions/<name> runs one. JSON in, JSON out. REST API.
  • MCP server: https://fencingoffice.com/api/mcp, for AI apps such as Claude, ChatGPT, Cursor and VS Code. Each action is one MCP tool. MCP server.
  • OAuth 2.1 sign-in: for an app that many clubs connect, a club admin signs in and picks the club instead of pasting a key. Sign in with OAuth.
  • Public schedule feed: a club’s class times and open spots, with no key, for its own website. Public schedule feed.
  • Webhooks: a signed POST to your server the moment something happens in the club, so you don’t have to poll. Webhooks.

Every action, with its parameters, is in the API reference.

Base URL

Every endpoint is under https://fencingoffice.com and HTTPS only. Requests and replies are JSON (Content-Type: application/json), except the OAuth token and revoke endpoints, which also take the usual form encoding.

Quickstart

  1. Create a key

    As a club admin, go to SettingsConnections and find Keys, for apps that can’t sign in. Name the key and click Create key. Pick Allow changes only if your code needs to change things, or Test key to try changes without making them. The key starts with fo_live_ and is shown once, so copy it now and keep it in an environment variable, here FO_KEY.
  2. See what the key can do

    curl https://fencingoffice.com/api/actions \
      -H "Authorization: Bearer $FO_KEY"

    The reply lists every action this key may run, with its description, risk and the JSON Schema of its input.

  3. Run a lookup

    curl https://fencingoffice.com/api/actions/members_search \
      -H "Authorization: Bearer $FO_KEY" \
      -H "Content-Type: application/json" \
      -d '{"input": {"query": "maya"}}'

    Lookups run straight away:

    {
      "result": {
        "total": 1,
        "offset": 0,
        "nextOffset": null,
        "members": [
          {
            "id": "Mx81Lq",
            "name": "Maya Chen",
            "email": "[email protected]",
            "groups": ["Youth Foil"],
            "membership": "active",
            "lastCheckedIn": "2026-11-04T22:05:00.000Z"
          }
        ]
      }
    }
  4. Make a change, in two calls

    A change never runs on the first call. The first call returns a preview of exactly what would happen and a confirm_token:

    curl https://fencingoffice.com/api/actions/group_add_members \
      -H "Authorization: Bearer $FO_KEY" \
      -H "Content-Type: application/json" \
      -d '{"input": {"group": "Adults", "member_ids": ["Mx81Lq"]}}'

    Show the preview to the person, and only when they say yes, send the same input again with the token:

    curl https://fencingoffice.com/api/actions/group_add_members \
      -H "Authorization: Bearer $FO_KEY" \
      -H "Content-Type: application/json" \
      -d '{"input": {"group": "Adults", "member_ids": ["Mx81Lq"], "confirm_token": "i2GWHFXwgGcAtdQcj_97IdOi"}}'

    A token works once, for 10 minutes, for that key, that action and that exact input. How the confirm step works.

How it fits together

  • Actions. Everything is an action with a name like members_search or payment_refund, a typed input and a risk. The app’s own buttons, the REST API, the MCP server and the in-app assistant all run the same action code, with the same checks.
  • One club per key. A key, or a token from signing in, belongs to one club. Nothing it does can reach another club’s data.
  • Read or write. A read key can run lookups only. A write key acts as the admin who created it, and drops back to read-only the moment that person stops being an admin.
  • Risk. Each action says whether it reads, changes club data, deletes or cancels, emails or messages people, or moves money. Everything but a read asks first.
  • The club’s own rules. A club admin can turn a tool, or a whole level of tools, off for the assistant and API keys under SettingsAssistant. A tool that is off isn’t listed, and a call to it gets the reason.
  • A log of every change. Every change is logged with the action, its input, the key and the result, and shows in the club’s activity log.

Conventions

  • Times are ISO 8601 in UTC, like 2026-11-09T22:00:00.000Z. Results that group by day say which time zone they used (timeZone): the club’s first active class’s zone, else America/New_York.
  • Dates you send are YYYY-MM-DD; an end date includes that whole day.
  • Ids are opaque strings. Get them from a lookup (members_search, classes_list, class_roster) and pass them back as they are.
  • Money amounts are in dollars, as the action’s parameters say.
  • An action that takes people takes either member_ids or a match (the same filters as members_search), not both. Many people in one call.
  • Try changes safely with a test key: it reads real data, but a confirmed change is only simulated.

Limits

  • 120 requests a minute per key, counted on each server, so a client may get a little more. Over that you get 429 with Retry-After: 60.
  • A club can have 10 active keys. Tokens from signing in don’t count toward that.
  • Lists are capped (for example 200 members a page, 200 bookings, 31 days of classes), and each action’s parameters say its own limits. Paging and ranges.

What isn’t here yet

  • No client libraries. Any HTTP client works; the REST API has curl, JavaScript and Python.