Skip to main content
RSVPs require a write scopecreateRsvp, 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 addresscreateMember 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 scopecreateUploadSession, 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 rosterNetwork.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 Userme, 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.userReaction.member, Message.userMessage.member, AutomationRuleLogEntry.user and userIdmember and memberId, and FeedPostItem.introducedUserintroducedMember. 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 spacesPaymentPlan.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 GrantOutcomeGRANTED, 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 Headless 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 Headless 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 Headless 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.
Additions this month
  • createEvent and updateEvent accept optional thumbnailId and headerId arguments 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. On updateEvent, an explicit null clears an image.
  • The asset upload endpoint (POST /networks/:network_id/assets) accepts two new asset styles for event images: square_thumbnail and cinema_header.
  • createEvent and updateEvent accept optional street and city arguments 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; on updateEvent, an explicit null clears a field.
  • Post.venue and Poll.venue return the free-text venue or place name saved on a post, such as “Blue Bottle Coffee”, or null when none is set. Event.venue already returned this value, so the place name now reads the same way on all three post content types.
  • createLiveSpace starts a livestream and returns it with the url to open to join the room as its creator. Pass spaceId to start it in a space, or omit it to start it on the Network. Recording is off unless you pass record: true, and notifyMembers: true tells the space’s members it has started. A livestream draws down your Network’s streaming hours until it ends.
  • completeLiveSpace ends 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.
Documentation clarifications
  • createEvent / updateEvent — the city argument is free text, so you can append a state or province and a postal code after the city name (for example London, Ontario, N5V 3N8). Including them improves the accuracy of the coordinates geocoding derives from street and city.
Automation rule targets can be a hostAutomationRuleTarget 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.