Archive API docs
    Jump to

    Narrow by kind with a prefix — q: queries, m: mutations, t: types, g: guides, f: fields and arguments.

    Changelog

    Changes to the Archive API and to these guides, newest first.

    About this changelog

    This page records partner-visible changes to the /api/v2 surface: schema additions and removals, authentication or header changes, rate-limit behavior, and error-shape changes. Add an entry in any pull request that changes the GraphQL schema or guide-visible behavior, dated by its merge date, with one bullet per change.

    Stability and versioning

    Everything this documentation describes as generally available changes additively only. New fields, arguments, types, enum members, and MCP tools may appear at any time; existing ones are not renamed, removed, or changed in meaning. The same rule covers MCP tool names and input schemas, and the webhook delivery envelope — new keys may be added, existing keys keep their shape.

    Build for that: select only the fields you use, ignore unknown keys in JSON payloads, and treat unrecognized enum values as “other” rather than an error. A change that would break a correctly built integration ships as a new URL version (an /api/v3), never in place on /api/v2.

    Coverage began at different times: the GraphQL schema has been additive-only since launch, while MCP tools joined the contract when the tool surface went generally available on 2026-08-14. MCP-only breaking changes from before that date — such as the 2026-07-25 argument-casing migration — were part of the pre-GA shakedown and are recorded below as Breaking (MCP only) entries; none can recur now that the tools are GA.

    The one exception is alpha features, which are opt-in precisely because they carry no stability contract. Everything else that is partner-visible — schema, behavior, limits, error shapes — lands as a dated entry below when it changes.

    2026-11-04

    • Deep Research credit-permission errors now distinguish read-only connections from denied credit-spending access and explain how to resolve each. Campaign-edit access alone does not grant permission to spend credits.
    • Documentation only: the engagementHistory first argument description now states its default of 20 and its maximum of 1000. The pagination guide now lists operationRecords as the second connection that accepts up to 1000. Behavior is unchanged.
    • A creators query that passes customAttributeConditions or presetId now costs the same as a creators query without them. A page of 20 records costs 66 points instead of 316.
    • X-RateLimit-Cost and the r= value of RateLimit drop for those calls. Plan limits do not change.
    • Re-enabling a webhook subscription with replayFailedSince24h now replays its failed backlog through the paced retry path in batches. Replay normally starts on the next sweep; backlog and delivery pacing can delay the first attempt.

    2026-11-03

    • When Archive cannot schedule the refresh job, refetchEngagementBulk now returns a retryable userError saying the refresh could not be scheduled. Archive holds no credits for that request. A retry with the same itemIds is not reported in skippedItemIds.
    • A /api/v2 request that costs more than your plan allows per request now returns HTTP 200 with extensions.code QUERY_TOO_COMPLEX and the message “This request costs 680 points; your plan allows at most 500 per request. Split it into smaller requests.” (with your request’s numbers), instead of a 429 whose Retry-After never came true. Nothing is charged. Split the request; don’t retry it.
    • The MCP server returns the same text as an isError tool result for such a call, instead of “Rate limit exceeded. Retry after N seconds.”
    • The getSocialProfiles MCP tool’s includeStats summary now returns engagementsImpressionsAvg. engagementsViewsAvg is still returned.
    • The SocialProfileStats descriptions now say that engagementsViews and engagementsViewsAvg are rarely populated (0 for most profiles), and they point to engagementsImpressions and engagementsImpressionsAvg for views, plays, or reach.

    2026-11-02

    • An unknown orderBy, order_by, sortBy, sort_by, sort or order argument on a field that takes sorting now suggests sorting in the validation error, for example on items(orderBy: …). MCP tools return the same hint: searchItems with orderBy answers “did you mean ‘sorting’?”.
    • The items field and its sorting argument now show how to sort by engagement or earned media value, and point to filter.engagement for engagement ranges.
    • The MCP tools searchItems and getCompetitorBrandItems now return currentEngagement.impressions in both concise and detailed responses. views is still returned.
    • The Engagement.impressions and EngagementHistoryEntry.impressions descriptions now name impressions as the field for a post’s views, plays, or reach. The views descriptions now say it is rarely populated and is null for most items.
    • submitFeedback reports a problem with the Archive API to the Archive team: a bug, unclear docs, a missing capability, an unexpected result or a slow call. It returns only a receipt, { feedback: { id, duplicate }, userErrors }.
    • The same report sent again within 24 hours returns the earlier receipt with duplicate: true. A workspace sends at most 10 reports at once, then one more every 15 minutes; over the limit the call returns a userError on ["input"], not a 429.
    • Reports are not visible to workspace users and get no reply. Leave personal data out of them.
    • The matching submitFeedback MCP tool is listed for every authorized caller, read-only OAuth connections included. Pricing: the mutation is a write costing 50.
    • The MCP server’s initialize instructions ask agents to call submitFeedback on their own when they hit what looks like an Archive problem, continue the task, and tell the user in one line with the receipt id.
    • MCP tool errors “Tool execution failed” and “Invalid argument …” now end with “If this looks like an Archive bug, report it with submitFeedback.” submitFeedback’s own errors do not.

    2026-10-31

    • updateCampaign posts (add and remove by item id) now also applies to BRAND_MONITORING campaigns, with the same limits and per-entry errors as on CONTENT_CAPTURE. Added posts sit alongside automatically captured ones, and an added post’s creator joins the roster. A removed post can be captured again by a later replay when it is in-window and from a brand-listening source. CREATOR_MONITORING still refuses posts.

    2026-10-28

    • CampaignType gains BRAND_MONITORING. Brand Monitoring campaigns built in the app, which read campaignType: null until now, read BRAND_MONITORING and can be changed with updateCampaign (name, dateEnd, creators). Their tag, content and source narrowing is kept as stored. A client that switches exhaustively on CampaignType sees a new value.
    • createCampaign creates BRAND_MONITORING campaigns where the workspace offers Brand Monitoring. dateStart is required and dateEnd is optional (omitted means open-ended); the campaign captures workspace-wide, and it takes no settings.
    • updateCampaign refuses a dateEnd on an open-ended BRAND_MONITORING campaign, as a userError on ["input","dateEnd"].
    • MCP createCampaign, updateCampaign and getCampaign describe the new type.
    • OperationRecord.globalCreatorId: for a creator-import record that SUCCEEDED, the global, person-level id of the creator behind the row’s main handle. It stays the same when the same person is imported into another workspace, and it is not an input to creator(id:) (use creatorId for that). Null for other records, other operation types and rows imported before this field existed.

    2026-10-23

    • Collection.itemCount on collections and collection, also returned by the getCollections / getCollection MCP tools, is now computed from the item search index with one aggregation per request instead of a per-workspace Postgres count. The field answers in well under a second on large workspaces where it took 20-60 s.
    • Behavior change: itemCount no longer counts soft-deleted items and now matches items(filter: {collectionsIds: [id]}).totalCount, up to search-index lag (seconds) and any orphaned index documents. It is no longer refreshed by a 5-minute cache.
    • The INVALID_INPUT message that socialProfile (and the getSocialProfile MCP tool) returns for an accountName that isn’t a handle now names the platform with the right article: “is not an Instagram handle”, “a TikTok handle”, “a YouTube handle”. It used to read “is not a instagram handle”. The error code is unchanged.
    • The same lookup with provider: INTERNAL, fallback: true and an input that isn’t a handle returns that INVALID_INPUT error (“is not a valid handle”) instead of an internal error.
    • createViewGroup and updateViewGroup (and the matching MCP tools) now also refuse a name that differs from an existing view group’s name only by its spacing: a no-break space, a narrow no-break space or an ideographic space where the other name has an ordinary space, or a run of spaces where it has one. The refusal is the usual A view group named "…" already exists in this workspace. userError on the name field. Existing names are unchanged, and sending a group’s current name back still keeps it.
    • The same mutations no longer treat a zero-width joiner or non-joiner as invisible where it changes what renders: an emoji sequence such as 👨‍👩‍👧 and its separate emoji 👨👩👧, or a Persian word spelled with and without a non-joiner, are now distinct names. A joiner between Latin letters, next to a space or at either end of a name is still ignored.
    • MCP refuses a request id with a fractional part or an exponent (1.5, 1.0, 1e3) with a -32600 Invalid Request error (id null), as it already did for null. Send a string or an integer id.
    • MCP refuses a request whose Content-Type is missing or is not application/json (for example text/plain) with HTTP 415 and a -32000 JSON-RPC error (id null).
    • MCP refuses a request whose Accept header is missing, or accepts no application/json, with HTTP 406 and a -32000 JSON-RPC error (id null). Send Accept: application/json, text/event-stream; */* and application/json alone are still answered.
    • Either refusal is answered only to a request that carries a credential. Without one, the 401 OAuth discovery challenge comes first, as before, and a ping or a notification is still answered ({} / 202).
    • On MCP, a Content-Type header that is not a parseable media type is answered with HTTP 415 and a JSON-RPC -32000 error instead of 406. On /api/v2 it keeps its 406, now with a {"errors": "..."} body. An Accept header that cannot be parsed keeps its 406 on both, with the same JSON error bodies. No response echoes the header.
    • Every MCP read tool’s outputSchema now describes the fields of the objects and list items it returns, with their types and nullability, instead of a bare object or an untyped array. The payloads are unchanged. A client that validates structuredContent now checks each field. No property was made required and no enum values are pinned, so a field added to a payload later still validates.
    • MCP getWebhookSubscriptions: the top-level nextCursor and hasNextPage are now marked deprecated in the tool’s outputSchema. They were deprecated copies of pageInfo.endCursor and pageInfo.hasNextPage already and are still returned. Read pageInfo instead.
    • MCP searchItems, getSocialProfiles and searchCreators: a presetId of the wrong view type now gets an error that says which view to pass and which tool lists its ids (for example “presetId is not a Social Profile View; pass a Social Profile View id (from getSocialProfileViews).”), instead of naming internal accessor values. The GraphQL items, socialProfiles and creators errors are unchanged.
    • The MCP getOperation description now says that a label operation (delete_label, rename_label, merge_labels) reports succeededCount, failedCount and pendingCount as 0. Read status, total and processed for its progress.

    2026-10-22

    • New setCreatorCustomAttributes and clearCreatorCustomAttribute mutations, with matching MCP tools, write one creator custom attribute value back from your CRM. The value is checked against the key’s schema in customAttributeSchemas; a select takes an option id or an exact option name.
    • Up to 40 creators are written in the request. Up to 1,000 run as one background operation to poll with operation(id:). An unknown creator id refuses the whole call; labels stays on the label mutations.
    • SocialProfile gains the vetting fields the Archive app shows for a profile. All are nullable and additive.
      • biography: the profile’s biography text.
      • accountType: a SocialProfileCategory, the app’s “Profile type”. It covers every platform, unlike the Instagram-only SocialProfileAccountType.
      • location: where the account holder is based, as canonical names that match LocationCanonicalInput.
      • stats: a SocialProfileStats object with item counts per platform and format. It also holds last-month engagement totals, averages and rates, and earned media value in cents. It is null when Archive holds no content for the profile.
    • The getSocialProfiles MCP tool returns biography, accountType and location in its detailed response. The new includeStats input adds a short stats summary per profile. It is off by default and adds 5 per profile to the call cost.
    • creators gains filter.accountNames, a list of social handles. A bare handle matches every platform; tiktok:handle matches one platform.
    • creators also takes an optional sorting with one entry: CREATED_AT or FULL_NAME, ASC or DESC. An inline sorting wins over a presetId view’s stored sort. The MCP searchCreators tool accepts the same filter.accountNames and sorting.
    • A LocationCanonicalInput entry may name a region group with group instead of country, region or city. It then matches every member country. This applies to creators filter.locations and to items filter.creatorLocations and filter.ugcLocations. The matching MCP tool arguments accept it too.
    • New query locationGroups lists region group codes with their names and member countries (cost 1). MCP tool: getLocationGroups.
    • New public types: CreatorSortKey, CreatorSortingInput, LocationGroup.
    • socialProfiles now filters inline by everything the Social Profiles page filters by. SocialProfileFilterInput adds accountNames, creatorIds, followersCount, engagement, verified, takenAt and postingActivity (new enum FilterPostingActivity). It also adds itemTypes, contentTypes, tagsNames with tagsNamesOperator, and sourcesIds with sourcesIdsOperator.
    • socialProfiles takes customAttributeConditions like creators does. A combination the search cannot honor returns INVALID_INPUT. The MCP getSocialProfiles tool takes the same filter keys and customAttributeConditions.
    • importCreators takes an optional customAttributes object per creator. Its keys are creator custom attribute keys, and it is shaped like Creator.customAttributes. The import sets those values on the imported creator.
    • An unknown key, or a value that does not fit its schema, is a userErrors entry. The entry’s path is ["creators", "<index>", "customAttributes", "<key>"].
    • The MCP importCreators tool accepts the same field. See Creator import.
    • New query campaignCreators pages one campaign’s roster. Each CampaignCreator node carries the creator, addedAt and the campaign-specific customAttributes, such as gifting, shipping or contract status. Filter the roster with customAttributeConditions and sort it by ADDED_AT. Conditions also accept labels, creator_name and posting_activity, one condition each. post_date and collections are refused with UNSUPPORTED_CUSTOM_ATTRIBUTE_CONDITION. Offset paging can skip or repeat rows that change between pages. An unknown campaign ID, or one from another workspace, returns a NOT_FOUND error. The new getCampaignCreators MCP tool wraps it.
    • CustomAttributeSchemaEntity gains CAMPAIGN_CREATOR. Call customAttributeSchemas(entity: CAMPAIGN_CREATOR), or getCustomAttributeSchemas in MCP, to list the campaign-specific keys.
    • deleteCampaign deletes a campaign exactly as deleting it in the app does. It returns { deletedCampaignId, userErrors }.
    • It reaches the same campaigns updateCampaign does: those whose campaignType is not null. Any other id, or a repeat delete, is a not_found userError on ["input","id"].
    • The roster and posts list go with the campaign; creators and items survive. Deleting a CREATOR_MONITORING campaign with empty tagNames narrows back the creator tracking it widened.
    • The matching deleteCampaign MCP tool is listed for write-capable callers. Pricing: the mutation is a write costing 50.
    • New createCustomAttributeSchema mutation and MCP tool create a creator custom attribute field in your workspace. The key is derived from the name. Only value types and the SINGLE_SELECT_V3 / MULTIPLE_SELECT_V2 selects can be created; other types are a userError on type.
    • New addCustomAttributeOptions mutation and MCP tool add options to a creator select field. Names the field already has come back in skippedNames, so a repeat call adds nothing.
    • Names must be unique among creator fields, ignoring case. Built-in names such as Labels are refused; labels keep their own mutations. A workspace holds at most 250 creator fields besides the built-in ones, and a field at most 500 options.
    • createCampaign now creates Vetting Workflow campaigns with campaignType: VETTING_WORKFLOW, a new CampaignType value. It needs dateStart and dateEnd. Archive pulls each rostered creator’s posts in that window and runs AI brand safety review on them. dateEnd may not be more than 24 hours from now. The app ends the window at the end of today in the user’s timezone, because a creator’s posts are pulled once, when they are added. A workspace whose Archive app does not list Vetting Workflow campaigns gets a userError on ["input","campaignType"].
    • New settings.vettingWorkflow input (VettingWorkflowSettingsInput) takes two members. maxMediaItemsPerCreator is 1 to 1000000 and defaults to 5000. excludeCreatorsInOtherVettingCampaigns defaults to true. An out-of-range limit is a userError on ["input","settings","vettingWorkflow","maxMediaItemsPerCreator"]. An externalId replay must repeat both values.
    • updateCampaign renames these campaigns and changes their roster (INSTAGRAM and TIKTOK profiles). It also moves dateEnd, again no more than 24 hours from now.
    • New Campaign fields maxMediaItemsPerCreator and excludeCreatorsInOtherVettingCampaigns read the stored settings back. Both are null on other types.
    • Existing Vetting Workflow campaigns now appear in campaigns, campaign(id:) and the MCP getCampaigns and getCampaign tools. This includes campaigns built in the app. They appear in their created-at place, and totalCount rises to count them. This holds in workspaces whose Archive app lists Vetting Workflow campaigns. Elsewhere they stay out of every read.
    • MCP createCampaign takes and returns the new members, and getCampaign returns them. MCP updateCampaign accepts Vetting Workflow campaigns, and its output is unchanged. Clients that switch over CampaignType must still handle values they do not know.
    • New mutation requestUsageRights(itemIds, requestType, message) and MCP write tool requestUsageRights. They ask creators for usage rights, a Partnership Ad Code or a Spark Code. Each item joins the workspace’s UGC Repurpose campaign with status IN_QUEUE. Up to 100 items per call. Send each request from the Manage Rights page; the API returns neither the message nor its link.
    • Skipped items come back in skippedItemIds, as passed. These are items outside the workspace, items not from Instagram or TikTok, and items that already have a request. Items past the campaign item or creator limit are skipped too.
    • New enum UsageRightsRequestType: USAGE_RIGHTS, WHITELISTING, USAGE_RIGHTS_AND_WHITELISTING.
    • New field CompetitorBrand.previousPeriod on competitorBrands and competitorBrand. It returns the same six metrics for the window before the requested one, with that window’s from and to. Use it for period-over-period change. It is null when that window lies entirely before Competitor Insights data. A window that starts earlier is cut to start on that date, so read its from.
    • New field CompetitorBrand.provenance: for each metric, the captured value, the reported total and the estimated amounts in it. Each estimate names its method, STORY_MEDIAN_MULTIPLIER or IGM1_POST_MULTIPLIER. Totals that include estimates can now be told apart from captured values. previousPeriod.provenance gives the same breakdown for the previous window.
    • New argument postDate on competitorBrands and competitorBrand: an explicit window in whole UTC days, at most 3 months, through the latest settled day (currently today minus 8 days). Workspace Early access does not extend this default. It replaces period and date. Passing either of them with it, or a window outside the bounds, returns an INVALID_INPUT error; windows are rejected, not shortened. Post listings retain their existing day-9 rule.
    • New optional argument includeProvisionalDays: Boolean = false on both aggregate queries, available without an alpha header. Pass true with postDate to acknowledge that engagement figures may still grow and use the workspace’s latest selectable day. This does not grant Early access; omitted, null or false retains the settled ceiling. Passing true without postDate, or dates beyond the workspace’s ceiling, returns INVALID_INPUT.
    • The brandId description of competitorBrandItems now says that the own-brand entry has no competitor posts, so its id returns an empty connection. Use items for the workspace’s own posts.
    • These fields add no rate-limit cost. They come from the same metrics read as the existing metric fields.
    • Item gains the values the Archive app’s Content library shows, so a list render needs no second call. New fields: createdAt (when Archive ingested the item) and sourceIds (the IDs ItemFilterInput.sourcesIds takes). sentiment is an ItemSentiment.
    • usageRights returns a connection of ItemUsageRights, newest request first, with status and scopes. The status shares its values with FilterContractStatus.
    • New fields also include products (ItemProduct) and customProduct (ItemCustomProduct). coAuthors lists the other profiles on a collaborative post. thumbnailUrl is the first available media-content preview.
    • products and usageRights accept first (default 20, maximum 100) and after. Select nodes, edges, and pageInfo to traverse them; totalCount is computed only when requested. Products are ordered by product ID. customProduct remains a separate single-record lookup.
    • All are additive; see rate limits for their weights.
    • ItemFilterInput gains tagsNamesOperator and sourcesIdsOperator, each a FilterOperator. Values: CONTAINS (the default), CONTAINS_ALL, DOES_NOT_CONTAIN, IS_EMPTY or IS_NOT_EMPTY. Any other value is refused with INVALID_INPUT.
    • ItemFilterInput also gains excludeCollectionsIds, which drops items in any of the given Collections. creatorIds keeps items by any of the given creators.
    • They apply to items, createContentView and updateContentView. A content view’s stored filters hold them as tags_names_operator, sources_ids_operator, exclude_collections_ids and creator_ids. The MCP searchItems, createContentView and updateContentView tools take them too.
    • updateCampaign takes an optional settings input of the new CampaignSettingsPatchInput type. On a CREATOR_MONITORING campaign, settings.creatorMonitoring changes tagNames and pullLogic. Only the fields you send change; an omitted field keeps its stored value. tagNames: [] clears the list. Content conditions set in the Archive app are kept.
    • CreatorMonitoringSettingsInput gains pullLogic (HAS_ANY, the default, or HAS_ALL) on createCampaign. A createCampaign retry with a different pullLogic is a userError on that field.
    • Campaign.pullLogic reads the stored value back next to Campaign.tagNames. It is null for other campaign types. The getCampaign, createCampaign and updateCampaign MCP tools return both fields, and updateCampaign accepts settings.
    • campaigns takes an optional filter: CampaignFilterInput with campaignTypes, externalIds and nameContains. Members AND together, and totalCount counts the matches. nameContains ignores case.
    • updateCampaign takes creators.addCreatorIds: creators already in the workspace join the roster during the call. A refused id is reported at its own index path, and the rest still apply.
    • The getCampaigns MCP tool takes the same filter, and each node now carries campaignType and externalId. The two new fields raise a getCampaigns call’s cost by 2. It goes from 14 to 16 at limit 20, and from 18 to 20 at limit 100.
    • The updateCampaign MCP tool takes creators.addCreatorIds.
    • New query webhookLimits returns the workspace’s webhook caps and usage (cost 1). Fields: tier, maxSubscriptions, usedSubscriptions, maxViewsPerSubscription, metadataMaxBytes and capsSource. capsSource is plan, workspace_override or org_override.
    • Read it before createWebhookSubscription or updateWebhookSubscription instead of hitting a cap’s limit userError. MCP tool: getWebhookLimits.
    • New public type: WebhookLimits.
    • New field Creator.activity(first, after): the creator’s activity history, newest first. It is the same history the creator drawer shows in the app. Each ActivityLogEntry carries occurredAt, an action, the actorType and actorName, the changed field with oldValue and newValue, and a readable summary. Pages hold up to 100 entries, 20 by default. The field costs 5, plus 5 for totalCount.
    • The MCP getCreator tool accepts includeActivity and activityCursor and then returns the activity history, 20 entries per call. Its output is unchanged when includeActivity is off.
    • socialProfiles (and the getSocialProfiles MCP tool) now pages by a seek cursor under the default sort, so a deep page costs about the same as the first one instead of growing with its depth. after stays opaque: pass the previous page’s endCursor as before.
    • endCursor values issued before this change are still accepted; the page they return carries a new cursor, so a walk in progress continues without restarting.
    • A view (presetId) whose stored sort is not the default keeps the previous paging behavior. A cursor issued under the default sort is refused with Invalid cursor (INVALID_INPUT) when it is replayed under such a view.
    • A WORKSPACE-ID header that isn’t a UUID, such as a numeric workspace ID, now returns HTTP 404 with {"errors":"WORKSPACE-ID must be the workspace UUID shown in the app's API settings; numeric workspace ids are not accepted"} instead of {"errors":"Shop not found"}. The status and body shape are unchanged. An unknown or inaccessible workspace UUID still returns Shop not found, and a missing header still returns 400. See Workspaces.
    • rotateWebhookSubscriptionSecret no longer revokes an earlier secret when you rotate again within 24 hours: every secret retired in the last 24 hours keeps signing deliveries until its own 24-hour window ends, so X-Archive-Signature carries one v1= per active secret (current first, then retired secrets newest first). Up to 5 retired secrets can be live at once; a sixth rotation within that window is refused with a userError on id that says Retry after <time>, and nothing is rotated.
    • MCP tools now word an argument that has the wrong type or value as Invalid argument '$.<path>': <reason>, naming the tool argument (for example Invalid argument '$.filter.capturedAt.from': must be an ISO-8601 date-time). They used to pass through GraphQL text such as Variable $filter of type … was provided invalid value, which named a variable the caller never sent. The call is still refused the same way; only the message text changes.
    • MCP argument checks report every failing argument in one response, joined with ; , instead of stopping at the first.
    • createCampaign returns one userError per invalid tagNames entry instead of only the first, and the create-view mutations (for example createContentView) return one userError per invalid input when several are wrong. The MCP tools return the same errors.
    • The getEngagementHistory MCP tool refuses a filter.capturedAt window whose from is after its to, with the message searchItems already uses. It used to return an empty result. engagementHistory on /api/v2 still returns an empty list for a reversed window.
    • Stricter input rules: the calls below used to succeed and silently return the wrong result. They now return an error. No field was removed or renamed.
    • A location filter whose entries are all blank ([{}], [{country: ""}]) on items (creatorLocations, ugcLocations) or creators (locations) returns INVALID_INPUT instead of matching every record. A list that mixes blank and real entries keeps working and ignores the blank ones. searchItems and searchCreators behave the same.
    • An inline custom-attribute condition on items or creators whose type contradicts the workspace’s custom attribute of that name returns UNSUPPORTED_CUSTOM_ATTRIBUTE_CONDITION instead of matching nothing. A key the workspace has no custom attribute for is still searched.
    • createCampaign and updateCampaign refuse a dateStart or dateEnd outside 2000-01-01T00:00:00Z to 2100-01-01T00:00:00Z with a userError on that field (dateStart must be between 2000-01-01T00:00:00Z and 2100-01-01T00:00:00Z.). Nothing is created or changed.
    • The updateCampaign MCP tool no longer lists INTERNAL as a provider, since no campaign type accepts it. The GraphQL Provider enum is unchanged.
    • updateWebhookSubscription treats repeated viewIds and eventTypes as one entry, as createWebhookSubscription already did, so duplicates no longer count against the per-subscription limits.
    • The refetchEngagementBulk MCP tool counts and quotes distinct itemIds: a repeated id counts, and is charged, once, and only more than 50 distinct ids need confirm: true.
    • Documented, behavior unchanged: a superSearch with no non-blank searchQuery, imageUrl, similarMediaContentId or fileName (for example {} or { searchQuery: "" }) is no search, so results are unranked and only the other filters apply.
    • MCP rejects a request whose id is null with a -32600 Invalid Request error (id null) instead of answering it. Requests carry a string or number id; a message with no id is still a notification and is acknowledged with 202 Accepted.
    • MCP now enforces the MCP-Protocol-Version request header on every message except initialize: a value other than 2025-03-26, 2025-06-18 or 2026-07-28 gets HTTP 400 with a -32000 JSON-RPC error whose data.supported lists the accepted versions. An absent or empty header is still read as 2025-03-26, and initialize keeps negotiating from its body, so send the protocolVersion the handshake returned on later requests.
    • MCP initialize without a params object returns a -32602 Invalid params error. Without a credential it still returns the 401 OAuth discovery challenge.
    • A request body over the 2 MB limit on the MCP endpoint is still refused with HTTP 413, now with a JSON-RPC error body (-32000, id null) instead of {"errors": "..."}. /api/v2 GraphQL keeps its existing 413 body.
    • Workspace.hashtags no longer lists hashtags whose detection is turned off in the workspace, matching the app; mentions are unchanged.
    • mediaContents no longer reports a live post’s media as deleted: true just because Archive has not stored its file. deleted is now true only when the media was removed or its stored file purged. YouTube videos read deleted: false with a thumbnailUrl, falling back to the platform’s own poster; fileUrl stays null for YouTube, as before.
    • Documented, behavior unchanged: uploadItemFromUrl is not tracked as an operation — it adds no operations entry and sends no operation.completed webhook; poll itemIdsByUrl with the same URL. When the post belongs to an account the workspace does not track yet, the import also creates a creator and a social profile for it, as an upload in the Archive app does. The API cannot delete creators.
    • redeliverWebhookDelivery now refuses a delivery whose subscription is DISABLED_BY_FAILURES, with a userError on deliveryId asking you to call enableWebhookSubscription first. Previously the replay went out and, on success, reset consecutiveFailures and set lastSuccessAt while the subscription stayed disabled. The MCP tool returns the same error.
    • A delivery that settles SUCCEEDED now has nextAttemptAt: null. A redelivered delivery used to keep the time its replay was scheduled for.
    • New field CompetitorBrand.ownMetricsHasData on competitorBrands and competitorBrand, also returned by the getCompetitorBrands / getCompetitorBrand MCP tools. For the own-brand entry it is true when ownMetricsStatus is AVAILABLE and the window has own-brand data, and false when it is AVAILABLE but the window has none, so every metric is null and should be read as missing, not zero. It is null for competitor brands and when ownMetricsStatus is UNAVAILABLE. ownMetricsStatus is unchanged.
    • New field CompetitorBrand.displayName: the name the Archive app shows for the brand. It equals name for competitors; for the own-brand entry it is the name of the brand linked as the workspace’s own brand, or "You" when none is linked. name stays "You" for the own entry. Both MCP tools return it.
    • Unchanged behavior to know: CompetitorBrandConnection.totalCount counts competitor brands only and never includes the pinned own-brand entry, so a workspace with no competitors returns one node and totalCount: 0. The field description now says so.
    • The sort argument of the six create/update Content, Creator and Social Profile View MCP tools now declares its item shape ({ field, direction }); the values they accept are unchanged.
    • Documentation only: the MCP tool descriptions and the webhooks guide now state the engagement-history lag after refetchEngagementBulk, the type to itemTypes mapping, the case-sensitive labels filter, the searchCreators index lag, that getSocialProfile fallback hits are not saved to the workspace, the four view-group member kinds, the per-workspace ping throttle, and that a webhook event’s payload is the envelope’s data member.
    • Collection gains customAttributeOptionId: the id of the option that represents the Collection in the ITEM collections custom attribute (customAttributeSchemas(entity: ITEM)). It differs from the Collection id; use it as the value of a collections custom-attribute condition and to match an item’s customAttributes.collections. It is null where the workspace stores Collections in the older format. The MCP getCollections and getCollection tools return it too.
    • Renaming a Collection (updateCollection) now advances its updatedAt.
    • createCampaign now refuses an externalId replay whose dateStart, dateEnd or settings.creatorMonitoring.tagNames differ from the request that created the campaign, with a userError on ["input","dateStart"], ["input","dateEnd"] or ["input","settings","creatorMonitoring","tagNames"]. Such a replay used to return the stored campaign as a success, silently dropping the new values. Only name may still differ. The comparison is with that first request, not the campaign’s current values, so replaying the original input still works after updateCampaign; campaigns created before this change replay unchecked. The MCP createCampaign tool behaves the same way. This is a one-off exception to the additive-only rule above, recorded as contract exception API-801: to migrate, retry with the identical input, change dates with updateCampaign, or use a new externalId; the userError’s field names the input that differs.
    • New field Campaign.tagNames: the stored tag list of a CREATOR_MONITORING campaign (empty captures every post), null for other types. The MCP createCampaign and getCampaign tools return it.
    • deepResearchReports cursors are now bound to the workspace and filter that issued them. A hand-built cursor, or one reused under another workspace or filter, is refused with Invalid cursor (INVALID_INPUT), the message every other connection uses; it previously read “Invalid after cursor. Pass the endCursor of the previous page.” Cursors issued before this change are refused once: restart pagination without after.
    • TikTok post URLs now include the creator’s handle: Item.originalUrl and CompetitorBrandItem.originalUrl (and the matching MCP tool output) read https://www.tiktok.com/@<handle>/video/<id> instead of https://www.tiktok.com/@/video/<id>. A post with no known creator keeps the handle-less form, which TikTok redirects. itemIdsByUrl accepts both forms.
    • createViewGroup and updateViewGroup (and the matching MCP tools) now refuse a name that differs from an existing view group’s name only by invisible format characters, such as a zero-width space inside the name, with the usual A view group named "…" already exists in this workspace. userError on the name field. Existing names are unchanged, and sending a group’s current name back still keeps it.

    2026-10-20

    • Deep Research is generally available, with no alpha header: deepResearchTemplates lists the report templates the workspace can run, deepResearchQuote validates a report input and quotes its cost, createDeepResearchReport starts a report, and deepResearchReports and deepResearchReport read them back, the finished report as Markdown. The five matching MCP tools are listed for every caller; createDeepResearchReport only for write-capable callers (an OAuth connection needs the write scope).
    • Unchanged behavior to know: a workspace whose plan lacks Deep Research is refused with DEEP_RESEARCH_NOT_AVAILABLE when it quotes or creates. Reports spend the workspace’s credits, which are never refunded. The MCP createDeepResearchReport tool starts nothing without confirm: true and returns the quoted cost instead; the GraphQL mutation has no confirm step.
    • Pricing: each Deep Research query costs 21 points; the mutation is a write costing 50.
    • New public types: DeepResearchTemplate, DeepResearchInputType, DeepResearchSupport, DeepResearchQuote, DeepResearchReportInput, DeepResearchReportPage, DeepResearchReportFilterInput, DeepResearchReportStatus, DeepResearchReport and CreateDeepResearchReportPayload. Nothing existing changed shape. The three Deep Research enums are now under the stability contract.
    • The deep_research alpha key is retired. Clients that still send it are unaffected: unknown keys are ignored, so they see the same surface as clients that do not, and get no error.

    2026-10-19

    • createCampaign now normalizes settings.creatorMonitoring.tagNames the way the app does: entries are trimmed, lowercased, split on whitespace, stripped of trailing commas and deduplicated. An entry that is blank or is not a #hashtag / @mention is refused as a userError on ["input","settings","creatorMonitoring","tagNames","<index>"] and nothing is created. Such values were previously stored but never matched a post. The MCP createCampaign tool behaves the same way.

    2026-10-18

    • Campaign provisioning is generally available without an alpha header: read a campaign, createCampaign with a stable external key, and updateCampaign to change its name, end date, creators or explicitly held posts. Both CREATOR_MONITORING and CONTENT_CAPTURE are public.
    • Matching MCP tools getCampaign, createCampaign and updateCampaign are available by default under existing workspace and write permissions. Valid update entries can apply alongside userErrors; inspect returned state before retrying. Creator additions remain asynchronous and updateCampaign does not promise blind-retry safety.
    • The campaign_provisioning alpha key is retired. Sending it remains harmless; the same public surface is available without it. Supplied UTC timestamps retain existing whole-second precision.

    2026-10-17

    • renameLabel is generally available, with no alpha header, and the new mergeLabels folds one label into another existing label: every creator carrying the source ends up on the target, and the source is deleted. Both run in the background and return a pollable operationId; merging is irreversible. The matching renameLabel and mergeLabels MCP tools are listed for every write-capable caller. See Labels.
    • Both work in every workspace. The ["base"] refusal “Label management is not enabled for this workspace; contact support.” is gone.
    • New public types: RenameLabelPayload and MergeLabelsPayload. Nothing existing changed shape.
    • The labels alpha key is retired. Clients that still send it see the same surface as clients that do not, and get no error.
    • Pricing: each call is a write costing 50 points plus the payload fields you select, like any other mutation. Neither draws from the label-write limit.

    2026-10-16

    • Creator labels are generally available, with no alpha header. The labels query lists the workspace’s labels; createLabels, addLabelsToCreators, removeLabelsFromCreators, bulkAddLabelsToCreators, bulkRemoveLabelsFromCreators and deleteLabel change them, and the seven matching MCP tools (getLabels, createLabels, addLabelsToCreators, removeLabelsFromCreators, bulkAddLabelsToCreators, bulkRemoveLabelsFromCreators, deleteLabel) are listed for every caller. See the new Labels guide.
    • Label assignment is priced per creator. addLabelsToCreators and removeLabelsFromCreators take at most 40 creators and cost 50 points plus 100 for each further creator; they also share a per-workspace label-write limit of 40 creators at once, then one more every 2 seconds, answered with HTTP 429 when exceeded. A call your plan can never afford is a userErrors entry naming the largest call that fits. For more creators, bulkAddLabelsToCreators and bulkRemoveLabelsFromCreators take up to 1,000 creators, run as a background operation and cost a flat 250 points, with at most 2 in flight per workspace. The rate-limit calculator prices all four.
    • Renaming a label stays an alpha feature until label management is enabled for workspaces: renameLabel and its MCP tool need the labels key in X-Archive-Alpha-Features.
    • New public types: Label and the six mutation payloads (AddLabelsToCreatorsPayload, RemoveLabelsFromCreatorsPayload, BulkAddLabelsToCreatorsPayload, BulkRemoveLabelsFromCreatorsPayload, CreateLabelsPayload, DeleteLabelPayload).
    • Heads-up: an unfiltered operations query, and the MCP getOperations tool, now also return the workspace’s label operations (operationType delete_label, rename_label and merge_labels) and its bulk label edits (operationType update_custom_attributes or remove_custom_attributes), including ones started in the app. If your client assumed every listed operation is a refetch_engagement or a creator import, check operationType.
    • Label operations and bulk label edits send no operation.completed webhook; follow them with operation(id:) or the labels query.
    • Clients that send labels in X-Archive-Alpha-Features see the same GA surface plus renameLabel.

    2026-10-15

    • Creator import is generally available. The importCreators mutation and the importCreators MCP tool import creators into a workspace by social handle, with no alpha header. See the new Importing creators guide.
    • New public fields for following an import: Operation.failedRecordsCsvUrl, Operation.successfulRecordsCsvUrl and Operation.importSource, and OperationRecord.handles, OperationRecord.creatorId, OperationRecord.errorCode and OperationRecord.errorMessage. They are null for every operation that is not a creator import. The CreatorImportInput, CreatorImportHandleInput, CreatorImportHandle, CreatorImportProvider, CreatorImportSkipReason, CreatorImportSource and ImportCreatorsSkippedRow types are public too.
    • Heads-up: an unfiltered operations query, and the MCP getOperations tool, now also return the workspace’s creator imports (operationType oneschema_creator_import), including imports started in the app. If your client assumed every listed operation is a refetch_engagement, check operationType.
    • MCP getOperation and getOperationRecords now always return the creator-import fields (null for other operation types), so each call’s weighted rate-limit cost rises by the few points those fields add.
    • Clients that still send creator_import in X-Archive-Alpha-Features are unaffected; the key is ignored.
    • Requests the server can never answer no longer report extensions.code: "INTERNAL_SERVER_ERROR", which told clients to retry them. They now carry a client-error code: BAD_REQUEST (no query, a batched array, an unknown or ambiguous operationName), PERSISTED_QUERY_NOT_SUPPORTED, GRAPHQL_PARSE_FAILED (syntax errors, which previously had no code at all), GRAPHQL_VALIDATION_FAILED (query depth), INVALID_INPUT (variable values, first out of range, invalid cursors, conflicting arguments) and NOT_FOUND. creator(id:) with an unknown id now reports NOT_FOUND “Creator not found” instead of a non-null violation. HTTP statuses and messages of these GraphQL errors are unchanged except where noted in the Errors guide.
    • A string argument containing the NUL character (U+0000), whether inline, in a variable or anywhere inside an input object, list or JSON value, is now refused before execution with one INVALID_INPUT error naming where it sits (for example `$filter.tagsNames[0]`). Some such requests used to fail with INTERNAL_SERVER_ERROR, others ran as if nothing matched. A NUL in a field skipped with @skip / @include is refused too, and a refused request pays only the per-request minimum. MCP tools answer Invalid argument '$.<path>': must not contain the NUL character (U+0000).
    • A body that is not valid UTF-8 JSON now gets 400 with a JSON body instead of an empty HTML page, and GET, PUT, PATCH or DELETE on /api/v2 gets 405 with an Allow: POST header and a JSON body instead of 404. On /api/v2/mcp, the same body gets JSON-RPC -32700, a 405 carries a JSON-RPC error body, and a batch array names why it is rejected.
    • refetchEngagementBulk reports its item-count limit on userErrors[].field: ["itemIds"], and credit refusals with an empty field: [], instead of the placeholder ["base"].
    • Webhook deliveries awaiting a scheduled retry (status: FAILED with nextAttemptAt set) are now retried automatically once nextAttemptAt passes; previously such rows were never re-attempted. See retries.
    • sendWebhookTestEvent pings now carry the event type subscription.ping (envelope, X-Archive-Event-Type header and webhookEvents). A ping is a single attempt, never affects the subscription’s health counters or auto-disable, and is sent even while its endpoint host is paused after repeated transport failures.
    • redeliverWebhookDelivery now replays only a terminal FAILED delivery (nextAttemptAt: null) and never a subscription.ping delivery; anything else returns a not_replayable userError. enableWebhookSubscription’s 24-hour replay follows the same rule and keeps each replayed delivery’s lastError / lastResponseStatus until the replay settles.
    • createWebhookSubscription and updateWebhookSubscription now refuse a metadata value that is not a JSON object (including false), and report input errors on the paths ["input","name"], ["input","eventTypes"] and ["input","metadata"].
    • Webhook subscription name is limited to 200 characters and url to 2048 characters.
    • responseTimeMs is now recorded for non-2xx responses too, not only for successful deliveries.
    • Webhook endpoints are checked against more private and reserved address ranges: an address under the local-use NAT64 prefix 64:ff9b:1::/48 is judged by the IPv4 address it embeds, and Teredo (2001::/32) addresses are refused.
    • MCP getWebhookSubscriptions accepts cursor for pagination (after keeps working as a deprecated alias), and redeliverWebhookDelivery now advertises idempotentHint: false.
    • /api/v2 accepts the Authorization scheme in any case (bearer, BEARER), as HTTP requires. A lowercase bearer previously returned 401. Every 401 now carries a WWW-Authenticate: Bearer header. See authentication.
    • uploadItemFromUrl userErrors for a rejected URL now report field: ["input", "url"], the path of the URL in the mutation’s arguments. They previously reported ["url"]. The no-user error still reports ["shop"].
    • The MCP uploadItemFromUrl tool now advertises openWorldHint: true: the import fetches the URL you pass from outside Archive.
    • GET /oauth/authorize without a client_id or redirect_uri now returns HTTP 400 invalid_request instead of redirecting to the login page.
    • Corrected the docs: a mutation costs 50 plus 1 for each payload field it selects, not a flat 50; RateLimit’s t is an absolute Unix timestamp, not the IETF draft’s delta-seconds; schema introspection is disabled in production, so the reference and the Explorer are how you discover the schema. The site now publishes an llms.txt index of its Markdown pages. See rate limits and alpha features.
    • updateWebhookSubscription no longer reports success for an explicit null it ignored: metadata: null clears the metadata (as {} does), and null on any other field returns a userError on that field. The status userError lists the values as the enum spells them (ACTIVE, DISABLED_BY_USER).
    • MCP getWebhookSubscriptions also returns pageInfo { hasNextPage, endCursor } and totalCount, like the other list tools. The top-level hasNextPage and nextCursor stay, as deprecated copies. The tool always requests totalCount, so a default call now costs 20 credits instead of 15.
    • A bare OPTIONS request to /api/v2 or /api/v2/mcp gets 405 with Allow: POST instead of a 422. CORS preflights are unchanged.

    2026-08-21

    • Every docs page now links the terms of service and privacy policy in a footer; previously they were reachable only from the MCP security page.
    • This page now states the /api/v2 stability contract explicitly: the GA surface changes additively only, and a breaking change would ship as a new URL version. The GraphQL schema has operated under this rule since launch; MCP tools since their GA on 2026-08-14. It is now written down where partners can cite it.
    • New Webhooks guide: triggering model, the delivery request and signature verification, retry and auto-disable behavior, testing and monitoring, and per-plan limits. The webhook reference pages are unchanged; this adds the prose walkthrough.

    2026-08-14

    • MCP searchItems with responseFormat: "concise" now returns creator { id } on every node. This is the join key for creator-level comparisons, such as matching a campaign roster from searchCreators against that campaign’s posters; matching on accountName is unsafe there because a workspace can hold two creator records that share a handle. The added relation raises a concise searchItems call’s weighted cost by 8 (a 20-row page goes from 55 to 63) and adds one batched creator lookup (about 17 ms) per request. detailed responses are unchanged in both respects. See conventions and rate limits.

    2026-08-09

    • Rate-limit capacity and refill rate now vary by plan instead of being the same for every paying plan. Read your ceiling from the RateLimit-Policy header (q is the capacity, w the seconds to refill it) rather than assuming the previous flat numbers; for some plans the capacity is now lower. See rate limits.
    • Rate-limit buckets are per-workspace again. A workspace owned by an organization takes its capacity and refill rate from the organization’s plan but keeps its own balance, so one workspace’s traffic no longer drains its sibling workspaces’ quota. This reverses the organization-pooled buckets introduced on 2026-08-01. See rate limits.
    • New: Item.aiFilterReasons returns the explanation an AI Filter produced for each AI-generated custom-attribute value on an item. Every entry pairs to a value: a key is only returned when the item carries a value for it in customAttributes. reason is null when the run recorded no explanation and "" when the model produced an empty one. The list is empty, never null, when an item has none.
    • New: CustomAttributeSchema.aiGenerated marks which attribute keys are generated by an AI Filter, so you can discover which keys carry reasons before requesting them.

    2026-08-06

    • New: the MCP section documents Archive’s Model Context Protocol server: its read tools, write tools, and the conventions that apply across every tool. Each tool wraps exactly one public root field, and every operation’s reference page now links to the tool that wraps it.
    • The seven list fields that return a plain list rather than a connection now return at most 1000 rows. The cap is silent; there is no cursor to continue from. See pagination.
    • itemIdsByUrl accepts any public HTTP(S) URL. It returns INVALID_URL only for a malformed URL, a non-HTTP scheme, or a private/reserved host. A well-formed public URL with no matching item now returns NOT_FOUND rather than INVALID_URL.
    • Clarified that a Social Profile View’s and a workspace Creator View’s filters blob is stored and echoed back but not applied when reading through socialProfiles(presetId:) or creators(presetId:). This is long-standing behavior, not a change; the documentation was previously wrong. See filtering.

    2026-08-02

    • MCP requests now charge and refund the same pooled organization bucket as /api/v2, so routing load through /api/v2/mcp no longer multiplies an organization’s capacity. See rate limits.

    2026-08-01

    • POST /api/v2 accepts variables as a JSON-encoded string as well as a JSON object, and honors operationName, so a document that defines several operations is usable. A string variables previously returned a non-JSON error page.
    • An unhandled resolver error now returns a coded GraphQL error envelope instead of a non-JSON error page. See errors.
    • Workspaces owned by the same organization share one rate-limit bucket rather than one bucket each. See rate limits.
    • Breaking (MCP only): tools/list now requires credentials and answers an unauthenticated call with the same RFC 9728 discovery challenge as initialize.
    • MCP tool arguments now enforce the minimum, maximum, and minLength bounds that tools/list advertises for them.
    • Revoking an MCP connection now revokes its whole refresh-token rotation lineage, so a superseded credential can no longer mint access tokens.
    • uploadItemFromUrl rejects URLs whose host is a private or reserved address, or that carry no host at all.

    2026-07-31

    • The weighted cost model is now enforced: a request that exhausts the bucket is rejected with HTTP 429, a Retry-After header, and a RATE_LIMIT_EXCEEDED error. It previously ran in shadow mode and only reported cost in headers. See rate limits.

    2026-07-30

    • after cursors are bound to the connection that issued them and shape-checked per entity, so a cursor borrowed from another connection fails with the coded Invalid cursor error rather than an internal error. See pagination.
    • The payload-heavy MCP read tools accept responseFormat: "concise" | "detailed". detailed is the default and its shape is unchanged.

    2026-07-29

    • A query canceled by the database statement timeout now returns a GraphQL error envelope with the stable QUERY_TIMEOUT code instead of a non-JSON error page. See errors.
    • competitorBrands and competitorBrand keep returning roster data when aggregate metrics are temporarily unavailable, and report the outage as a SERVICE_UNAVAILABLE error alongside the data. Match on extensions.code to decide whether to retry; ownMetricsStatus: UNAVAILABLE alone no longer implies a retry will help.
    • MCP tool responses were trimmed to the fields each tool needs for its stated purpose; 20 tools now return fewer fields.
    • MCP honors the MCP-Protocol-Version request header.

    2026-07-28

    • A superseded MCP refresh token is now revoked when its successor is used. Superseded refresh tokens previously stayed valid indefinitely. The OAuth discovery document also advertises the revocation_endpoint, so clients can log out cleanly.

    2026-07-27

    • refetchEngagementBulk is available to every API-enabled workspace. The separate engagement-refresh feature gate is gone, and its userErrors no longer report “feature not enabled”. The mutation is still credit-gated.
    • MCP answers the ping method with the protocol’s empty result instead of -32601 Method not found.

    2026-07-26

    • competitorBrands now returns your own brand as one additional pinned node on the first page, named You and flagged by the new isOwnBrand field, so you can compute self-vs-competitor Share of Voice. totalCount still counts tracked competitors only, so page one returns one more edge than it reports. See pagination.
    • CompetitorBrand gained earnedMediaValue, engagementsTotal, impressions, influencersCount, postsCount, and reach, plus ownMetricsStatus, which is set only on the own-brand entry and distinguishes “computed, no data in window” from “could not be computed”.
    • competitorBrands and competitorBrand accept period (WEEK/MONTH, default MONTH) and date to select the metric window. competitorBrand also resolves the own-brand entry by its stable ID.

    2026-07-25

    • Malformed or foreign pagination cursors now fail with a coded Invalid cursor error instead of a generic internal error, across every cursor-paginated connection.
    • Breaking (MCP only): every MCP tool argument is now camelCase (itemId, presetId, viewIds), matching the GraphQL field names one-to-one. Stored keys inside the opaque saved-view filters blob stay snake_case; clone them verbatim. The /api/v2 GraphQL surface is unchanged.

    2026-07-24

    • MCP responses now carry the same IETF RateLimit and RateLimit-Policy headers as /api/v2. See rate limits.

    2026-07-23

    • Recalibrated the base rate-limit plan to a capacity of 15000 with a refill of 250 per second.
    • Published the Archive API documentation site: task-oriented guides plus a schema-driven reference for every query, mutation, and type.
    • Documented the generally available surface: bearer-token authentication, the WORKSPACE-ID workspace model, Relay-style pagination, the items filtering and search guide, the rate-limit layers, and the error model.