MPC Tally API Server
Tally API - LLM Query Construction Rules (Mandatory & Unbreakable)
Introduction
This document outlines the mandatory and unbreakable rules for Large Language Models (LLMs) when constructing queries for the Tally API. These rules are not suggestions—they must be strictly followed to ensure correct, efficient, and error-free GraphQL queries. Failure to adhere to any of these rules will result in query errors, inaccurate data, and is considered a fatal error. There is no acceptable deviation from these rules.
Core Principles
Never Assume: You must not assume any default values or behaviors for sort, filter, or other optional input parameters. You must explicitly declare them in the query.
Type Awareness: You must always be acutely aware of the GraphQL types involved, especially interface and union types, and use inline fragments accordingly. Failure to do so is a fatal error.
Fragment Prioritization: You must use fragments to minimize repetition, improve maintainability, and ensure efficient queries. Not using fragments is unacceptable.
Explicit Field Selection: You must always explicitly request each field you need, and never assume fields will be returned automatically.
Pagination: You must always use pagination where appropriate to ensure complete query results are retrieved, using the page input and pageInfo fields.
Correct API Use: You must adhere to API constraints. Some queries have required fields that must be used correctly.
Schema Consultation: You must consult the complete schema reference before creating any queries.
Multi-step Queries: You must properly structure multi-step queries into a sequence of dependent queries if data from one query is needed for a subsequent query.
Fragment Usage: All Fragments must be used, and any unused fragments must be removed.
Rule 1: Interface and Union Type Handling (Mandatory)
Problem: The nodes field in paginated queries often returns a list of types that implement a GraphQL interface (like Node), or are part of a union type. You cannot query fields directly on the interface type.
Solution: You must use inline fragments (... on TypeName) to access fields on the concrete types within a list of interface types. Failure to do so is a fatal error.
Example (Correct):
query GetOrganizations {
organizations {
nodes {
... on Organization { # Correct: Uses inline fragment
id
name
slug
metadata {
icon
}
}
}
pageInfo {
firstCursor
lastCursor
count
}
}
}
content_copy
download
Use code with caution.
Graphql
Example (Incorrect - Avoid):
query GetOrganizations {
organizations {
nodes {
id # Incorrect: querying on the interface directly
name # Incorrect: querying on the interface directly
slug # Incorrect: querying on the interface directly
}
}
}
content_copy
download
Use code with caution.
Graphql
Specific Error Case: Attempting to query fields directly on the nodes field when querying votes without the ... on Vote fragment. This is a fatal error.
query GetVotes {
votes(input: {
filters: {
voter: "0x1B686eE8E31c5959D9F5BBd8122a58682788eeaD"
}
}) {
nodes {
type # Error: Didn't use ... on Vote
}
}
}
content_copy
download
Use code with caution.
Graphql
Prevention: This error is a result of not following rule 1. This could also be prevented by consulting the schema first, before creating the query.
Action: Always use inline fragments (... on TypeName) inside the nodes list, and any other location where interface types can be returned, to query the specific fields of the concrete type. Failure to do so is a fatal error.
Rule 2: Explicit Sort and Filter Inputs (Mandatory)
Problem: Queries with sort or filter options often have required input types that must be fully populated.
Solution: You must never assume default sort or filter values. You must always explicitly provide them in the query if you need them. Even if you don't need sorting or filtering, you must provide an empty input object.
Example (Correct):
query GetProposals($input: ProposalsInput!) {
proposals(input: $input) {
nodes {
... on Proposal {
id
metadata {
title
}
status
}
}
pageInfo {
firstCursor
lastCursor
count
}
}
}
content_copy
download
Use code with caution.
Graphql
* **Input:**
content_copy
download
Use code with caution.
input ProposalsInput {
filters: ProposalsFiltersInput
page: PageInput
sort: ProposalsSortInput
}
input ProposalsFiltersInput {
governorId: AccountID
includeArchived: Boolean
isDraft: Boolean
organizationId: IntID
proposer: Address
}
input ProposalsSortInput {
isDescending: Boolean!
sortBy: ProposalsSortBy!
}
enum ProposalsSortBy {
id
}
input PageInput {
afterCursor: String
beforeCursor: String
limit: Int
}
content_copy
download
Use code with caution.
Graphql
* **Query:** (with optional sort, and filters)
content_copy
download
Use code with caution.
query GetProposalsWithSortAndFilter {
proposals(input: {
filters: {
governorId: "eip155:1:0x123abc"
includeArchived: true
},
sort: {
sortBy: id
isDescending: false
},
page: {
limit: 10
}
})
{
nodes {
... on Proposal {
id
metadata {
title
}
status
}
}
pageInfo {
firstCursor
lastCursor
count
}
}
}
content_copy
download
Use code with caution.
Graphql
Example (Incorrect - Avoid):
query GetProposals {
proposals {
nodes {
id
metadata {
title
}
status
}
}
}
content_copy
download
Use code with caution.
Graphql
Action: Always provide a valid input object for queries that require filters or sorts. Use null if no sorting or filtering is needed for a nullable input, but if the filter is required, use an empty object when no filters are required. Failure to do so is a fatal error.
Rule 3: Fragment Usage (Mandatory)
Problem: Repeated field selections in multiple queries make the code less maintainable and are prone to errors.
Solution: You must use fragments to group common field selections and reuse them across multiple queries. Not using fragments is unacceptable.
Example (Correct):
fragment BasicProposalDetails on Proposal {
id
onchainId
metadata {
title
description
}
status
}
query GetProposals($input: ProposalsInput!) {
proposals(input: $input) {
nodes {
... on Proposal {
...BasicProposalDetails
}
}
pageInfo {
firstCursor
lastCursor
count
}
}
}
query GetSingleProposal($input: ProposalInput!) {
proposal(input: $input) {
...BasicProposalDetails
}
}
content_copy
download
Use code with caution.
Graphql
Example (Incorrect - Avoid):
query GetProposals {
proposals {
nodes {
id
onchainId
metadata {
title
description
}
status
}
}
}
query GetSingleProposal {
proposal(input: {id: 123}) {
id
onchainId
metadata {
title
description
}
status
}
}
content_copy
download
Use code with caution.
Graphql
Action: Always create and use fragments, and make them focused, and reusable across multiple queries. Not using fragments is unacceptable.
Rule 4: Explicit Field Selection (Mandatory)
Problem: Assuming the API will return certain fields if they aren't specifically requested.
Solution: You must always request every field you need in your query.
Example (Correct):
query GetOrganization($input: OrganizationInput!) {
organization(input: $input) {
id
name
slug
metadata {
icon
description
socials {
website
}
}
}
}
content_copy
download
Use code with caution.
Graphql
Example (Incorrect - Avoid):
query GetOrganization {
organization { # Incorrect: Assuming all fields are returned by default
name
slug
}
}
content_copy
download
Use code with caution.
Graphql
Action: List out every field you need in the query, and avoid implied or implicit field selections.
Rule 5: Input Type Validation (Mandatory)
Problem: Using the wrong types when providing input values to a query.
Solution: Check that all values passed as inputs to a query match the type declared in the input. Failure to do so is a fatal error.
Example (Correct):
query GetAccount($id: AccountID!) {
account(id: $id) {
id
name
address
ens
picture
}
}
content_copy
download
Use code with caution.
Graphql
Query
query GetAccountCorrect {
account(id:"eip155:1:0x123") {
id
name
address
ens
picture
}
}
content_copy
download
Use code with caution.
Graphql
* The `id` argument correctly uses the `AccountID` type, which is a string representing a CAIP-10 ID.
content_copy
download
Use code with caution.
Specific Error Case: Attempting to use a plain integer for organizationId in proposal queries. This is a fatal error.
query GetProposals {
proposals(input: {
filters: {
organizationId: 1 # Wrong format for ID
}
})
{
nodes {
... on Proposal {
id
}
}
}
}
content_copy
download
Use code with caution.
Graphql
* **Prevention:** This error is caused by not following rule 5, and also the ID type definitions.
content_copy
download
Use code with caution.
Example (Incorrect - Avoid):
query GetAccount($id: AccountID!) {
account(id: $id) {
id
name
address
}
}
content_copy
download
Use code with caution.
Graphql
Query
query GetAccountIncorrect {
account(id:123) { # Incorrect: Using an Int when an AccountID is expected.
id
name
address
ens
picture
}
}
content_copy
download
Use code with caution.
Graphql
Action: Ensure you're using the correct type. Int cannot be used where an IntID, AccountID, HashID or AssetID type is required. Failure to do so is a fatal error.
ID Type Definitions
AccountID: A CAIP-10 compliant account id. (e.g., "eip155:1:0x7e90e03654732abedf89Faf87f05BcD03ACEeFdc")
AssetID: A CAIP-19 compliant asset id. (e.g., "eip155:1/erc20:0x6b175474e89094c44da98b954eedeac495271d0f")
IntID: A 64-bit integer represented as a string. (e.g., "1234567890")
HashID: A CAIP-2 scoped identifier for identifying transactions across chains. (e.g., "eip155:1:0xDEAD")
BlockID: A CAIP-2 scoped identifier for identifying blocks across chains. (e.g., "eip155:1:15672")
ChainID: A CAIP-2 compliant chain ID. (e.g., "eip155:1")
Address: A 20 byte ethereum address, represented as 0x-prefixed hexadecimal. (e.g., "0x1234567800000000000000000000000000000abc")
Rule 6: Enum Usage (Mandatory)
Problem: Using a string value when an enum type is expected.
Solution: Always use the correct values for an enum type. Failure to do so is a fatal error.
Example (Correct)
query GetVotes($input: VotesInput!) {
votes(input: $input) {
nodes {
id
type
}
}
}
content_copy
download
Use code with caution.
Graphql
Input:
input VotesInput {
filters: VotesFiltersInput
page: PageInput
sort: VotesSortInput
}
input VotesFiltersInput {
proposalId: IntID
proposalIds: [IntID!]
voter: Address
includePendingVotes: Boolean
type: VoteType
}
enum VoteType {
abstain
against
for
pendingabstain
pendingagainst
pendingfor
}
content_copy
download
Use code with caution.
Graphql
Query: (Correctly using an enum type)
query GetVotesFor {
votes(input: {
filters: {
type: for
proposalId: 123
}
})
{
nodes {
id
type
}
}
}
content_copy
download
Use code with caution.
Graphql
Example (Incorrect - Avoid):
query GetVotesFor {
votes(input: {
filters: {
type: "for" # Incorrect: using a string, when a VoteType enum is expected
proposalId: 123
}
})
{
nodes {
id
type
}
}
}
content_copy
download
Use code with caution.
Graphql
Action: Always ensure the values of enum types match the provided options, and that you are not using a string when an enum is expected. Failure to do so is a fatal error.
Rule 7: Pagination Handling (Mandatory)
Problem: Queries that return paginated data do not return complete results if pagination is not handled.
Solution: You must always use the page input with appropriate limit, afterCursor and beforeCursor values to ensure you are retrieving all the results that you want. You must also use the pageInfo field on the returned type to use the cursors.
Example (Correct):
query GetPaginatedProposals($input: ProposalsInput!) {
proposals(input: $input) {
nodes {
... on Proposal {
id
metadata {
title
}
}
}
pageInfo {
firstCursor
lastCursor
count
}
}
}
content_copy
download
Use code with caution.
Graphql
* **Input**
content_copy
download
Use code with caution.
input ProposalsInput {
filters: ProposalsFiltersInput
page: PageInput
sort: ProposalsSortInput
}
input ProposalsFiltersInput {
governorId: AccountID
includeArchived: Boolean
isDraft: Boolean
organizationId: IntID
proposer: Address
}
input ProposalsSortInput {
isDescending: Boolean!
sortBy: ProposalsSortBy!
}
enum ProposalsSortBy {
id
}
input PageInput {
afterCursor: String
beforeCursor: String
limit: Int
}
content_copy
download
Use code with caution.
Graphql
Query:
query GetProposalsWithPagination {
proposals(input: {
page: {
limit: 20
}
}) {
nodes {
... on Proposal {
id
metadata {
title
}
}
}
pageInfo {
firstCursor
lastCursor
count
}
}
}
content_copy
download
Use code with caution.
Graphql
Query: (Using cursors to get the next page of results)
query GetProposalsWithPagination {
proposals(input: {
page: {
limit: 20
afterCursor: "cursorFromPreviousQuery"
}
}) {
nodes {
... on Proposal {
id
metadata {
title
}
}
}
pageInfo {
firstCursor
lastCursor
count
}
}
}
content_copy
download
Use code with caution.
Graphql
Example (Incorrect - Avoid):
query GetProposals { # Incorrect: Not using the `page` input.
proposals {
nodes {
... on Proposal {
id
metadata {
title
}
}
}
}
}
content_copy
download
Use code with caution.
Graphql
Action: Always use the page input with a limit, and use the cursors to iterate through pages, especially when you are working with paginated data. Failure to do so may result in incomplete data.
Rule 8: Correctly Querying Related Data (Mandatory)
Problem: Attempting to query related data as nested fields within a type will lead to errors if the related data must be fetched in a separate query.
Solution: You must fetch related data by using separate queries, instead of assuming that related data is queryable as nested fields.
Example (Correct)
query GetProposalAndVotes($proposalId: IntID!, $voter: Address) {
proposal(input: { id: $proposalId}) {
id
metadata {
title
}
status
}
votes(input: {
filters: {
proposalId: $proposalId
voter: $voter
}
}) {
nodes {
... on Vote {
type
amount
voter {
id
name
}
}
}
}
}
content_copy
download
Use code with caution.
Graphql
Example (Incorrect - Avoid):
query GetProposals {
proposals {
... on Proposal {
id
metadata {
title
}
votes(input: {
filters: {
voter: "0x..." # Incorrect: Trying to access votes as a nested field
}
})
}
}
}
content_copy
download
Use code with caution.
Graphql
* **Prevention:** This can be prevented by reading rule 8, and by consulting the schema before creating a query.
content_copy
download
Use code with caution.
Action: Do not attempt to fetch related data in the same query, instead, fetch it via a second query. Failure to do so will result in an error.
Rule 9: API Constraints (Mandatory)
Problem: Not all fields or properties are queryable in all situations. Some queries have explicit requirements that must be met.
Solution: You must always check your query against the known API constraints, and ensure that all requirements are met.
Example:
The votes query requires that proposalId or proposalIds is provided in the input. This means you cannot query votes without first querying proposals. Failure to do so will result in an error.
An error you may see is: "proposalId or proposalIds must be provided"
Prevention: This can be prevented by reading rule 9, and by consulting the schema before creating a query.
Action: Ensure all API constraits are met and that any required fields are provided when making a query. Failure to do so will result in an error.
Rule 10: Multi-Step Queries (Mandatory)
Problem: Some data can only be accessed by using multiple queries, and requires that data from one query be used as the input for a subsequent query.
Solution: Properly construct multi-step queries by breaking them into a sequence of independent GraphQL queries. Ensure the output of one query is correctly used as input for the next query.
Example
If you need to fetch all the votes from a specific organization, you first need to get the organization id, then use that id to query all the proposals, and then finally, you need to query for all the votes associated with each proposal.
Correct Example
# Step 1: Get the organization ID using a query that filters by slug
query GetOrganizationId($slug: String!) {
organization(input: {slug: $slug}) {
id
}
}
# Step 2: Get the proposals for the given organization
query GetProposalsForOrganization($organizationId: IntID!) {
proposals(input: {
filters: {
organizationId: $organizationId
}
}) {
nodes {
... on Proposal {
id
}
}
}
}
# Step 3: Get all the votes for all of the proposals.
query GetVotesForProposals($proposalIds: [IntID!]!) {
votes(input: {
filters: {
proposalIds: $proposalIds
}
})
{
nodes {
... on Vote {
id
type
amount
}
}
}
}
content_copy
download
Use code with caution.
Graphql
* **Action:** When a query requires data from another query, structure it as a multi-step query, and use the result of the first query as the input to the subsequent query.
content_copy
download
Use code with caution.
Rule 11: Fragment Usage (Mandatory)
Problem: Defining fragments that aren't used creates unnecessary code.
Solution: You must always use all defined fragments, and any unused fragments must be removed before submitting a query.
Example
fragment BasicAccountInfo on Account {
id
address
ens
}
fragment VoteDetails on Vote {
type
amount
}
query GetVotes($input: VotesInput!) {
votes(input: $input) {
nodes {
... on Vote {
...VoteDetails # Correct: Using the VoteDetails fragment
}
}
}
}
content_copy
download
Use code with caution.
Graphql
* **Prevention:** This can be prevented by following rule 3.
* **Action:** All defined fragments *must* be used, and any unused fragments *must* be removed before submitting a query.
content_copy
download
Use code with caution.
Complete Schema Reference
While we cannot provide the entire schema (it would be too lengthy), here are the core types and their most commonly used fields, and examples of the input types:
type Account {
id: ID!
address: String!
ens: String
twitter: String
name: String!
bio: String!
picture: String
safes: [AccountID!]
type: AccountType!
votes(governorId: AccountID!): Uint256!
proposalsCreatedCount(input: ProposalsCreatedCountInput!): Int!
}
enum AccountType {
EOA
SAFE
}
type Delegate {
id: IntID!
account: Account!
chainId: ChainID
delegatorsCount: Int!
governor: Governor
organization: Organization
statement: DelegateStatement
token: Token
votesCount(blockNumber: Int): Uint256!
}
input DelegateInput {
address: Address!
governorId: AccountID
organizationId: IntID
}
type DelegateStatement {
id: IntID!
address: Address!
organizationID: IntID!
statement: String!
statementSummary: String
isSeekingDelegation: Boolean
issues: [Issue!]
}
type Delegation {
id: IntID!
blockNumber: Int!
blockTimestamp: Timestamp!
chainId: ChainID!
delegator: Account!
delegate: Account!
organization: Organization!
token: Token!
votes: Uint256!
}
input DelegationInput {
address: Address!
tokenId: AssetID!
}
input DelegationsInput {
filters: DelegationsFiltersInput!
page: PageInput
sort: DelegationsSortInput
}
input DelegationsFiltersInput {
address: Address!
governorId: AccountID
organizationId: IntID
}
input DelegationsSortInput {
isDescending: Boolean!
sortBy: DelegationsSortBy!
}
enum DelegationsSortBy {
id
votes
}
type Governor {
id: AccountID!
chainId: ChainID!
contracts: Contracts!
isIndexing: Boolean!
isBehind: Boolean!
isPrimary: Boolean!
kind: GovernorKind!
name: String!
organization: Organization!
proposalStats: ProposalStats!
parameters: GovernorParameters!
quorum: Uint256!
slug: String!
timelockId: AccountID
tokenId: AssetID!
token: Token!
type: GovernorType!
delegatesCount: Int!
delegatesVotesCount: Uint256!
tokenOwnersCount: Int!
metadata: GovernorMetadata
}
type GovernorContract {
address: Address!
type: GovernorType!
}
input GovernorInput {
id: AccountID
slug: String
}
type Organization {
id: IntID!
slug: String!
name: String!
chainIds: [ChainID!]!
tokenIds: [AssetID!]!
governorIds: [AccountID!]!
metadata: OrganizationMetadata
creator: Account
hasActiveProposals: Boolean!
proposalsCount: Int!
delegatesCount: Int!
delegatesVotesCount: Uint256!
tokenOwnersCount: Int!
endorsementService: EndorsementService
}
input OrganizationInput {
id: IntID
slug: String
}
input OrganizationsInput {
filters: OrganizationsFiltersInput
page: PageInput
sort: OrganizationsSortInput
}
input OrganizationsFiltersInput {
address: Address
chainId: ChainID
hasLogo: Boolean
isMember: Boolean
}
input OrganizationsSortInput {
isDescending: Boolean!
sortBy: OrganizationsSortBy!
}
enum OrganizationsSortBy {
id
name
explore
popular
}
type Proposal {
id: IntID!
onchainId: String
block: Block
chainId: ChainID!
creator: Account
end: BlockOrTimestamp!
events: [ProposalEvent!]!
executableCalls: [ExecutableCall!]
governor: Governor!
metadata: ProposalMetadata!
organization: Organization!
proposer: Account
quorum: Uint256
status: ProposalStatus!
start: BlockOrTimestamp!
voteStats: [VoteStats!]
}
input ProposalInput {
id: IntID
onchainId: String
governorId: AccountID
includeArchived: Boolean
isLatest: Boolean
}
type ProposalMetadata {
title: String
description: String
eta: Int
ipfsHash: String
previousEnd: Int
timelockId: AccountID
txHash: Hash
discourseURL: String
snapshotURL: String
}
input ProposalsInput {
filters: ProposalsFiltersInput
page: PageInput
sort: ProposalsSortInput
}
input ProposalsFiltersInput {
governorId: AccountID
includeArchived: Boolean
isDraft: Boolean
organizationId: IntID
proposer: Address
}
input ProposalsSortInput {
isDescending: Boolean!
sortBy: ProposalsSortBy!
}
enum ProposalsSortBy {
id
}
type Token {
id: AssetID!
type: TokenType!
name: String!
symbol: String!
supply: Uint256!
decimals: Int!
eligibility: Eligibility
isIndexing: Boolean!
isBehind: Boolean!
}
type Vote {
id: IntID!
amount: Uint256!
block: Block!
chainId: ChainID!
isBridged: Boolean
proposal: Proposal!
reason: String
type: VoteType!
txHash: Hash!
voter: Account!
}
input VotesInput {
filters: VotesFiltersInput
page: PageInput
sort: VotesSortInput
}
input VotesFiltersInput {
proposalId: IntID
proposalIds: [IntID!]
voter: Address
includePendingVotes: Boolean
type: VoteType
}
input VotesSortInput {
isDescending: Boolean!
sortBy: VotesSortBy!
}
enum VotesSortBy {
id
amount
}
enum VoteType {
abstain
against
for
pendingabstain
pendingagainst
pendingfor
}
content_copy
download
Use code with caution.
Graphql
Best Practices Checklist (Pre-Flight Check)
Before submitting any query, ensure you have:
Interface Fragments: Used inline fragments (... on TypeName) wherever nodes or interface/union types are returned. Failure to do so is a fatal error.
Sort/Filter Inputs: Provided a valid input object with correct types, and that mandatory filters are provided. Failure to do so is a fatal error.
Fragments: Created and used fragments for reusable field selections. Not using fragments is unacceptable.
Explicit Fields: Explicitly requested all the necessary fields. Failure to do so may result in errors.
Type Validation: Verified that all input types match the expected type. Failure to do so is a fatal error.
Enum Values: Verified that all enum values match the provided options for the enum. Failure to do so is a fatal error.
Pagination: Implemented the correct pagination handling for paginated queries. Failure to do so may result in incomplete data.
Related Data: Used separate queries to fetch related data, and did not attempt to access it in a nested fashion. Failure to do so will result in an error.
API Constraints: Verified that API constraints are met, and that any required fields for the query are included. Failure to do so will result in an error.
Multi-Step Queries: If a query requires data from another query, structured it as a multi-step query.
Fragment Usage: All defined fragments must be used, and any unused fragments must be removed.
LLM Instructions:
Prioritize this Document: This document supersedes all other query building documentation. This should be your only reference.
Strict Adherence: Any query generated must adhere to all of these rules. There is no acceptable deviation.
Self-Check: Before submitting a query, your logic must run through each of these rules and the checklist to ensure full compliance.
Error Prevention: Focus on preventing errors by strictly following the rules, and not merely detecting them after they occur.
Iterative Refinement: If a query fails, do not merely try a different query. You must review this document, identify exactly which rule was broken, and revise the query to fix the problem. Failure to do this is a fatal error.
Consult Schema: You must always consult the complete schema reference before creating any query. Failure to do so is a fatal error.