{"openapi":"3.1.0","info":{"title":"curltime club API","version":"1.0.0","description":"A club API key acts on its own club only, with the same content powers a vice president has: the page, the roster and announcements. It can never touch officers, other keys, invites or the presidency. Requests are limited to 60 per minute per key."},"servers":[{"url":"/"}],"security":[{"bearerKey":[]}],"paths":{"/api/v1/openapi.json":{"get":{"summary":"This document.","security":[],"responses":{"200":{"description":"The OpenAPI 3.1 description of this API."}}}},"/api/v1/clubs/{slug}":{"get":{"summary":"The club's page data.","parameters":[{"$ref":"#/components/parameters/slug"}],"responses":{"200":{"description":"The club.","content":{"application/json":{"schema":{"type":"object","properties":{"club":{"$ref":"#/components/schemas/Club"}},"required":["club"]}}}},"401":{"description":"The error, in the same envelope every route uses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The error, in the same envelope every route uses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"The error, in the same envelope every route uses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/clubs/{slug}/members":{"get":{"summary":"The roster.","parameters":[{"$ref":"#/components/parameters/slug"}],"responses":{"200":{"description":"Every roster row, officers first.","content":{"application/json":{"schema":{"type":"object","properties":{"members":{"type":"array","items":{"$ref":"#/components/schemas/Member"}}},"required":["members"]}}}},"401":{"description":"The error, in the same envelope every route uses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The error, in the same envelope every route uses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"The error, in the same envelope every route uses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Add bare roster names.","parameters":[{"$ref":"#/components/parameters/slug"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"names":{"type":"array","items":{"type":"string","minLength":1,"maxLength":64},"minItems":1,"maxItems":200}},"required":["names"]}}}},"responses":{"200":{"description":"The full roster after the additions.","content":{"application/json":{"schema":{"type":"object","properties":{"members":{"type":"array","items":{"$ref":"#/components/schemas/Member"}}},"required":["members"]}}}},"400":{"description":"The error, in the same envelope every route uses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The error, in the same envelope every route uses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The error, in the same envelope every route uses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"The error, in the same envelope every route uses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/clubs/{slug}/members/{id}":{"delete":{"summary":"Remove a plain member. Officers cannot be touched with a key.","parameters":[{"$ref":"#/components/parameters/slug"},{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The roster row id."}],"responses":{"200":{"description":"Removed.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}},"401":{"description":"The error, in the same envelope every route uses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The error, in the same envelope every route uses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The error, in the same envelope every route uses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The error, in the same envelope every route uses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"The error, in the same envelope every route uses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/clubs/{slug}/announcements":{"post":{"summary":"Post an announcement. The byline is the key's name.","parameters":[{"$ref":"#/components/parameters/slug"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":120},"body":{"type":"string","minLength":1,"maxLength":4000},"pinned":{"type":"boolean","default":false},"startsAt":{"type":["integer","null"],"description":"Epoch milliseconds. Setting it turns the announcement into an event, listed on the club page and in the club's ICS feed."},"endsAt":{"type":["integer","null"],"description":"Epoch milliseconds. Requires startsAt at or before it."},"place":{"type":["string","null"],"maxLength":120}},"required":["title","body"]}}}},"responses":{"200":{"description":"The announcement as published.","content":{"application/json":{"schema":{"type":"object","properties":{"announcement":{"$ref":"#/components/schemas/Announcement"}},"required":["announcement"]}}}},"400":{"description":"The error, in the same envelope every route uses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The error, in the same envelope every route uses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The error, in the same envelope every route uses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"The error, in the same envelope every route uses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/clubs/{slug}/daily/{day}":{"get":{"summary":"The club's daily-challenge standing for a day.","description":"Omit the day (GET /api/v1/clubs/{slug}/daily) for today in UTC. Only linked members appear; par is null on a day with no puzzle set.","parameters":[{"$ref":"#/components/parameters/slug"},{"name":"day","in":"path","required":true,"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"description":"YYYY-MM-DD, UTC."}],"responses":{"200":{"description":"The day's leaderboard.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Daily"}}}},"400":{"description":"The error, in the same envelope every route uses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The error, in the same envelope every route uses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The error, in the same envelope every route uses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"The error, in the same envelope every route uses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearerKey":{"type":"http","scheme":"bearer","description":"A club API key (\"ck_...\"), minted by the club president on the site. Send it as: Authorization: Bearer ck_..."}},"parameters":{"slug":{"name":"slug","in":"path","required":true,"schema":{"type":"string"},"description":"The club's address on the site. Must be the key's own club."}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]},"Club":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"status":{"type":"string","enum":["pending","active","rejected"]},"description":{"type":["string","null"]},"city":{"type":["string","null"]},"region":{"type":["string","null"]},"country":{"type":["string","null"]},"contactEmail":{"type":["string","null"]},"contactPhone":{"type":["string","null"]},"website":{"type":["string","null"]},"logoUrl":{"type":["string","null"],"description":"A path under /uploads."},"memberCount":{"type":"integer"},"createdAt":{"type":"integer","description":"Epoch milliseconds."}}},"Member":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string","description":"The roster name, kept by the club."},"role":{"type":"string","enum":["president","vice","member"]},"linked":{"type":"boolean","description":"Tied to a player account."},"claimed":{"type":"boolean","description":"That account has signed in."},"you":{"type":"boolean","description":"Always false through the key API."},"joinedAt":{"type":"integer","description":"Epoch milliseconds."}}},"Announcement":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"body":{"type":"string"},"pinned":{"type":"boolean"},"authorName":{"type":"string"},"at":{"type":"integer","description":"Epoch milliseconds."},"startsAt":{"type":["integer","null"],"description":"Epoch milliseconds. Non-null means the announcement is an event."},"endsAt":{"type":["integer","null"],"description":"Epoch milliseconds."},"place":{"type":["string","null"]}}},"Daily":{"type":"object","properties":{"day":{"type":"string"},"par":{"type":["integer","null"],"description":"What a simulated club player typically scores on this day."},"rows":{"type":"array","items":{"type":"object","properties":{"memberId":{"type":"string"},"name":{"type":"string"},"best":{"type":"integer"},"attempts":{"type":"integer"},"streak":{"type":"integer","description":"Days running, under the daily challenge's freeze rule."}}}},"linkedMembers":{"type":"integer"},"playedToday":{"type":"integer","description":"How many linked members have a result for this day."}}}}}}