{"openapi":"3.1.0","info":{"title":"Mingoal API","version":"1.0.0","summary":"Read and write a single club's data with a club API key.","description":"The Mingoal REST API exposes one club's data — the club record, members,\nevents, RSVPs, announcements, blog posts, deals, merch products, the\ncommunity feed, and photo galleries.\n\n**Authentication.** Every request carries a club API key, either as\n`Authorization: Bearer <key>` or as `X-Api-Key: <key>`. The key is bound\nto exactly one club, so no request takes a club identifier — the key\ndetermines the scope of every read and write. Keys are created in a\nclub's Settings → Developers and API access is a Scale-plan feature.\n\n**Scopes.** Each key carries a list of scopes. Endpoints reject requests\nwhose key is missing the required scope with `403 insufficient_scope`.\n\n**Rate limiting.** 600 requests per hour per key. Exceeding it returns\n`429 rate_limited` with a `Retry-After` header.\n\n**Envelopes.** Collection endpoints return the array under a\nresource-named key alongside `total`, `limit`, and `offset` — for\nexample `{ \"events\": [...], \"total\": 42, \"limit\": 50, \"offset\": 0 }`.\nSingle-resource endpoints return `{ \"event\": { ... } }`. Errors always\nreturn `{ \"error\": { \"code\": \"...\", \"message\": \"...\" } }`.\n\n**CORS.** All endpoints send permissive CORS headers and answer\n`OPTIONS` preflight with 204, so browser clients can call the API\ndirectly. Treat an API key in browser code as public.","contact":{"name":"Mingoal support","email":"help@mingoal.com","url":"https://mingoal.com/contact"},"termsOfService":"https://mingoal.com/terms"},"servers":[{"url":"https://mingoal.com/api/v1","description":"Production"}],"externalDocs":{"description":"Developer guide","url":"https://mingoal.com/developers"},"tags":[{"name":"Auth","description":"Identify the calling key."},{"name":"Club","description":"The club the key is bound to."},{"name":"Members","description":"Club memberships."},{"name":"Events","description":"Events and their RSVPs."},{"name":"Announcements","description":"Club announcements."},{"name":"Blog","description":"Club blog posts."},{"name":"Deals","description":"Member deals and perks."},{"name":"Products","description":"Merch store products."},{"name":"Content","description":"Community feed and photo galleries."}],"paths":{"/me":{"get":{"tags":["Auth"],"operationId":"getMe","summary":"Identify the calling key","description":"Returns the key id, its scopes, and basic details of the club it is bound to. Requires authentication but no particular scope — the cheapest way to verify a key works.","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"responses":{"200":{"description":"The key, its scopes, and its club.","content":{"application/json":{"schema":{"type":"object","properties":{"key_id":{"type":"string","format":"uuid"},"scopes":{"type":"array","items":{"$ref":"#/components/schemas/Scope"}},"club":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"slug":{"type":"string"},"name":{"type":"string"},"plan":{"type":"string","enum":["free","pro","grow","scale"]}},"required":["id","slug","name"]}},"required":["key_id","scopes","club"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`club_not_found` — the key's club no longer exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}},"options":{"tags":["Auth"],"summary":"CORS preflight","description":"Returns 204 with `Access-Control-Allow-Origin: *`, the allowed methods (GET, POST, PATCH, DELETE, OPTIONS), the allowed headers (Authorization, Content-Type, X-Api-Key), and a 24-hour max-age. No authentication required.","security":[],"responses":{"204":{"description":"Preflight accepted. No body."}}}},"/club":{"get":{"tags":["Club"],"operationId":"getClub","summary":"Get the club","description":"Returns the key's club with the public-safe field set. Billing internals such as Stripe customer and subscription identifiers are never included.","security":[{"bearerAuth":["club:read"]},{"apiKeyAuth":["club:read"]}],"responses":{"200":{"description":"The club.","content":{"application/json":{"schema":{"type":"object","properties":{"club":{"$ref":"#/components/schemas/Club"}},"required":["club"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}},"patch":{"tags":["Club"],"operationId":"updateClub","summary":"Update the club","description":"Updates editable club fields. Any property not in the request schema is ignored rather than rejected. Responds 400 `no_fields` if none of the editable fields are present.","security":[{"bearerAuth":["club:write"]},{"apiKeyAuth":["club:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClubUpdate"}}}},"responses":{"200":{"description":"The updated club.","content":{"application/json":{"schema":{"type":"object","properties":{"club":{"$ref":"#/components/schemas/Club"}},"required":["club"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}},"options":{"tags":["Auth"],"summary":"CORS preflight","description":"Returns 204 with `Access-Control-Allow-Origin: *`, the allowed methods (GET, POST, PATCH, DELETE, OPTIONS), the allowed headers (Authorization, Content-Type, X-Api-Key), and a 24-hour max-age. No authentication required.","security":[],"responses":{"204":{"description":"Preflight accepted. No body."}}}},"/members":{"get":{"tags":["Members"],"operationId":"listMembers","summary":"List members","description":"Club memberships, newest join first.","security":[{"bearerAuth":["members:read"]},{"apiKeyAuth":["members:read"]}],"parameters":[{"name":"status","in":"query","required":false,"description":"Filter to a single membership status.","schema":{"type":"string"}},{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/offset"}],"responses":{"200":{"description":"A page of members.","content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"members":{"type":"array","items":{"$ref":"#/components/schemas/Member"}}},"required":["members"]},{"$ref":"#/components/schemas/PageMeta"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}},"options":{"tags":["Auth"],"summary":"CORS preflight","description":"Returns 204 with `Access-Control-Allow-Origin: *`, the allowed methods (GET, POST, PATCH, DELETE, OPTIONS), the allowed headers (Authorization, Content-Type, X-Api-Key), and a 24-hour max-age. No authentication required.","security":[],"responses":{"204":{"description":"Preflight accepted. No body."}}}},"/members/{id}":{"parameters":[{"$ref":"#/components/parameters/id"}],"get":{"tags":["Members"],"operationId":"getMember","summary":"Get a member","security":[{"bearerAuth":["members:read"]},{"apiKeyAuth":["members:read"]}],"responses":{"200":{"description":"The membership.","content":{"application/json":{"schema":{"type":"object","properties":{"member":{"$ref":"#/components/schemas/Member"}},"required":["member"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}},"patch":{"tags":["Members"],"operationId":"updateMember","summary":"Update a member's role or status","description":"Only `role` and `status` are editable. There is no endpoint to create or delete a membership over the API — members join through the app.","security":[{"bearerAuth":["members:write"]},{"apiKeyAuth":["members:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberUpdate"}}}},"responses":{"200":{"description":"The updated membership.","content":{"application/json":{"schema":{"type":"object","properties":{"member":{"$ref":"#/components/schemas/Member"}},"required":["member"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}},"options":{"tags":["Auth"],"summary":"CORS preflight","description":"Returns 204 with `Access-Control-Allow-Origin: *`, the allowed methods (GET, POST, PATCH, DELETE, OPTIONS), the allowed headers (Authorization, Content-Type, X-Api-Key), and a 24-hour max-age. No authentication required.","security":[],"responses":{"204":{"description":"Preflight accepted. No body."}}}},"/events":{"get":{"tags":["Events"],"operationId":"listEvents","summary":"List events","description":"By default events are returned newest start-date first. With `upcoming=true` the list is filtered to events that have not started and is ordered soonest-first instead.","security":[{"bearerAuth":["events:read"]},{"apiKeyAuth":["events:read"]}],"parameters":[{"name":"upcoming","in":"query","required":false,"description":"Set to `true` to return only future events, ordered soonest first. Any other value is treated as false.","schema":{"type":"string","enum":["true","false"]}},{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/offset"}],"responses":{"200":{"description":"A page of events.","content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"events":{"type":"array","items":{"$ref":"#/components/schemas/Event"}}},"required":["events"]},{"$ref":"#/components/schemas/PageMeta"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}},"post":{"tags":["Events"],"operationId":"createEvent","summary":"Create an event","description":"`title` and `starts_at` are required.","security":[{"bearerAuth":["events:write"]},{"apiKeyAuth":["events:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventCreate"}}}},"responses":{"201":{"description":"The created event.","content":{"application/json":{"schema":{"type":"object","properties":{"event":{"$ref":"#/components/schemas/Event"}},"required":["event"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}},"options":{"tags":["Auth"],"summary":"CORS preflight","description":"Returns 204 with `Access-Control-Allow-Origin: *`, the allowed methods (GET, POST, PATCH, DELETE, OPTIONS), the allowed headers (Authorization, Content-Type, X-Api-Key), and a 24-hour max-age. No authentication required.","security":[],"responses":{"204":{"description":"Preflight accepted. No body."}}}},"/events/{id}":{"parameters":[{"$ref":"#/components/parameters/id"}],"get":{"tags":["Events"],"operationId":"getEvent","summary":"Get an event","security":[{"bearerAuth":["events:read"]},{"apiKeyAuth":["events:read"]}],"responses":{"200":{"description":"The event.","content":{"application/json":{"schema":{"type":"object","properties":{"event":{"$ref":"#/components/schemas/Event"}},"required":["event"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}},"patch":{"tags":["Events"],"operationId":"updateEvent","summary":"Update an event","security":[{"bearerAuth":["events:write"]},{"apiKeyAuth":["events:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventUpdate"}}}},"responses":{"200":{"description":"The updated event.","content":{"application/json":{"schema":{"type":"object","properties":{"event":{"$ref":"#/components/schemas/Event"}},"required":["event"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}},"delete":{"tags":["Events"],"operationId":"deleteEvent","summary":"Delete an event","security":[{"bearerAuth":["events:write"]},{"apiKeyAuth":["events:write"]}],"responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Deleted"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}},"options":{"tags":["Auth"],"summary":"CORS preflight","description":"Returns 204 with `Access-Control-Allow-Origin: *`, the allowed methods (GET, POST, PATCH, DELETE, OPTIONS), the allowed headers (Authorization, Content-Type, X-Api-Key), and a 24-hour max-age. No authentication required.","security":[],"responses":{"204":{"description":"Preflight accepted. No body."}}}},"/events/{id}/rsvps":{"parameters":[{"$ref":"#/components/parameters/id"}],"get":{"tags":["Events"],"operationId":"listEventRsvps","summary":"List RSVPs for an event","description":"Returns 404 `event_not_found` if the event does not exist or belongs to another club.","security":[{"bearerAuth":["rsvps:read"]},{"apiKeyAuth":["rsvps:read"]}],"parameters":[{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/offset"}],"responses":{"200":{"description":"A page of RSVPs, newest first.","content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"rsvps":{"type":"array","items":{"$ref":"#/components/schemas/Rsvp"}}},"required":["rsvps"]},{"$ref":"#/components/schemas/PageMeta"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`event_not_found` — no such event on this club.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}},"post":{"tags":["Events"],"operationId":"createEventRsvp","summary":"Create an RSVP","description":"`user_id` is required and must be an existing Mingoal user.","security":[{"bearerAuth":["rsvps:write"]},{"apiKeyAuth":["rsvps:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RsvpCreate"}}}},"responses":{"201":{"description":"The created RSVP.","content":{"application/json":{"schema":{"type":"object","properties":{"rsvp":{"$ref":"#/components/schemas/Rsvp"}},"required":["rsvp"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`event_not_found` — no such event on this club.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}},"options":{"tags":["Auth"],"summary":"CORS preflight","description":"Returns 204 with `Access-Control-Allow-Origin: *`, the allowed methods (GET, POST, PATCH, DELETE, OPTIONS), the allowed headers (Authorization, Content-Type, X-Api-Key), and a 24-hour max-age. No authentication required.","security":[],"responses":{"204":{"description":"Preflight accepted. No body."}}}},"/announcements":{"get":{"tags":["Announcements"],"operationId":"listAnnouncements","summary":"List announcements","description":"Newest sent first.","security":[{"bearerAuth":["announcements:read"]},{"apiKeyAuth":["announcements:read"]}],"parameters":[{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/offset"}],"responses":{"200":{"description":"A page of announcements.","content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"announcements":{"type":"array","items":{"$ref":"#/components/schemas/Announcement"}}},"required":["announcements"]},{"$ref":"#/components/schemas/PageMeta"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}},"post":{"tags":["Announcements"],"operationId":"createAnnouncement","summary":"Post an announcement","description":"`title` and `body` are required. `sent_at` is stamped server-side at creation.","security":[{"bearerAuth":["announcements:write"]},{"apiKeyAuth":["announcements:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnnouncementCreate"}}}},"responses":{"201":{"description":"The created announcement.","content":{"application/json":{"schema":{"type":"object","properties":{"announcement":{"$ref":"#/components/schemas/Announcement"}},"required":["announcement"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}},"options":{"tags":["Auth"],"summary":"CORS preflight","description":"Returns 204 with `Access-Control-Allow-Origin: *`, the allowed methods (GET, POST, PATCH, DELETE, OPTIONS), the allowed headers (Authorization, Content-Type, X-Api-Key), and a 24-hour max-age. No authentication required.","security":[],"responses":{"204":{"description":"Preflight accepted. No body."}}}},"/announcements/{id}":{"parameters":[{"$ref":"#/components/parameters/id"}],"get":{"tags":["Announcements"],"operationId":"getAnnouncement","summary":"Get an announcement","security":[{"bearerAuth":["announcements:read"]},{"apiKeyAuth":["announcements:read"]}],"responses":{"200":{"description":"The announcement.","content":{"application/json":{"schema":{"type":"object","properties":{"announcement":{"$ref":"#/components/schemas/Announcement"}},"required":["announcement"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}},"patch":{"tags":["Announcements"],"operationId":"updateAnnouncement","summary":"Update an announcement","security":[{"bearerAuth":["announcements:write"]},{"apiKeyAuth":["announcements:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnnouncementUpdate"}}}},"responses":{"200":{"description":"The updated announcement.","content":{"application/json":{"schema":{"type":"object","properties":{"announcement":{"$ref":"#/components/schemas/Announcement"}},"required":["announcement"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}},"delete":{"tags":["Announcements"],"operationId":"deleteAnnouncement","summary":"Delete an announcement","security":[{"bearerAuth":["announcements:write"]},{"apiKeyAuth":["announcements:write"]}],"responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Deleted"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}},"options":{"tags":["Auth"],"summary":"CORS preflight","description":"Returns 204 with `Access-Control-Allow-Origin: *`, the allowed methods (GET, POST, PATCH, DELETE, OPTIONS), the allowed headers (Authorization, Content-Type, X-Api-Key), and a 24-hour max-age. No authentication required.","security":[],"responses":{"204":{"description":"Preflight accepted. No body."}}}},"/blog":{"get":{"tags":["Blog"],"operationId":"listBlogPosts","summary":"List blog posts","description":"Newest created first. Note the array key is `posts`, not `blog`.","security":[{"bearerAuth":["blog:read"]},{"apiKeyAuth":["blog:read"]}],"parameters":[{"name":"status","in":"query","required":false,"description":"Filter by publication status, e.g. `published`.","schema":{"type":"string"}},{"name":"visibility","in":"query","required":false,"description":"Filter by visibility, e.g. `public` or `members`.","schema":{"type":"string"}},{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/offset"}],"responses":{"200":{"description":"A page of blog posts, under the `posts` key.","content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"posts":{"type":"array","items":{"$ref":"#/components/schemas/BlogPost"}}},"required":["posts"]},{"$ref":"#/components/schemas/PageMeta"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}},"post":{"tags":["Blog"],"operationId":"createBlogPost","summary":"Create a blog post","description":"`title`, `slug`, and `content_html` are required.","security":[{"bearerAuth":["blog:write"]},{"apiKeyAuth":["blog:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogPostCreate"}}}},"responses":{"201":{"description":"The created post, under the `post` key.","content":{"application/json":{"schema":{"type":"object","properties":{"post":{"$ref":"#/components/schemas/BlogPost"}},"required":["post"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}},"options":{"tags":["Auth"],"summary":"CORS preflight","description":"Returns 204 with `Access-Control-Allow-Origin: *`, the allowed methods (GET, POST, PATCH, DELETE, OPTIONS), the allowed headers (Authorization, Content-Type, X-Api-Key), and a 24-hour max-age. No authentication required.","security":[],"responses":{"204":{"description":"Preflight accepted. No body."}}}},"/blog/{id}":{"parameters":[{"$ref":"#/components/parameters/id"}],"get":{"tags":["Blog"],"operationId":"getBlogPost","summary":"Get a blog post","security":[{"bearerAuth":["blog:read"]},{"apiKeyAuth":["blog:read"]}],"responses":{"200":{"description":"The post.","content":{"application/json":{"schema":{"type":"object","properties":{"post":{"$ref":"#/components/schemas/BlogPost"}},"required":["post"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}},"patch":{"tags":["Blog"],"operationId":"updateBlogPost","summary":"Update a blog post","security":[{"bearerAuth":["blog:write"]},{"apiKeyAuth":["blog:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogPostUpdate"}}}},"responses":{"200":{"description":"The updated post.","content":{"application/json":{"schema":{"type":"object","properties":{"post":{"$ref":"#/components/schemas/BlogPost"}},"required":["post"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}},"delete":{"tags":["Blog"],"operationId":"deleteBlogPost","summary":"Delete a blog post","security":[{"bearerAuth":["blog:write"]},{"apiKeyAuth":["blog:write"]}],"responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Deleted"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}},"options":{"tags":["Auth"],"summary":"CORS preflight","description":"Returns 204 with `Access-Control-Allow-Origin: *`, the allowed methods (GET, POST, PATCH, DELETE, OPTIONS), the allowed headers (Authorization, Content-Type, X-Api-Key), and a 24-hour max-age. No authentication required.","security":[],"responses":{"204":{"description":"Preflight accepted. No body."}}}},"/deals":{"get":{"tags":["Deals"],"operationId":"listDeals","summary":"List deals","description":"Newest created first.","security":[{"bearerAuth":["deals:read"]},{"apiKeyAuth":["deals:read"]}],"parameters":[{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/offset"}],"responses":{"200":{"description":"A page of deals.","content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"deals":{"type":"array","items":{"$ref":"#/components/schemas/Deal"}}},"required":["deals"]},{"$ref":"#/components/schemas/PageMeta"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}},"post":{"tags":["Deals"],"operationId":"createDeal","summary":"Create a deal","description":"`title` is required.","security":[{"bearerAuth":["deals:write"]},{"apiKeyAuth":["deals:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DealCreate"}}}},"responses":{"201":{"description":"The created deal.","content":{"application/json":{"schema":{"type":"object","properties":{"deal":{"$ref":"#/components/schemas/Deal"}},"required":["deal"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}},"options":{"tags":["Auth"],"summary":"CORS preflight","description":"Returns 204 with `Access-Control-Allow-Origin: *`, the allowed methods (GET, POST, PATCH, DELETE, OPTIONS), the allowed headers (Authorization, Content-Type, X-Api-Key), and a 24-hour max-age. No authentication required.","security":[],"responses":{"204":{"description":"Preflight accepted. No body."}}}},"/deals/{id}":{"parameters":[{"$ref":"#/components/parameters/id"}],"get":{"tags":["Deals"],"operationId":"getDeal","summary":"Get a deal","security":[{"bearerAuth":["deals:read"]},{"apiKeyAuth":["deals:read"]}],"responses":{"200":{"description":"The deal.","content":{"application/json":{"schema":{"type":"object","properties":{"deal":{"$ref":"#/components/schemas/Deal"}},"required":["deal"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}},"patch":{"tags":["Deals"],"operationId":"updateDeal","summary":"Update a deal","security":[{"bearerAuth":["deals:write"]},{"apiKeyAuth":["deals:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DealUpdate"}}}},"responses":{"200":{"description":"The updated deal.","content":{"application/json":{"schema":{"type":"object","properties":{"deal":{"$ref":"#/components/schemas/Deal"}},"required":["deal"]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}},"delete":{"tags":["Deals"],"operationId":"deleteDeal","summary":"Delete a deal","security":[{"bearerAuth":["deals:write"]},{"apiKeyAuth":["deals:write"]}],"responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Deleted"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}},"options":{"tags":["Auth"],"summary":"CORS preflight","description":"Returns 204 with `Access-Control-Allow-Origin: *`, the allowed methods (GET, POST, PATCH, DELETE, OPTIONS), the allowed headers (Authorization, Content-Type, X-Api-Key), and a 24-hour max-age. No authentication required.","security":[],"responses":{"204":{"description":"Preflight accepted. No body."}}}},"/products":{"get":{"tags":["Products"],"operationId":"listProducts","summary":"List merch products","description":"Newest created first. Read-only over the API — products are managed in the app.","security":[{"bearerAuth":["products:read"]},{"apiKeyAuth":["products:read"]}],"parameters":[{"name":"active","in":"query","required":false,"description":"Set to `true` to return only active products. Any other value returns all products.","schema":{"type":"string","enum":["true","false"]}},{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/offset"}],"responses":{"200":{"description":"A page of products.","content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"products":{"type":"array","items":{"$ref":"#/components/schemas/Product"}}},"required":["products"]},{"$ref":"#/components/schemas/PageMeta"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}},"options":{"tags":["Auth"],"summary":"CORS preflight","description":"Returns 204 with `Access-Control-Allow-Origin: *`, the allowed methods (GET, POST, PATCH, DELETE, OPTIONS), the allowed headers (Authorization, Content-Type, X-Api-Key), and a 24-hour max-age. No authentication required.","security":[],"responses":{"204":{"description":"Preflight accepted. No body."}}}},"/feed":{"get":{"tags":["Content"],"operationId":"listFeedPosts","summary":"List community feed posts","description":"Visible (non-hidden) feed posts, newest first, under the `posts` key. Requires the `content:read` scope, not `blog:read`.","security":[{"bearerAuth":["content:read"]},{"apiKeyAuth":["content:read"]}],"parameters":[{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/offset"}],"responses":{"200":{"description":"A page of feed posts, under the `posts` key.","content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"posts":{"type":"array","items":{"$ref":"#/components/schemas/FeedPost"}}},"required":["posts"]},{"$ref":"#/components/schemas/PageMeta"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}},"options":{"tags":["Auth"],"summary":"CORS preflight","description":"Returns 204 with `Access-Control-Allow-Origin: *`, the allowed methods (GET, POST, PATCH, DELETE, OPTIONS), the allowed headers (Authorization, Content-Type, X-Api-Key), and a 24-hour max-age. No authentication required.","security":[],"responses":{"204":{"description":"Preflight accepted. No body."}}}},"/gallery":{"get":{"tags":["Content"],"operationId":"listGallery","summary":"List albums, or one album's photos","description":"Without `album_id` this returns the club's albums under the `albums` key, newest first. With `album_id` it returns that album's photos under the `photos` key, ordered by `sort_order`. The response shape therefore depends on the query — branch on which key is present.","security":[{"bearerAuth":["content:read"]},{"apiKeyAuth":["content:read"]}],"parameters":[{"name":"album_id","in":"query","required":false,"description":"When supplied, return this album's photos instead of the album list.","schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/offset"}],"responses":{"200":{"description":"A page of albums, or of photos when `album_id` is supplied.","content":{"application/json":{"schema":{"oneOf":[{"title":"Albums","allOf":[{"type":"object","properties":{"albums":{"type":"array","items":{"$ref":"#/components/schemas/Album"}}},"required":["albums"]},{"$ref":"#/components/schemas/PageMeta"}]},{"title":"Photos","allOf":[{"type":"object","properties":{"photos":{"type":"array","items":{"$ref":"#/components/schemas/Photo"}}},"required":["photos"]},{"$ref":"#/components/schemas/PageMeta"}]}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`album_not_found` — no such album on this club.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}},"options":{"tags":["Auth"],"summary":"CORS preflight","description":"Returns 204 with `Access-Control-Allow-Origin: *`, the allowed methods (GET, POST, PATCH, DELETE, OPTIONS), the allowed headers (Authorization, Content-Type, X-Api-Key), and a 24-hour max-age. No authentication required.","security":[],"responses":{"204":{"description":"Preflight accepted. No body."}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Club API key sent as `Authorization: Bearer mg_live_…`. Create keys in Settings → Developers (Scale plan)."},"apiKeyAuth":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"The same club API key, sent in the `X-Api-Key` header instead of `Authorization`. Equivalent to `bearerAuth`; send one or the other."}},"parameters":{"limit":{"name":"limit","in":"query","required":false,"description":"Page size. Clamped to 1–100; non-numeric values fall back to the default.","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},"offset":{"name":"offset","in":"query","required":false,"description":"Zero-based row offset. Negative values are clamped to 0.","schema":{"type":"integer","minimum":0,"default":0}},"id":{"name":"id","in":"path","required":true,"description":"Resource identifier.","schema":{"type":"string","format":"uuid"}}},"responses":{"BadRequest":{"description":"Malformed request. Codes: `invalid_json`, `invalid_body`, `missing_fields`, `no_fields`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Missing or invalid API key. Codes: `missing_key`, `invalid_key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"`insufficient_scope` — the key does not carry the scope this endpoint requires.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"`not_found` — no such resource on the key's club.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"`rate_limited` — more than 600 requests in the last hour for this key. A `Retry-After` header gives the wait in seconds.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"ServerError":{"description":"`db_error` — the request was valid but the datastore returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Scope":{"type":"string","description":"An API key permission.","enum":["club:read","members:read","events:read","rsvps:read","announcements:read","blog:read","deals:read","products:read","content:read","club:write","members:write","events:write","rsvps:write","announcements:write","blog:write","deals:write"]},"Error":{"type":"object","description":"The uniform error envelope returned by every endpoint.","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code.","examples":["insufficient_scope","not_found","rate_limited"]},"message":{"type":"string","description":"Human-readable explanation."}},"required":["code","message"]}},"required":["error"]},"PageMeta":{"type":"object","description":"Pagination metadata attached to every collection response.","properties":{"total":{"type":"integer","description":"Total matching rows, ignoring limit/offset."},"limit":{"type":"integer","description":"Page size that was applied."},"offset":{"type":"integer","description":"Offset that was applied."}},"required":["total","limit","offset"]},"Deleted":{"type":"object","description":"Returned by DELETE endpoints.","properties":{"deleted":{"type":"boolean","const":true},"id":{"type":"string","format":"uuid"}},"required":["deleted","id"]},"UserSummary":{"type":"object","description":"Embedded public profile of a Mingoal user.","properties":{"id":{"type":"string","format":"uuid"},"display_name":{"type":["string","null"]},"avatar_url":{"type":["string","null"]},"bio":{"type":["string","null"]}},"required":["id"]},"Club":{"type":"object","description":"Public-safe club record. Billing identifiers are deliberately excluded.","properties":{"id":{"type":"string","format":"uuid"},"slug":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"category":{"type":["string","null"]},"category_custom":{"type":["string","null"]},"cover_image_url":{"type":["string","null"]},"favicon_url":{"type":["string","null"]},"logo_url":{"type":["string","null"]},"location":{"type":["string","null"]},"tagline":{"type":["string","null"]},"accent_color":{"type":["string","null"]},"is_public":{"type":"boolean"},"membership_type":{"type":["string","null"]},"membership_price_cents":{"type":["integer","null"]},"membership_currency":{"type":["string","null"]},"membership_interval":{"type":["string","null"]},"member_count":{"type":["integer","null"]},"plan_tier":{"type":["string","null"],"enum":["free","pro","grow","scale",null]},"social_links":{"type":["object","null"],"additionalProperties":true},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}},"required":["id","slug","name"]},"ClubUpdate":{"type":"object","description":"Editable club fields (CLUB_WRITABLE_FIELDS). Unknown properties are ignored. `updated_at` is stamped server-side.","properties":{"name":{"type":"string"},"description":{"type":["string","null"]},"tagline":{"type":["string","null"]},"location":{"type":["string","null"]},"category_custom":{"type":["string","null"]},"cover_image_url":{"type":["string","null"]},"logo_url":{"type":["string","null"]},"favicon_url":{"type":["string","null"]},"accent_color":{"type":["string","null"]},"is_public":{"type":"boolean"},"social_links":{"type":["object","null"],"additionalProperties":true}},"additionalProperties":false},"Member":{"type":"object","description":"A club membership, with the member's public profile.","properties":{"id":{"type":"string","format":"uuid"},"role":{"type":["string","null"]},"status":{"type":["string","null"]},"joined_at":{"type":["string","null"],"format":"date-time"},"user_id":{"type":["string","null"],"format":"uuid"},"users":{"oneOf":[{"$ref":"#/components/schemas/UserSummary"},{"type":"null"}]}},"required":["id"]},"MemberUpdate":{"type":"object","description":"At least one property must be present, or 400 `no_fields`.","properties":{"role":{"type":"string"},"status":{"type":"string"}},"additionalProperties":false},"Event":{"type":"object","description":"A club event. The full row is returned; the properties below are the stable, documented subset.","properties":{"id":{"type":"string","format":"uuid"},"club_id":{"type":"string","format":"uuid"},"title":{"type":"string"},"description":{"type":["string","null"]},"location":{"type":["string","null"]},"starts_at":{"type":"string","format":"date-time"},"ends_at":{"type":["string","null"],"format":"date-time"},"is_free":{"type":["boolean","null"]},"ticket_price":{"type":["number","null"]},"max_attendees":{"type":["integer","null"]},"cover_image_url":{"type":["string","null"]},"visibility":{"type":["string","null"]},"is_recurring":{"type":["boolean","null"]},"recurrence_rule":{"type":["string","null"]},"created_at":{"type":["string","null"],"format":"date-time"}},"required":["id","title","starts_at"],"additionalProperties":true},"EventCreate":{"type":"object","description":"`club_id` is taken from the API key and must not be sent.","properties":{"title":{"type":"string"},"description":{"type":["string","null"]},"location":{"type":["string","null"]},"starts_at":{"type":"string","format":"date-time"},"ends_at":{"type":["string","null"],"format":"date-time"},"is_free":{"type":"boolean"},"ticket_price":{"type":["number","null"]},"max_attendees":{"type":["integer","null"]},"cover_image_url":{"type":["string","null"]},"visibility":{"type":"string"},"is_recurring":{"type":"boolean"},"recurrence_rule":{"type":["string","null"]}},"required":["title","starts_at"],"additionalProperties":false},"EventUpdate":{"type":"object","description":"At least one property must be present, or 400 `no_fields`.","properties":{"title":{"type":"string"},"description":{"type":["string","null"]},"location":{"type":["string","null"]},"starts_at":{"type":"string","format":"date-time"},"ends_at":{"type":["string","null"],"format":"date-time"},"is_free":{"type":"boolean"},"ticket_price":{"type":["number","null"]},"max_attendees":{"type":["integer","null"]},"cover_image_url":{"type":["string","null"]},"visibility":{"type":"string"},"is_recurring":{"type":"boolean"},"recurrence_rule":{"type":["string","null"]}},"additionalProperties":false},"Rsvp":{"type":"object","description":"An RSVP. The list endpoint embeds the user's public profile; the create endpoint returns the bare row.","properties":{"id":{"type":"string","format":"uuid"},"event_id":{"type":"string","format":"uuid"},"user_id":{"type":"string","format":"uuid"},"status":{"type":["string","null"]},"tier_id":{"type":["string","null"],"format":"uuid"},"created_at":{"type":["string","null"],"format":"date-time"},"users":{"oneOf":[{"$ref":"#/components/schemas/UserSummary"},{"type":"null"}]}},"required":["id"],"additionalProperties":true},"RsvpCreate":{"type":"object","description":"`event_id` comes from the path and must not be sent.","properties":{"user_id":{"type":"string","format":"uuid"},"status":{"type":"string"},"tier_id":{"type":["string","null"],"format":"uuid"}},"required":["user_id"],"additionalProperties":false},"Announcement":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"club_id":{"type":"string","format":"uuid"},"title":{"type":"string"},"body":{"type":"string"},"comments_enabled":{"type":["boolean","null"]},"media":{"type":["array","object","null"],"additionalProperties":true},"sent_at":{"type":["string","null"],"format":"date-time"}},"required":["id","title","body"],"additionalProperties":true},"AnnouncementCreate":{"type":"object","properties":{"title":{"type":"string"},"body":{"type":"string"},"comments_enabled":{"type":"boolean"},"media":{"type":["array","object","null"],"additionalProperties":true}},"required":["title","body"],"additionalProperties":false},"AnnouncementUpdate":{"type":"object","description":"At least one property must be present, or 400 `no_fields`.","properties":{"title":{"type":"string"},"body":{"type":"string"},"comments_enabled":{"type":"boolean"},"media":{"type":["array","object","null"],"additionalProperties":true}},"additionalProperties":false},"BlogPost":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"club_id":{"type":"string","format":"uuid"},"slug":{"type":"string"},"title":{"type":"string"},"excerpt":{"type":["string","null"]},"cover_image_url":{"type":["string","null"]},"content_html":{"type":["string","null"]},"visibility":{"type":["string","null"]},"status":{"type":["string","null"]},"published_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}},"required":["id","slug","title"],"additionalProperties":true},"BlogPostCreate":{"type":"object","properties":{"slug":{"type":"string"},"title":{"type":"string"},"excerpt":{"type":["string","null"]},"cover_image_url":{"type":["string","null"]},"content_html":{"type":"string"},"visibility":{"type":"string"},"status":{"type":"string"},"published_at":{"type":["string","null"],"format":"date-time"}},"required":["slug","title","content_html"],"additionalProperties":false},"BlogPostUpdate":{"type":"object","description":"At least one property must be present, or 400 `no_fields`.","properties":{"slug":{"type":"string"},"title":{"type":"string"},"excerpt":{"type":["string","null"]},"cover_image_url":{"type":["string","null"]},"content_html":{"type":"string"},"visibility":{"type":"string"},"status":{"type":"string"},"published_at":{"type":["string","null"],"format":"date-time"}},"additionalProperties":false},"Deal":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"club_id":{"type":"string","format":"uuid"},"title":{"type":"string"},"description":{"type":["string","null"]},"discount_code":{"type":["string","null"]},"redeem_url":{"type":["string","null"]},"image_url":{"type":["string","null"]},"category":{"type":["string","null"]},"is_active":{"type":["boolean","null"]},"created_at":{"type":["string","null"],"format":"date-time"}},"required":["id","title"],"additionalProperties":true},"DealCreate":{"type":"object","properties":{"title":{"type":"string"},"description":{"type":["string","null"]},"discount_code":{"type":["string","null"]},"redeem_url":{"type":["string","null"]},"image_url":{"type":["string","null"]},"category":{"type":["string","null"]},"is_active":{"type":"boolean"}},"required":["title"],"additionalProperties":false},"DealUpdate":{"type":"object","description":"At least one property must be present, or 400 `no_fields`.","properties":{"title":{"type":"string"},"description":{"type":["string","null"]},"discount_code":{"type":["string","null"]},"redeem_url":{"type":["string","null"]},"image_url":{"type":["string","null"]},"category":{"type":["string","null"]},"is_active":{"type":"boolean"}},"additionalProperties":false},"Product":{"type":"object","description":"A merch store product. Read-only over the API.","properties":{"id":{"type":"string","format":"uuid"},"club_id":{"type":"string","format":"uuid"},"is_active":{"type":["boolean","null"]},"created_at":{"type":["string","null"],"format":"date-time"}},"required":["id"],"additionalProperties":true},"FeedPost":{"type":"object","description":"A community feed post. Hidden posts are never returned by the API.","properties":{"id":{"type":"string","format":"uuid"},"club_id":{"type":"string","format":"uuid"},"is_hidden":{"type":"boolean","const":false},"created_at":{"type":["string","null"],"format":"date-time"}},"required":["id"],"additionalProperties":true},"Album":{"type":"object","description":"A club photo album.","properties":{"id":{"type":"string","format":"uuid"},"club_id":{"type":"string","format":"uuid"},"created_at":{"type":["string","null"],"format":"date-time"}},"required":["id"],"additionalProperties":true},"Photo":{"type":"object","description":"A photo inside an album, ordered by `sort_order`.","properties":{"id":{"type":"string","format":"uuid"},"album_id":{"type":"string","format":"uuid"},"sort_order":{"type":["integer","null"]}},"required":["id"],"additionalProperties":true}}},"security":[{"bearerAuth":[]},{"apiKeyAuth":[]}]}