A deprecation policy for the Mighty APIWe now publish a deprecation policy that sets out how we handle breaking
changes to the API. We announce a deprecation at least three months before we deploy the breaking
change, record the deprecation and its replacement on this changelog, mark the deprecated element
in the GraphQL schema, and notify you in-app if we detect that your integration still uses it.
Security updates, critical bug fixes, and changes required to protect the reliability or integrity
of the platform are outside that notice period.The policy also defines what we count as a breaking change, for GraphQL and for REST, and which
additions are compatible, so you can tell from a changelog entry whether you need to act.
The member roster pages all the way through
network.members no longer stops at 10,000 members. Previously pageInfo.hasNextPage went false
once you had paged that far even though totalCount reported the whole roster, so a Network with
more members than that could not be exported — and the same ceiling applied to members(spaceId:)
and members(planId:). Keep paging until hasNextPage is false and you now reach the end of any
roster. See the members reference for the full set of filters and fields.Two things to know about the pages you get. A page can hold fewer members than you asked for, or
none at all, and still have more after it — that happens when a member was removed just after the
roster read them, and pageInfo.hasNextPage is the only end-of-roster signal. And totalCount is
re-counted on every request, so treat it as the current roster size rather than counting your pages
against it. A member who has just joined may not appear yet.Cursors keep their opaque form but their contents have changed, including the per-edge cursors on
edges { cursor }. An iteration that was part-way through when this shipped is rejected with a
BAD_USER_INPUT error naming the cursor — start that iteration again from the first page. Always
page from pageInfo.endCursor rather than from an edge’s own cursor; see the
pagination guide for the general pattern.A cursor now belongs to the run that created it, so hold sort, sortOrder and ambassador
constant across a paging run — and hold term constant too when you sort by ROLE, where the
term’s relevance is part of the ordering. Change one of those and the cursor is rejected with
BAD_USER_INPUT rather than quietly returning the wrong page; start again from the first page
under the ordering you want. Narrowing the other filters mid-run is still fine. The Ambassador
roster’s cursors are not interchangeable with the full roster’s in either direction.A new sort key for roster exports: RESOURCE_IDMemberSort gains RESOURCE_ID, which sorts by the member’s resource ID. Use it whenever you are
reading a whole roster: it is the only sort key that never changes for a member, so a full
enumeration returns every member exactly once. Choose it on your first call — switching to it while
you already hold a cursor is rejected, because a cursor belongs to the sort that created it. Under
the other sorts a member whose key moves while you page — their last visit, their name, their role
— can come back twice or be missed, because each page is a fresh read rather than a snapshot. The
Ambassador roster is always ordered by referral count and does not accept RESOURCE_ID, so that
roster keeps the same caveat.updateComment links bare URLs and email addresses in a comment bodyA bare URL or email address in the body you send to updateComment now comes back as a link in the stored comment, the way createComment has always handled one. An edited comment previously kept both as plain text, so the same text produced links when you created a comment and none when you updated it. An email address becomes a mailto: link.If you compare a comment’s body against the text you sent, expect link markup around any bare URL or email address in it. Send your own <a> markup when you want to control the link yourself, because an anchor that is already in the text is left as it is.RSVPs require a write scope
createRsvp, updateRsvp, and deleteRsvp now require the calling credential to hold write:rsvps (for a member token) or host:write:network_events (for a Host credential such as an Admin API key). Either one satisfies the requirement. A read scope no longer does: a member token holding only read:network could previously write the member’s own RSVP, because reading the event was the only check. A credential without either scope receives a FORBIDDEN error naming the scopes that satisfy the requirement. Tokens issued before scope enforcement began are exempt.write:rsvps also lets a token read the event it is answering and the RSVP it creates — Event, EventInstance, EventRecurrenceException, and Rsvp all accept it — but it does not grant listing the Network’s events. An integration that browses events still requests read:network.If your application RSVPs on a member’s behalf, request write:rsvps — or host:write:network_events on a Host credential — when authorizing. Members who already authorized your application need to authorize again so their tokens are reissued with the new scope. See OAuth Applications — Scopes.Create a member from an email address
createMember creates an account for an email address and adds it to your Network as a full member, so you can onboard someone who has never signed up. Pass email, firstName, and lastName. An account that already exists for that address is reused rather than duplicated, and a call naming someone who already belongs to your Network — as a full or limited member — returns an error rather than a second membership. To admit someone who already has an account in your Network, use createNetworkMembership instead.role sets the new member’s role on the Network and defaults to CONTRIBUTOR. sendWelcomeEmail defaults to true and emails the new member a sign-in link; set it to false to skip it. The payload returns the new member and an errors list carrying anything you can correct and retry.The mutation requires a token acting as a Network Host and holding the host:write:network_members scope. See OAuth Applications — Scopes.Uploading media requires an asset write scope
createUploadSession, completeUploadSession, and abortUploadSession — and the POST /networks/:network_id/assets REST endpoint — now require the calling credential to hold write:assets (for a member token) or the new host:write:network_assets scope (for a Host credential such as an Admin API key). A write scope for another resource, such as write:posts, no longer satisfies the upload endpoints. A credential without either scope receives a FORBIDDEN error naming the scopes that satisfy the requirement. Tokens issued before scope enforcement began are exempt.deleteAsset now accepts host:write:network_assets alongside write:assets, and the write:assets consent description covers uploading as well as deleting media.If your integration uploads media, request write:assets — or host:write:network_assets on a Host credential — when authorizing.A member total on the Network roster
Network.members returns a totalCount: how many members match the search and filters you passed, across every page. Getting that number used to mean paging the roster to the end, because pageInfo.hasNextPage only tells you the page in your hand is not the last one.The count answers the query you sent, so it narrows with term and with the role, memberType, spaceId, segmentIds, and ambassador filters. Ask for totalCount with first: 1 when you want the size of a roster and not its members. It is an exact count at any size, so a Network with hundreds of thousands of members reports what it actually has.Reading the roster still requires a token acting as a Network Host or Moderator. A token without that access continues to receive an empty connection, and its totalCount is 0 rather than the roster size.The
User type is deprecated in favor of MemberThe schema now describes members with a Member interface that carries every field User had, and every field that returned User — me, Comment.author, Post.creator, mutation payloads, and the rest — now returns Member. The connection wrappers keep their names for now: Member.followers, Member.followedMembers, and Network.blockedMembers still return a UserConnection whose nodes are User. User remains in the schema as the concrete type implementing Member, so your existing requests keep working: plain field selections are unchanged, ... on User fragments still match, and __typename still returns "User".When we remove User, Member will become the concrete type and __typename will return "Member". We will announce the removal here before it happens. To be ready for it, write new fragments on Member, and accept either "User" or "Member" wherever you compare __typename.A few fields are also deprecated in favor of member-named replacements: Reaction.user → Reaction.member, Message.user → Message.member, AutomationRuleLogEntry.user and userId → member and memberId, and FeedPostItem.introducedUser → introducedMember. The deprecated names keep working until the removal.BROKEN on StripeAccountStatusNetwork.stripeAccount.status returns BROKEN when a connection attempt to Stripe failed. The account is still attached to the Network, lastError carries the reason Stripe gave, and a Host can reconnect it. If your code switches exhaustively on status, add a branch for it.DISABLED has narrowed to mean only a Stripe account that has been fully disconnected and is no longer attached to the Network. If you treat DISABLED as “payments are not working”, split that into the two cases: a BROKEN account is one a Host can reconnect, and a DISABLED one is gone.Member counts on plans and spaces
PaymentPlan.memberCount returns how many members hold a plan, and Space.memberCount returns how many belong to a space. Counting either used to mean paging the whole member list to the end, which cost a large Network hundreds of requests for one number. Both fields return the total in a single request.A plan’s count covers everyone with access through it, whether they subscribed or bought it outright. Both fields return null unless your token acts as a Network Host or Moderator.Badge and tag grants report what they changedcreateBadgeMemberships, createTagMemberships, deleteBadgeMemberships, and deleteTagMemberships now return an outcome alongside the badge or tag. It is a GrantOutcome — GRANTED, ALREADY_GRANTED, REVOKED, NOT_GRANTED, or SKIPPED_NO_MEMBERSHIP — and answers only whether the call changed anything. Which members changed is answered separately by grantedMembers on the two create mutations and revokedMembers on the two delete mutations, so a batch where some members already held the badge is no longer indistinguishable from one where every member did.The two create mutations also stop granting to a member who holds no membership in your Network, the state a ban, a removal, or an abandoned signup leaves behind. That member is dropped from the batch, and a call naming only such members grants nobody and returns SKIPPED_NO_MEMBERSHIP. Both cases previously reported plain success, so if you grant from a member list you maintain yourself, read grantedMembers rather than assuming every id you sent was granted. A limited member is unaffected, and you can still grant them a badge or tag.createEvent takes either form of space IDcreateEvent’s spaceId now accepts a space’s numeric resource ID as well as its GlobalID, so it matches createPost, which already took both. It previously accepted only the GlobalID.Nothing changes for calls you already make — a GlobalID is still accepted, and the argument is unchanged in every other way.Nested connections page 25 at a time, and queries above a complexity of 1,500 are rejectedConnections on the query root and on
Network, such as network { posts } and network { members }, still page up to 50. Every connection you reach through another connection’s page now caps a page at 25, including Post.comments, Comment.replies, Event.rsvps, User.followers, User.spaces, and PublicConversation.messages / PrivateConversation.messages. A first: or last: above the cap is clamped to it rather than rejected, and pageInfo and the cursors keep working, so anything already paging by cursor is unaffected.A nested connection’s page size multiplies both the rows one request materializes and the complexity you are billed for. A page of 50 posts with 50 comments each came to 2,500 rows, and the same page now comes to 1,250.Queries are also rejected above a complexity of 1,500. Before a query runs, we estimate its cost from the most it could return: 1 for each row on every connection page (the first: or last: you supply, clamped to the page cap, or the page cap when you supply neither), 1 for each object field it reaches, and nothing for scalars. That is the same model extensions.cost.complexity bills you on after execution. A query estimated above the ceiling is rejected before execution and comes back with a top-level error and no data, and the request is billed the 1-unit minimum: {"errors": [{"message": "Query has complexity of 2703, which exceeds max complexity of 1500"}]}.For example, an inbox list of 50 direct messages selecting id, title, lastChatAt, and unreadCount estimates at about 103 and runs. Those same 50 conversations each selecting messages(first: 25) { nodes { id user { id name } } } estimate at about 2,700 and are rejected. Fetch messages one conversation at a time instead, and only for the conversations whose lastChatAt has changed.On a successful response, extensions.cost.complexity is what your query actually cost, so it is the number to watch as you tune a query toward the ceiling. Both limits apply only to the Mighty API, and both are documented under Query cost limits.Simpler upload strategy and provider namesThe
UploadStrategy and UploadProvider values now describe the wire protocol instead of the storage implementation behind it. Use MULTIPART_SERVER_MEDIATED, MULTIPART_PRESIGNED, SINGLE_PUT, and LEGACY_MULTIPART_FORM in place of S3_MULTIPART_SERVER_MEDIATED, S3_MULTIPART_PRESIGNED, S3_SINGLE_PUT, and KALTURA_MULTIPART_FORM. On UploadProvider, LEGACY_EXTERNAL replaces KALTURA.Responses now return the new names. The old names are deprecated but still accepted as input, so a createUploadSession request that sends preferredStrategy: S3_MULTIPART_PRESIGNED keeps working. Update any code that compares against the returned strategy or provider, and switch to sending the new names. The deprecated names will be removed in a later release, and that removal will be announced here first.location on events is deprecated — use venueThe free-text place name on events is now called venue: a new field on Event and a new optional argument on createEvent and updateEvent. The old name was ambiguous — the value holds a venue or place name (“Blue Bottle Coffee”), not an address — and real address fields now sit beside it (see this month’s additions below).location is deprecated on Event and on both mutations, but keeps working: both names read and write the same value, so nothing breaks today. Update your queries and mutations to use venue. A mutation that supplies both venue and location is rejected, so migrate each call site in one step. The removal of location will be announced on this page before it happens.Direct messages, with member consentThe schema now exposes a member’s private chats: their direct message conversations (including group conversations) and the reply threads they take part in, including threads started from a space chat. Read them from
me { directMessages }, and manage them with the conversation and message mutations — createConversation, createDirectMessage, deleteDirectMessage, markDirectMessageRead, markDirectMessageUnread, markAllConversationsRead, createDirectMessageReaction, deleteDirectMessageReaction, updateConversation, archiveConversation, leaveConversation, createConversationMemberships, and deleteConversationMemberships.Access is gated on two new member scopes: read:chats to read, and write:chats to send and manage (which also grants read). Both are consent-required — the consent screen is always shown when they’re requested, even on applications configured to skip it. A token acts as the member who approved it and reaches only that member’s own conversations; hosts gain no access to other members’ private chats. A request made without the required scope fails with a FORBIDDEN error naming the scope it needs. See OAuth Applications — Scopes.General availabilityThe Mighty API is generally available on the Scale plan and above. OAuth scopes are now enforced on every token, so a token’s access is the user’s product permissions intersected with the scopes they approved — request the narrowest set your app needs.Breaking changes — removals, renames, and arguments that become required — are deprecated and announced on this page before they take effect, so check it before you upgrade an integration.Scope requirements in the referenceThe GraphQL reference now names the OAuth scope each query, mutation, field, and type requires, so you can pick the scopes your app needs before you write the request rather than discovering them from a
FORBIDDEN response.Where more than one scope reaches the same data, the reference lists every scope that works and any one of them is enough. A write: scope also covers what its matching read: scope covers, so both appear wherever that applies.Documentation launchThe Mighty API documentation is live. Its GraphQL reference is generated from the schema we currently serve, so it always shows what is deployed today but not what changed or why. This changelog is where you find that. It starts here, so it does not cover changes made before this entry.From now on, every change to the published schema is recorded on this page:- New features and changes to what already exists get their own dated entry — new queries, mutations, fields, and enum values, along with renames, removals, deprecations, arguments that become required, and changes to permissions, pagination, or error codes. Everything that ships on a given day appears in a single entry.
- Bug fixes and documentation clarifications are collected into one entry per month, so you can review a month of them together.
createEventandupdateEventaccept optionalthumbnailIdandheaderIdarguments to set an event’s card thumbnail and header image. Upload the image through the asset upload endpoint first, then pass the returned asset id. OnupdateEvent, an explicitnullclears an image.- The asset upload endpoint (
POST /networks/:network_id/assets) accepts two new asset styles for event images:square_thumbnailandcinema_header. createEventandupdateEventaccept optionalstreetandcityarguments on in-person (local) events, and the address is geocoded to map coordinates automatically. Supplying an address on any other event type returns a validation error; onupdateEvent, an explicitnullclears a field.Post.venueandPoll.venuereturn the free-text venue or place name saved on a post, such as “Blue Bottle Coffee”, or null when none is set.Event.venuealready returned this value, so the place name now reads the same way on all three post content types.createLiveSpacestarts a livestream and returns it with theurlto open to join the room as its creator. PassspaceIdto start it in a space, or omit it to start it on the Network. Recording is off unless you passrecord: true, andnotifyMembers: truetells the space’s members it has started. A livestream draws down your Network’s streaming hours until it ends.completeLiveSpaceends a livestream and stops it consuming those hours. One that never went live is aborted, and one that is broadcasting is stopped, disconnecting its attendees. Ending a livestream that has already ended succeeds and changes nothing.
createEvent/updateEvent— thecityargument is free text, so you can append a state or province and a postal code after the city name (for exampleLondon, Ontario, N5V 3N8). Including them improves the accuracy of the coordinates geocoding derives fromstreetandcity.
AutomationRuleTarget now includes User, so a rule whose target is a host resolves like any other.A rule can fire when a host receives a direct message, and that rule’s target is the host. Because the union did not list User, a request selecting triggerTargetable on one of those rules failed rather than returning the target, and an action’s targetable behaved the same way.Select the host’s fields with a ... on User fragment. Without it you read only __typename from the target, the same as for any other type in the union you do not spread.