{"openapi":"3.1.0","info":{"title":"Hypha Hypha API","version":"2.1.0","description":"Headless API for the Hypha Hypha trust network. Authenticate via OAuth 2.1 (authorization endpoint /authorize, token endpoint /token, dynamic client registration /register). Scopes: profile:read, availability:read, availability:write, profile:write, invite:create, gigs:read, gigs:write."},"servers":[{"url":"https://hyphahypha.club"}],"components":{"securitySchemes":{"oauth":{"type":"oauth2","flows":{"authorizationCode":{"authorizationUrl":"https://hyphahypha.club/authorize","tokenUrl":"https://hyphahypha.club/token","scopes":{"profile:read":"see member profiles — names, interests, tags, availability, and contact details including email addresses","availability:read":"see who's available","availability:write":"update your availability","profile:write":"update your profile","invite:create":"invite new members","gigs:read":"read the gigs people have posted","gigs:write":"post gigs in your name, and close your own"}}}}},"schemas":{"Member":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"first_name":{"type":["string","null"]},"last_name":{"type":["string","null"]},"email":{"type":["string","null"]},"links":{"type":"array","items":{"type":"string"}},"status":{"enum":["active","left","removed","erased"]},"invited_by":{"type":["string","null"]},"created_at":{"type":"integer"}}},"AvailabilityMember":{"type":"object","description":"An entry in the availability roster. `email`, `links`, `interests`, and `tags` are present only when the token also holds profile:read.","properties":{"id":{"type":"string"},"name":{"type":"string"},"confirmed_at":{"type":["integer","null"],"description":"unix seconds of the last confirm/save, or null if never answered"},"fresh":{"type":"boolean","description":"true if confirmed_at is within the 21-day freshness window"},"weeks":{"type":"object","description":"Monday YYYY-MM-DD → pt | ft | na. Contains only weeks this member actually answered within the requested range.","additionalProperties":{"enum":["pt","ft","na"]}},"email":{"type":"string"},"links":{"type":"array","items":{"type":"string"}},"interests":{"type":["string","null"],"maxLength":90,"description":"one line, at most 90 characters; rendered on the member's roster row"},"tags":{"type":"array","items":{"type":"string"}}}},"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}}}}}}},"security":[{"oauth":[]}],"paths":{"/api/v1/me":{"get":{"operationId":"getMe","summary":"Own profile","security":[{"oauth":["profile:read"]}],"responses":{"200":{"description":"ok"}}},"patch":{"operationId":"updateMe","summary":"Update own profile (first_name, last_name, links, interests ≤ 90 chars, tags; email changes only via web UI)","security":[{"oauth":["profile:write"]}],"responses":{"200":{"description":"ok"},"403":{"description":"requires profile:write"}}}},"/api/v1/me/export":{"get":{"operationId":"exportMe","summary":"GDPR data export","security":[{"oauth":["profile:read"]}],"responses":{"200":{"description":"ok"}}}},"/api/v1/members":{"get":{"operationId":"listMembers","summary":"Directory (all non-erased members)","security":[{"oauth":["profile:read"]}],"responses":{"200":{"description":"ok"}}}},"/api/v1/members/{id}":{"get":{"operationId":"getMember","summary":"Profile with lineage and vouchers","security":[{"oauth":["profile:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"404":{"description":"no such member"}}}},"/api/v1/invites":{"post":{"operationId":"createInvite","summary":"Invite by email (uniform response; requires invite:create)","security":[{"oauth":["invite:create"]}],"responses":{"200":{"description":"ok"}}},"get":{"operationId":"listInvites","summary":"List own invites","security":[{"oauth":["invite:create"]}],"responses":{"200":{"description":"ok"}}}},"/api/v1/availability":{"get":{"operationId":"getAvailability","summary":"Availability roster, week by week. Each member's `weeks` map contains only the weeks that member actually answered inside the requested range — never every week in the range, and never weeks outside it. email/links/interests/tags are included only when the token also holds profile:read. `from`/`to` default to the next 8 weeks (today's week forward) but do not cap — request any range.","security":[{"oauth":["availability:read"]}],"parameters":[{"name":"week","in":"query","required":false,"schema":{"type":"string","format":"date"},"description":"a single Monday (YYYY-MM-DD); restricts `weeks` to that week and, unlike `from`/`to`, omits members with no answer for it entirely (from=to=week would keep them with an empty map). Takes precedence over `from`/`to` when any are supplied together."},{"name":"from","in":"query","required":false,"schema":{"type":"string","format":"date"},"description":"range start, a Monday (YYYY-MM-DD); defaults to the current week"},{"name":"to","in":"query","required":false,"schema":{"type":"string","format":"date"},"description":"range end, a Monday (YYYY-MM-DD); defaults to 8 weeks after from (or today's week if from is also omitted)"},{"name":"status","in":"query","required":false,"schema":{"type":"string"},"description":"comma-separated subset of pt,ft,na — keeps only members with at least one matching week in range"},{"name":"tag","in":"query","required":false,"schema":{"type":"string"},"description":"requires the token to also hold profile:read"}],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"type":"object","properties":{"members":{"type":"array","items":{"$ref":"#/components/schemas/AvailabilityMember"}}}}}}},"400":{"description":"bad_status | bad_week (a week/from/to param is not a Monday YYYY-MM-DD, or from is after to)"},"403":{"description":"missing_scope (tag filter requested without profile:read)"}}}},"/api/v1/me/availability":{"put":{"operationId":"setMyAvailability","summary":"Set your own weeks: body is { weeks: { \"YYYY-MM-DD\": \"pt\" | \"ft\" | \"na\" | null } }, one Monday per key; null clears that week; resets the ping clock","security":[{"oauth":["availability:write"]}],"responses":{"200":{"description":"ok"},"400":{"description":"bad_json | bad_week (a key is not a Monday YYYY-MM-DD, or a value is not pt, ft, na, or null)"}}}}}}