instructure/canvas-lms · error · GraphQL::ExecutionError
Must specify exactly one of id or sisId
Error message
Must specify exactly one of id or sisId
What it means
Canvas's GraphQL `account` resolver accepts either a GraphQL/legacy `id` or a SIS `sis_id`, but not both and not neither. It raises this GraphQL::ExecutionError when the argument pair is ambiguous or missing, so the lookup target is unambiguous.
Solutions
- Pass exactly one of `id` or `sisId`; remove the other argument from the query/variables.
- If both values exist, decide which lookup you need (SIS IDs require SIS import context) and drop the other.
- Check variable serialization so unset variables are omitted rather than sent as empty strings/nulls.
Example fix
// before
query { account(id: "1", sisId: "acc-100") { name } }
// after
query { account(sisId: "acc-100") { name } } Defensive patterns
Strategy: validation
Validate before calling
const argCount = [vars.id, vars.sisId].filter(v => v != null && v !== '').length;
if (argCount !== 1) throw new Error('account requires exactly one of id or sisId'); Type guard
const hasExactlyOne = (vars) => ['id','sisId'].filter(k => vars[k] != null && vars[k] !== '').length === 1;
Try / catch
try { await gql(ACCOUNT_QUERY, vars); } catch (e) { if (e.message.includes('Must specify exactly one of id or sisId')) { vars = pickSingleIdentifier(vars, ['id','sisId']); retry } else throw e; } Prevention
- Use separate query documents for id-lookup vs sisId-lookup instead of one query with both optional arguments.
- Strip null/empty-string variables before sending requests.
- Document which lookup mode your integration uses and enforce it in a shared request helper.
When it happens
Trigger: Calling query { account(...) } with both id and sisId supplied, or with neither supplied (or both null). Any other argument combination (exactly one) is accepted.
Common situations: Clients templating the query pass both fields as variables with one left as an empty value that still serializes; frontend code copies a query snippet and leaves a stale id variable when switching to sisId lookup; omitting arguments entirely when testing in GraphiQL.
Related errors
- Must specify an id or an assignment_id and user_id or an…
- Must specify exactly one of id or name
- Must specify exactly one of ids or sisIds
- A maximum of 50 assessees can be provided at once
- A maximum of 50 assessors can be provided at once
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/a1504b3cf1053d8c.
Report an issue: GitHub.
Appendix: source
Thrown at app/graphql/types/query_type.rb:45
field :legacy_node, GraphQL::Types::Relay::Node, null: true do
description "Fetches an object given its type and legacy ID"
argument :_id, ID, required: true
argument :type, LegacyNodeType, required: true
end
def legacy_node(type:, _id:) # rubocop:disable Lint/UnderscorePrefixedVariableName -- named for DSL reasons
GraphQLNodeLoader.load(type, _id, context)
end
field :account, Types::AccountType, null: true do
argument :id,
ID,
"a graphql or legacy id",
required: false,
prepare: GraphQLHelpers.relay_or_legacy_id_prepare_func("Account")
argument :sis_id, String, "a id from the original SIS system", required: false
end
def account(id: nil, sis_id: nil)
raise GraphQL::ExecutionError, "Must specify exactly one of id or sisId" if (id && sis_id) || !(id || sis_id)
return GraphQLNodeLoader.load("Account", id, context) if id
GraphQLNodeLoader.load("AccountBySis", sis_id, context) if sis_id
end
field :user, Types::UserType, null: true do
argument :id,
ID,
"a graphql or legacy id",
required: true,
prepare: GraphQLHelpers.relay_or_legacy_id_prepare_func("User")
end
def user(id:)
GraphQLNodeLoader.load("User", id, context)
end
field :course, Types::CourseType, null: true do
argument :id,View on GitHub (pinned to 1c9f0bb801)