Skip to main content
Glama

get_submissions

Retrieve form submissions from Tally MCP by specifying form ID, with options to filter by date, limit results, or skip entries.

Instructions

Retrieve submissions for a specific form

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
formIdYesID of the form
limitNoMaximum number of submissions to return
offsetNoNumber of submissions to skip
sinceNoISO date string to filter submissions since

Implementation Reference

  • Registers the MCP tool 'get_submissions' with name, description, and input schema specifying formId (required), limit, offset, since parameters.
    name: 'get_submissions', description: 'Retrieve submissions for a specific Tally form', inputSchema: { type: 'object', properties: { formId: { type: 'string', description: 'ID of the form to get submissions for' }, limit: { type: 'number', description: 'Maximum number of submissions to return', minimum: 1, maximum: 100 }, offset: { type: 'number', description: 'Number of submissions to skip for pagination', minimum: 0 }, since: { type: 'string', description: 'Only return submissions created after this ISO 8601 timestamp' } }, required: ['formId'] } },
  • Core handler function that executes the API call to retrieve form submissions from Tally.so API endpoint `/forms/{formId}/submissions` with support for pagination (page, limit) and status filtering. Handles mock mode and response validation.
    public async getSubmissions( formId: string, options: { page?: number; limit?: number; status?: 'all' | 'completed' | 'partial'; } = {} ): Promise<TallySubmissionsResponse> { if (this.isMockEnabled()) { const mockRes = await tallyApiMock.getSubmissions(formId, options); return mockRes.data; } const params = new URLSearchParams(); if (options.page) params.append('page', options.page.toString()); if (options.limit) params.append('limit', options.limit.toString()); if (options.status) params.append('status', options.status); const query = params.toString(); const url = `/forms/${formId}/submissions${query ? `?${query}` : ''}`; const response = await this.get(url); return validateTallyResponse(TallySubmissionsResponseSchema, response.data); }
  • Helper service method wrapping TallyApiClient to fetch form submissions with SubmissionFilters (status, dates, afterId) and pagination.
    public async getFormSubmissions( formId: string, options: SubmissionFilters & { page?: number; limit?: number } = {} ): Promise<TallySubmissionsResponse> { const endpoint = `/forms/${formId}/submissions`; const params: Record<string, any> = { page: options.page, limit: options.limit, filter: options.status, startDate: options.startDate, endDate: options.endDate, afterId: options.afterId, }; return this.apiClient.requestWithValidation( 'GET', endpoint, TallySubmissionsResponseSchema, undefined, params, ); }
  • Validation schema definition for the 'get_submissions' tool input, matching the registration schema.
    name: 'get_submissions', description: 'Retrieve submissions for a specific Tally form', inputSchema: { type: 'object', properties: { formId: { type: 'string', description: 'ID of the form to get submissions for' }, limit: { type: 'number', description: 'Maximum number of submissions to return', minimum: 1, maximum: 100 }, offset: { type: 'number', description: 'Number of submissions to skip for pagination', minimum: 0 }, since: { type: 'string', description: 'Only return submissions created after this ISO 8601 timestamp' } }, required: ['formId'] } },
  • Supporting method in SubmissionAnalysisTool to list submissions by calling SubmissionService.getFormSubmissions.
    public async list(formId: string, filters: SubmissionFilters = {}): Promise<TallySubmission[]> { const response = await this.submissionService.getFormSubmissions(formId, filters); return response.submissions; } public async filterByDateRange(formId: string, startDate: string, endDate: string): Promise<TallySubmission[]> { return this.list(formId, { startDate, endDate }); } public async filterByStatus(formId: string, status: SubmissionStatusFilter): Promise<TallySubmission[]> { return this.list(formId, { status }); } public async formatForAnalysis(formId: string, filters: SubmissionFilters = {}): Promise<FormattedSubmission[]> { const response = await this.submissionService.getFormSubmissions(formId, filters); const questions = response.questions; const submissions = response.submissions; const questionMap = new Map<string, string>(questions.map(q => [q.id, q.title])); return submissions.map(sub => ({ submissionId: sub.id, submittedAt: sub.submittedAt, isCompleted: sub.isCompleted, responses: sub.responses.map(res => ({ questionTitle: questionMap.get(res.questionId) || 'Unknown Question', answer: res.value, })), })); } public async getAverageRating(formId: string, questionTitle: string, filters: SubmissionFilters = {}): Promise<number | null> { const formattedSubmissions = await this.formatForAnalysis(formId, filters); let totalRating = 0; let ratingCount = 0; for (const sub of formattedSubmissions) { for (const res of sub.responses) { if (res.questionTitle === questionTitle && typeof res.answer === 'number') { totalRating += res.answer; ratingCount++; } } } return ratingCount > 0 ? totalRating / ratingCount : null; } public async getResponseDistribution(formId: string, questionTitle: string, filters: SubmissionFilters = {}): Promise<Record<string, number>> { const formattedSubmissions = await this.formatForAnalysis(formId, filters); const distribution: Record<string, number> = {}; for (const sub of formattedSubmissions) { for (const res of sub.responses) { if (res.questionTitle === questionTitle) { const answer = String(res.answer); distribution[answer] = (distribution[answer] || 0) + 1; } } } return distribution; } public async exportToCSV(formId: string, filePath: string, filters: SubmissionFilters = {}): Promise<void> { const formattedSubmissions = await this.formatForAnalysis(formId, filters); if (formattedSubmissions.length === 0) { return; } // Flatten the data for CSV const flattenedData = formattedSubmissions.flatMap(sub => sub.responses.map(res => ({ submissionId: sub.submissionId, submittedAt: sub.submittedAt, isCompleted: sub.isCompleted, questionTitle: res.questionTitle, answer: res.answer })) ); const csv = Papa.unparse(flattenedData); // Ensure the directory exists const dir = path.dirname(filePath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } fs.writeFileSync(filePath, csv); } public async exportToJSON(formId: string, filePath: string, filters: SubmissionFilters = {}): Promise<void> { const formattedSubmissions = await this.formatForAnalysis(formId, filters); // Ensure the directory exists const dir = path.dirname(filePath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } fs.writeFileSync(filePath, JSON.stringify(formattedSubmissions, null, 2)); } public async search(formId: string, query: string, filters: SubmissionFilters = {}): Promise<FormattedSubmission[]> { const formattedSubmissions = await this.formatForAnalysis(formId, filters); const lowerCaseQuery = query.toLowerCase(); return formattedSubmissions.filter(sub => sub.responses.some(res => String(res.answer).toLowerCase().includes(lowerCaseQuery) ) ); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/learnwithcc/tally-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server