Slack Canvas Access After Migration: canvases.access.set Guide
App-created Slack canvases are invisible until shared. Learn how canvases.access.set works, its 20-ID batch limit, channel-only constraints, and error handling.
Planning a migration?
Get a free 30-min call with our engineers. We'll review your setup and map out a custom migration plan — no obligation.
Schedule a free call- 1,500+ migrations completed
- Zero downtime guaranteed
- Transparent, fixed pricing
- Project success responsibility
- Post-migration support included
Slack Canvas Access After Migration: canvases.access.set Complete Guide
Last verified against Slack API documentation: July 2025. Slack Canvas API methods have changed since the 2023 launch; check docs.slack.dev for current method signatures before production use.
A canvas created through canvases.create with a bot token is owned by the app and invisible to every human in the workspace. There is no implicit sharing, no inheritance from the channel the bot lives in, and no admin override that auto-distributes newly created canvases. If your migration script creates 500 canvases and never calls canvases.access.set, you have 500 documents that exist but nobody can find, open, or read. (docs.slack.dev)
This guide covers the full mechanics of granting access to migrated Slack canvases: the API constraints, the batching math, the channel-type restrictions, the Enterprise Grid ceiling, the token-type differences, ownership transfer from a bot context, and the ordering rules that determine whether your users see content or an error.
If you're migrating from Quip to Slack Canvases, start with our Quip to Slack Canvases migration guide for the end-to-end process. This article focuses on the access-granting step that guide doesn't cover in API depth.
Created does not mean visible. Every canvas created via canvases.create with a bot token is owned by the bot. No workspace member can see it until you call canvases.access.set. This is documented behavior, not a bug. Plan your migration script accordingly.
Why App-Created Canvases Are Invisible by Default
A standalone canvas created via the Slack API is owned by the acting token's identity. When that token belongs to a bot, the bot is the owner and sole viewer. No workspace member — not even a Workspace Owner or Org Admin — can see the canvas in their sidebar, search results, or canvas list until access is granted through the API.
Slack documents canvases.create as creating "a new standalone canvas owned by the acting user." When the acting user is a bot, the bot is that owner. The canvas exists on Slack's servers, occupies storage, and has a valid canvas_id (prefixed with F), but it is a private document belonging to a non-human identity. The default canvas visibility is Invite only, meaning the canvas will not appear in search or the canvas browser for anyone it has not been shared with. (docs.slack.dev)
This matters for migrations because a human who creates a canvas in the UI usually shares it immediately. A migration app does not. A technically successful create — "ok": true — can still produce a cutover failure from the user's perspective: the content exists, but the people it was migrated for cannot see it.
How canvases.access.set Works
canvases.access.set is Slack's Web API method for granting read, write, or owner access to a canvas for specified channels or users. It requires the canvases:write scope on either a bot or user token. Slack documents it at Tier 3 rate limits — 50 or more requests per minute per workspace per app. (docs.slack.dev)
Required parameters
| Parameter | Type | Description |
|---|---|---|
token |
string | Bot or user token with canvases:write scope |
canvas_id |
string | The F-prefixed ID returned by canvases.create |
access_level |
enum | read, write, or owner |
Target parameters (mutually exclusive)
| Parameter | Type | Limit | Description |
|---|---|---|---|
channel_ids |
array | Max 20 per call | Channels to grant access to |
user_ids |
array | Max 20 per call | Users to grant access to |
You cannot pass both channel_ids and user_ids in the same request. If a document needs to be shared with three channels and two specific users, that requires two separate HTTP requests.
The three access levels
read— view-only access to the canvas content.write— read and edit access.owner— transfers ownership. Only works withuser_ids, notchannel_ids. Only the current owner can set another user as owner, and cross-team ownership transfers are blocked. Passingchannel_idswithownerreturnsinvalid_arguments. (docs.slack.dev)
A minimal channel-based grant:
curl -X POST https://slack.com/api/canvases.access.set \
-H "Authorization: Bearer xoxb-your-bot-token" \
-H "Content-Type: application/json" \
-d '{
"canvas_id": "F07ABC123DE",
"access_level": "write",
"channel_ids": ["C01ABCDEF", "C02GHIJKL"]
}'If you are granting access to private channels, the app must be a member of those channels first. Slack returns restricted_action or no_permission when the bot lacks channel access. (docs.slack.dev)
Bot Tokens vs. User Tokens: What Changes
Token type determines what operations are available and who appears as the acting identity. For canvas access operations, the practical differences are:
| Capability | Bot Token (xoxb-) |
User Token (xoxp-) |
|---|---|---|
Grant read/write via channel_ids |
✓ | ✓ |
Grant read/write via user_ids |
✓ | ✓ |
Transfer owner via user_ids |
Only if the bot is current owner | Only if the authed user is current owner |
| Access private channels | Must be invited first | Must be a member first |
| Canvas creator (and initial owner) | Bot identity | Human user identity |
The critical migration implication: when your script creates canvases with a bot token, the bot is the owner. The ownership transfer question — "can the bot hand ownership to a human?" — has a specific answer: yes, the bot can call canvases.access.set with access_level: "owner" and user_ids: ["U_TARGET_USER"], using the same bot token that owns the canvas. This is the correct sequence for post-migration stewardship handoff.
However, once ownership transfers to a human user, any subsequent owner changes require a user token for that human, not the bot token. The bot is no longer the owner and cannot perform further ownership transfers. Plan the ownership chain before you begin: bot creates → bot transfers to human steward → human manages further ownership through the UI or a user token.
Cross-team ownership transfers are explicitly blocked by Slack's API regardless of token type. (docs.slack.dev)
Why channel_ids Is the Only Reliable Pattern
Granting access by user_ids fails when the user is already a member of a channel where the canvas is shared. This is not a race condition — it is documented behavior. Slack's own documentation states: "Even if the user is a member of a channel where the canvas has been shared, calling the canvases.access.set method with a user_ids argument will fail. Since the permissions are set at the channel level, they must also be changed at the channel level." (docs.slack.dev)
The same model applies on the delete side: if a canvas was shared through a channel, you cannot remove one person's access with user_ids. You must update the channel permissions instead. Once you accept that Slack Canvas permissions are channel-first, your migration logic gets simpler and more predictable.
In a migration context, user_ids is impractical as your primary distribution mechanism. You would need to first send the canvas link directly to every individual user (via chat.postMessage to each user's DM), then call canvases.access.set with their user IDs. For a migration touching hundreds of canvases and thousands of users, that sequence is unreasonable.
Design your migration around channel_ids from the start. Grant access to the channels where the document belongs, and every member of those channels inherits the access level. Treat user_ids as a special-case tool for DM-based sharing or ownership transfers only.
One trade-off to watch: sharing through a public channel can widen visibility more than intended. If an invite-only canvas is shared in a public channel, it becomes visible to everyone in that workspace or Enterprise organization. The right approach is to map each document to the smallest stable set of channels that matches the source audience. (slack.com)
Decision table: channel_ids vs. user_ids vs. manual follow-up
| Scenario | Recommended approach |
|---|---|
| Canvas shared to a team or project | channel_ids → target channel |
| Canvas shared to a few users in a common channel | channel_ids → their shared channel |
| Canvas shared via DM in source system | user_ids if no shared channel exists |
| Canvas owner transfer to human steward | user_ids with access_level: "owner" |
| User is already in a channel where canvas is shared | Update via channel_ids only |
| Users have no common channel | Create a private channel, then channel_ids |
| Truly org-wide document | channel_ids to high-membership channels (#general, #announcements) |
Do not build a general migration on user_ids. This is a documented API constraint. Slack's architecture ties canvas access to channel-level permissions. Use direct user grants only for narrow DM-style cases or ownership transfers.
DM and MPDM Channel IDs Are Rejected
canvases.access.set rejects direct message (DM) and multi-party direct message (MPDM) channel IDs when passed as channel_ids. The method only accepts regular channel IDs — public or private channels prefixed with C. Passing a DM ID (prefixed with D) or MPDM ID (prefixed with G) results in an unsuccessful request. (docs.slack.dev)
If your source system has documents shared in DMs — common in Quip, where documents can be shared with individual users — you need a different approach:
- Map DM-shared documents to a channel. If users A, B, and C shared a Quip doc, create or identify a private channel containing those users and share the canvas there.
- Accept the gap. Some DM-context documents will not map cleanly to Slack's channel-based model. Document these cases and handle them as manual follow-ups post-migration.
- Use
chat.postMessagewith the canvas link to each user's DM as a notification mechanism, separate from the access grant.
Pre-filter DM and MPDM IDs in your data mapping layer before you hit the API. A simple ID-prefix check (if not channel_id.startswith("C")) catches this class of error before it fails at the API boundary.
The 20-ID Batch Limit and How to Loop It
Each call to canvases.access.set accepts a maximum of 20 channel_ids or 20 user_ids. If a canvas needs to be shared to 60 channels, that requires a minimum of three API calls. (docs.slack.dev)
Batching logic in Python using the Slack SDK, with correct Retry-After header handling:
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
import time
client = WebClient(token="xoxb-your-bot-token")
def grant_canvas_access(canvas_id: str, channel_ids: list[str], access_level: str = "write"):
"""Grant access in batches of 20 channel IDs, respecting Retry-After on 429."""
batch_size = 20
for i in range(0, len(channel_ids), batch_size):
batch = channel_ids[i:i + batch_size]
while True: # retry loop for rate limiting
try:
response = client.canvases_access_set(
canvas_id=canvas_id,
access_level=access_level,
channel_ids=batch,
)
if response["ok"]:
break # success, move to next batch
else:
print(f"Failed batch {i // batch_size}: {response['error']}")
break # non-retryable failure
except SlackApiError as e:
if e.response.status_code == 429:
retry_after = int(e.response.headers.get("Retry-After", 60))
print(f"Rate limited. Waiting {retry_after}s before retry.")
time.sleep(retry_after)
# loop continues to retry the same batch
else:
print(f"Error on batch {i // batch_size}: {e}")
breakThe Tier 3 rate limit gives you 50+ requests per minute per workspace per app. Under normal conditions, one request per 1.2 seconds is a conservative pace. Under actual rate limiting, always read the Retry-After header — Slack sets this dynamically and it may be longer than 1.2 seconds. Hardcoding a fixed sleep value is incorrect behavior that will fail under load.
Migration math example: 200 canvases each shared to 40 channels = 200 × 2 batches = 400 API calls. At 50 calls per minute, that is roughly 8 minutes of API time just for access grants, assuming no rate limit responses. Factor this into your migration timeline and consider running access grants off-hours if your workspace is active.
What Does the 1,000-Channel Limit Mean?
A single canvas can be shared to a maximum of 1,000 channels. This limit applies to the cumulative total across both API-based grants and manual sharing through the UI. Your migration script and any subsequent human sharing count against the same ceiling.
Slack's own API documentation does not prominently surface this limit in the canvases.access.set reference page. If you encounter this ceiling, the API returns an error rather than a partial success. Test against a staging workspace with a high channel count before running production migrations for org-wide documents.
For most migrated documents, 1,000 channels is well above what's needed. But on Enterprise Grid — where organizations routinely have thousands of channels across multiple workspaces — this limit matters for company-wide reference documents like HR policies, engineering standards, or all-hands announcements.
Alternatives for org-wide documents:
- Share to high-membership org-wide channels. Instead of sharing to 2,000 individual channels, share to the 5–10 channels that already have the broadest membership (
#general,#announcements, department-level channels). - Duplicate by workspace or audience. Past a certain scale, maintaining one canonical canvas shared everywhere is less practical than creating workspace-specific copies.
- Keep the source of truth elsewhere. If a document is truly org-wide, consider whether it belongs in a knowledge base rather than Slack's channel-based access model. A canvas is a channel-scoped document, not a broadcast medium.
The 1,000-channel limit applies to both API and manual sharing. If your script shares a canvas to 990 channels during migration, users can only manually add it to 10 more.
Enterprise Grid: Multi-Workspace Canvas Sharing
On Enterprise Grid, canvas access behavior depends on whether channels span workspaces. When you share a canvas to a channel that belongs to a specific workspace, members of that workspace can access it. When a channel is shared across workspaces (a multi-workspace channel), canvas access follows the channel's membership across those workspaces.
Key Grid-specific constraints:
- Cross-team ownership transfers are blocked.
canvases.access.setwithownerwill fail if the target user is in a different workspace than the canvas owner. Ownership must remain within the same workspace. - Bot scope is workspace-scoped. A bot token from workspace A cannot grant access to channels in workspace B, even within the same Grid org. You need separate app installations (and tokens) per workspace.
- Org-wide channels. If your Grid org uses org-wide channels (channels that span all workspaces), sharing a canvas to one of these channels can expose it across the entire organization. Verify channel scope before bulk-sharing.
- Audit logs. Enterprise Grid customers can use Slack's Audit Logs API to verify canvas access events:
canvas_access_added,canvas_access_upgraded, andcanvas_access_revokedare the relevant event types.
If your Slack topology is still changing, read our Slack Enterprise Grid migration guide before you lock in the document distribution strategy.
Why You Must Write Content Before Granting Access
Always write the full document content before calling canvases.access.set. The moment access is granted, channel members can see the canvas. If the content is not finished, users see a partial or empty document.
This ordering matters for three reasons:
-
User experience. A canvas appearing in a channel with half-written content erodes trust in the migration. Users see broken formatting, missing sections, or placeholder text and assume the migration failed.
-
Edit contention. If you grant write access before content is complete, a human user could edit the canvas while your script is still writing via
canvases.edit. Slack does not provide document-level locking through the API, so concurrent edits produce unexpected results. -
The
canvases.editone-operation limit. Each call tocanvases.editsupports only one operation (insert, replace, or delete). If your document requires multiple edit operations after creation, you need sequential API calls. Granting access mid-sequence means users see intermediate states.
The correct sequence for each canvas:
canvases.create— create the canvas with initial content (up to 1 MiB of markdown)canvases.sections.lookup— retrieve section IDs if you need to target specific sections for subsequent editscanvases.edit— apply any additional content operations if needed (one operation per call, sequential)canvases.access.set— grant access only after all content is finalized- Verify — confirm access state (see verification section below)
canvases.access.setwithowner— transfer ownership to a human steward if required (final step)
Transfer ownership last. Ownership transfer only works with user_ids, only by the current owner, and not across teams. Once the bot transfers ownership to a human, subsequent ownership changes require a user token for that human. Make it the final step in your sequence.
Verifying That Access Was Actually Granted
Your migration script should confirm access state programmatically, not assume success from "ok": true.
The primary verification tool is files.info, which returns metadata about the canvas including sharing state. A canvas with a valid canvas_id is also a file with an F-prefixed ID — the same identifier.
def verify_canvas_access(canvas_id: str) -> dict:
"""Check sharing state of a canvas via files.info."""
response = client.files_info(file=canvas_id)
if not response["ok"]:
return {"verified": False, "error": response["error"]}
file_obj = response["file"]
return {
"verified": True,
"is_public": file_obj.get("is_public", False),
"shared_in_channels": file_obj.get("channels", []),
"shared_in_groups": file_obj.get("groups", []), # private channels
"num_stars": file_obj.get("num_stars", 0),
}The files.info response includes:
channels— list of public channel IDs the file is shared ingroups— list of private channel IDs the file is shared inis_public— boolean indicating public visibilityshares— detailed sharing data including per-channel share timestamps
Compare the channels and groups lists against your intended-grants ledger. A canvas that returns "ok": true from canvases.access.set but does not appear in the expected channel list in files.info indicates a silent failure requiring investigation.
For Enterprise Grid customers, the Audit Logs API provides a more complete picture. Query for canvas_access_added events filtered by entity_id (the canvas ID) to confirm every grant was recorded server-side.
Revoking Access: canvases.access.delete
canvases.access.delete is the symmetric operation for removing canvas access. It shares the same parameter structure as canvases.access.set and the same constraints: 20 IDs per call, mutually exclusive channel_ids vs. user_ids, no DM IDs, requires canvases:write scope.
curl -X POST https://slack.com/api/canvases.access.delete \
-H "Authorization: Bearer xoxb-your-bot-token" \
-H "Content-Type: application/json" \
-d '{
"canvas_id": "F07ABC123DE",
"channel_ids": ["C01ABCDEF"]
}'Key behavioral constraints for canvases.access.delete:
- Channel-level grants must be revoked at the channel level. If access was granted via
channel_ids, you cannot remove an individual user's access usinguser_ids. You must revoke the channel's access entirely. - Revoking a channel removes access for all channel members simultaneously. There is no mechanism to remove one user's access while preserving another's through the same channel grant.
access_levelis not a parameter for delete. The call removes the relationship entirely; it does not downgrade fromwritetoread.
Migration error recovery pattern: if your access grant script runs and you discover incorrect grants (wrong channel, wrong access level), call canvases.access.delete for the incorrect grants before re-running canvases.access.set with the correct parameters. (docs.slack.dev)
Error Handling: Bad IDs, Partial Failures, and Recovery
If any ID in a channel_ids or user_ids array is invalid, the entire call fails. The API returns "ok": false with an error like channel_not_found. Slack does not apply partial success for validation errors — none of the valid IDs in the same batch receive access.
There is an important distinction for server-side errors: Slack documents that fatal_error and internal_error responses may have partially succeeded. For these transient errors, do not blindly replay the entire batch. Reconcile against files.info first, because some grants in that batch may already have been applied. (docs.slack.dev)
def grant_with_retry(canvas_id: str, channel_ids: list[str], access_level: str = "write"):
"""Grant access with single-ID fallback on batch failure, Retry-After on 429."""
batch_size = 20
for i in range(0, len(channel_ids), batch_size):
batch = channel_ids[i:i + batch_size]
try:
resp = client.canvases_access_set(
canvas_id=canvas_id,
access_level=access_level,
channel_ids=batch,
)
if not resp["ok"]:
error = resp.get("error", "")
if error in ("internal_error", "fatal_error"):
# May have partially succeeded — reconcile before retry
queue_for_reconcile(canvas_id, batch)
else:
# Validation error — fall back to one-at-a-time
for cid in batch:
try:
single_resp = client.canvases_access_set(
canvas_id=canvas_id,
access_level=access_level,
channel_ids=[cid],
)
if not single_resp["ok"]:
log_failure(canvas_id, cid, single_resp["error"])
except SlackApiError as e:
if e.response.status_code == 429:
retry_after = int(e.response.headers.get("Retry-After", 60))
time.sleep(retry_after)
# retry this single ID
else:
log_failure(canvas_id, cid, str(e))
except SlackApiError as e:
if e.response.status_code == 429:
retry_after = int(e.response.headers.get("Retry-After", 60))
time.sleep(retry_after)
else:
log_failure(canvas_id, "batch", str(e))Error code reference
| Error | Meaning | Action |
|---|---|---|
channel_not_found |
Channel ID does not exist or bot is not a member | Verify channel exists; ensure bot is invited |
canvas_not_found |
Canvas ID is wrong or was deleted | Check creation step succeeded |
invalid_parameters |
Both channel_ids and user_ids were passed |
Fix the request — they are mutually exclusive |
invalid_arguments |
owner access level used with channel_ids |
Only user_ids supports ownership transfer |
no_permission |
Bot lacks access to the channel | Invite the bot to the channel first |
restricted_action |
Workspace admin settings block this operation | Check canvas sharing settings in admin panel |
ratelimited |
Too many requests | Read Retry-After header; do not use fixed sleep |
internal_error / fatal_error |
Server-side issue | May have partially succeeded — reconcile via files.info before retry |
not_allowed |
Cross-team ownership transfer attempted | Ownership must stay within the same workspace |
cant_set_dm_channel |
DM or MPDM ID passed as channel_ids |
Pre-filter IDs; only C-prefixed IDs are valid |
Reconciling Source Permissions with Slack's Channel-First Model
Permission reconciliation is the job of translating the source system's access control list into Slack's channel-first model without silently widening or dropping access. That translation is never 1:1 when the source system supports permission states that Slack Canvas does not.
Salesforce's own Quip-to-Slack documentation is direct about the gaps: account matching is email-based, users without a matching Slack account lose access, comment-only users become read-only, external users from the originating Quip company can lose access, and no users should gain elevated permissions automatically from the conversion. (help.salesforce.com)
Access level translation table
| Source system level | Slack Canvas equivalent | Notes |
|---|---|---|
| Owner / Creator | owner (via user_ids) |
Only one owner per canvas; bot transfers first |
| Can edit / Collaborator | write |
Standard editor access |
| Can comment | read |
Slack Canvas has no comment-only level |
| Can view | read |
Direct mapping |
| No access / Removed | canvases.access.delete |
Must explicitly revoke if previously granted |
The three reconciliation steps
1. Extract the source sharing model. For each document, capture who has access and at what level. In Quip, this comes from the get_folder and get_thread API responses. In Confluence, it is page restrictions plus space permissions. Export this as a structured grants table: (document_id, principal_type, principal_id, access_level).
2. Map source principals to Slack channels. This is the hard part. A Quip document shared with users A, B, and C does not have a natural channel equivalent unless those users already share a channel. Your options:
- If the users share a channel, use that channel ID
- If they don't, create a dedicated private channel (adds channel sprawl but preserves access isolation)
- If the document was shared with a Quip folder, map the folder to the closest Slack channel or set of channels
3. Translate access levels. Use the table above. If multiple people "owned" a source document, pick one Slack owner and downgrade the rest to write. Document this decision in your migration ledger.
The intended-grants ledger
Your migration needs a grants ledger: a database table recording (canvas_id, channel_id_or_user_id, access_level, grant_status, verified_at) for every intended grant. Structure:
CREATE TABLE canvas_grants (
canvas_id TEXT NOT NULL,
target_type TEXT NOT NULL CHECK (target_type IN ('channel', 'user')),
target_id TEXT NOT NULL,
access_level TEXT NOT NULL CHECK (access_level IN ('read', 'write', 'owner')),
grant_status TEXT NOT NULL DEFAULT 'pending', -- pending, granted, failed, reconcile_needed
attempted_at TIMESTAMP,
verified_at TIMESTAMP,
error_code TEXT
);Slack's API does not provide a method to read back the complete current access list for a canvas. Your database is the source of truth for intended state. Use files.info for spot checks, and on Enterprise Grid use audit log events (canvas_access_added, canvas_access_upgraded, canvas_access_revoked) to verify what actually happened server-side.
Build the permissions mapping table before you write any migration code. Export your source system's sharing data, map each document to its target Slack channels, and validate that every channel exists and that your bot is a member. Discovering missing channels mid-migration turns a batched operation into an incident.
Canvas Access Checklist for Migration Scripts
Before running a bulk canvas migration, verify each of these:
- Bot token has
canvases:writescope (andcanvases:readif usingcanvases.getContentfor verification) - Bot is a member of every target channel (
conversations.joinfor public channels; admin invite for private) - No target channel IDs are DMs (
D-prefix) or MPDMs (G-prefix) — pre-filter with ID prefix check - Channel list for each canvas is under 1,000 cumulative (API + manual)
- Batching logic chunks
channel_idsinto groups of ≤20 - Content is fully written before
canvases.access.setis called -
canvases.sections.lookupused before any section-targetedcanvases.editoperations - Rate limit handling reads
Retry-Afterheader on HTTP 429 (not fixed sleep) - Error handling distinguishes validation failures (retry individually) from
internal_error/fatal_error(reconcile first) - Intended-grants ledger populated before migration begins
- Post-migration verification runs
files.infoagainst a sample of canvases and compares to ledger - Ownership transfer to human steward is the final step, using
user_idswithaccess_level: "owner" -
canvases.access.deletescript prepared for rollback of incorrect grants - On Enterprise Grid: bot token is scoped to the correct workspace; cross-workspace operations use separate tokens
The Step That Decides Whether the Migration Worked
A Slack canvas migration is only successful when the right humans can open the right document on day one. Content conversion is the straightforward part. The harder part is translating a source system's person-centric or folder-centric sharing model into Slack's channel-centric permissions, batching those grants safely, handling the bot-to-human ownership handoff correctly, and proving the result after cutover through programmatic verification.
Access granting is the step where the most failures occur silently. A canvas that exists but nobody can see looks like a failed migration to the user — even though the creation succeeded. The debugging cycle ("is the content there? is the access set? is the bot in the channel? is the channel a DM? did the Retry-After get respected? did the partial-success case get reconciled?") can consume more time than the actual data migration if the scaffolding isn't built before cutover.
Frequently Asked Questions
- Why can't anyone see my Slack canvas after creating it with the API?
- A canvas created via canvases.create with a bot token is owned by the bot. The default visibility is Invite only. No workspace member can see it until you call canvases.access.set to grant read or write access to specific channels or users.
- Can I mix channels and users in a single canvases.access.set call?
- No. The channel_ids and user_ids parameters are mutually exclusive. You must make separate API calls if you need to grant access to both channels and specific users.
- What happens if one channel ID in a canvases.access.set batch is invalid?
- The entire call fails and none of the IDs in the batch receive access. For validation errors like channel_not_found, fall back to single-ID calls to isolate the bad ID. For internal_error or fatal_error, reconcile first because the batch may have partially succeeded.
- Can I share a Slack canvas to a DM or MPDM using canvases.access.set?
- No. The channel_ids parameter only accepts regular public and private channel IDs. DM and MPDM IDs are rejected. For DM-based sharing, Slack suggests user_ids, but that requires the canvas to have been sent directly to the user first.
- How many channels can a single Slack canvas be shared to?
- A single canvas can be shared to a maximum of 1,000 channels. This limit covers both API-driven and manual sharing combined. On Enterprise Grid with thousands of channels, org-wide documents may need distribution via high-membership channels instead.