ClawSkills logoClawSkills

Tally

Tally API integration with managed OAuth. Manage forms, submissions, workspaces, and webhooks. Use this skill when users want to create or manage Tally forms, r

Introduction

# Tally

Access the Tally API with managed OAuth authentication. Manage forms, submissions, workspaces, and webhooks for your Tally account.

## Quick Start

```bash # List your forms python <<'EOF' import urllib.request, os, json req = urllib.request.Request('https://gateway.maton.ai/tally/forms') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') req.add_header('User-Agent', 'Maton/1.0') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF ```

## Base URL

``` https://gateway.maton.ai/tally/{native-api-path} ```

Replace `{native-api-path}` with the actual Tally API endpoint path. The gateway proxies requests to `api.tally.so` and automatically injects your OAuth token.

## Authentication

All requests require the Maton API key in the Authorization header and the User Agent header:

``` Authorization: Bearer $MATON_API_KEY User-Agent: Maton/1.0 ```

**Environment Variable:** Set your API key as `MATON_API_KEY`:

```bash export MATON_API_KEY="YOUR_API_KEY" ```

### Getting Your API Key

1. Sign in or create an account at [maton.ai](https://maton.ai) 2. Go to [maton.ai/settings](https://maton.ai/settings) 3. Copy your API key

## Connection Management

Manage your Tally OAuth connections at `https://ctrl.maton.ai`.

### List Connections

```bash python <<'EOF' import urllib.request, os, json req = urllib.request.Request('https://ctrl.maton.ai/connections?app=tally&status=ACTIVE') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF ```

### Create Connection

```bash python <<'EOF' import urllib.request, os, json data = json.dumps({'app': 'tally'}).encode() req = urllib.request.Request('https://ctrl.maton.ai/connections', data=data, method='POST') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') req.add_header('Content-Type', 'application/json') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF ```

### Get Connection

```bash python <<'EOF' import urllib.request, os, json req = urllib.request.Request('https://ctrl.maton.ai/connections/{connection_id}') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF ```

**Response:** ```json { "connection": { "connection_id": "cd54e2b0-f1d0-435e-a97d-f2d6a5c474bf", "status": "ACTIVE", "creation_time": "2026-02-07T21:00:31.222600Z", "last_updated_time": "2026-02-07T21:00:37.821240Z", "url": "https://connect.maton.ai/?session_token=...", "app": "tally", "metadata": {} } } ```

Open the returned `url` in a browser to complete OAuth authorization.

### Delete Connection

```bash python <<'EOF' import urllib.request, os, json req = urllib.request.Request('https://ctrl.maton.ai/connections/{connection_id}', method='DELETE') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF ```

### Specifying Connection

If you have multiple Tally connections, specify which one to use with the `Maton-Connection` header:

```bash python <<'EOF' import urllib.request, os, json req = urllib.request.Request('https://gateway.maton.ai/tally/forms') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') req.add_header('Maton-Connection', 'cd54e2b0-f1d0-435e-a97d-f2d6a5c474bf') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF ```

If omitted, the gateway uses the default (oldest) active connection.

## API Reference

### User

#### Get Current User

```bash GET /tally/users/me ```

**Response:** ```json { "id": "w2lBkb", "firstName": "John", "lastName": "Doe", "email": "[email protected]", "organizationId": "n0Ze8Q", "subscriptionPlan": "FREE", "createdAt": "2026-02-07T20:58:54.000Z", "updatedAt": "2026-02-07T22:50:35.000Z" } ```

### Forms

#### List Forms

```bash GET /tally/forms ```

**Query Parameters:** - `page` - Page number (default: 1) - `limit` - Items per page (default: 50)

**Response:** ```json { "items": [ { "id": "GxdRaQ", "name": "Contact Form", "workspaceId": "3jW9Q1", "organizationId": "n0Ze8Q", "status": "PUBLISHED", "hasDraftBlocks": false, "numberOfSubmissions": 42, "createdAt": "2026-02-09T08:36:00.000Z", "updatedAt": "2026-02-09T08:36:17.000Z", "isClosed": false } ], "page": 1, "limit": 50, "total": 1, "hasMore": false } ```

#### Get Form

```bash GET /tally/forms/{formId} ```

**Response:** ```json { "id": "GxdRaQ", "name": "Contact Form", "workspaceId": "3jW9Q1", "status": "PUBLISHED", "blocks": [ { "uuid": "11111111-1111-1111-1111-111111111111", "type": "FORM_TITLE", "groupUuid": "22222222-2222-2222-2222-222222222222", "groupType": "FORM_TITLE", "payload": {} }, { "uuid": "33333333-3333-3333-3333-333333333333", "type": "INPUT_TEXT", "groupUuid": "44444444-4444-4444-4444-444444444444", "groupType": "INPUT_TEXT", "payload": {} } ], "settings": null } ```

#### Create Form

```bash POST /tally/forms Content-Type: application/json

{ "status": "DRAFT", "workspaceId": "3jW9Q1", "blocks": [ { "type": "FORM_TITLE", "uuid": "11111111-1111-1111-1111-111111111111", "groupUuid": "22222222-2222-2222-2222-222222222222", "groupType": "FORM_TITLE", "title": "My Form", "payload": {} }, { "type": "INPUT_TEXT", "uuid": "33333333-3333-3333-3333-333333333333", "groupUuid": "44444444-4444-4444-4444-444444444444", "groupType": "INPUT_TEXT", "title": "Your name", "payload": {} } ] } ```

**Block Types:** - `FORM_TITLE` - Form title block - `INPUT_TEXT` - Single-line text input - `INPUT_EMAIL` - Email input - `INPUT_NUMBER` - Number input - `INPUT_PHONE_NUMBER` - Phone number input - `INPUT_DATE` - Date picker - `INPUT_TIME` - Time picker - `INPUT_LINK` - URL input - `TEXTAREA` - Multi-line text input - `MULTIPLE_CHOICE` - Radio buttons - `CHECKBOXES` - Checkbox group - `DROPDOWN` - Dropdown select - `LINEAR_SCALE` - Scale rating - `RATING` - Star rating - `FILE_UPLOAD` - File upload - `SIGNATURE` - Signature field - `PAYMENT` - Payment field - `HIDDEN_FIELDS` - Hidden fields

**Note:** Block `uuid` and `groupUuid` must be valid UUIDs (GUIDs).

#### Update Form

```bash PATCH /tally/forms/{formId} Content-Type: application/json

{ "name": "Updated Form Name", "status": "PUBLISHED" } ```

**Status Values:** - `DRAFT` - Form is a draft - `PUBLISHED` - Form is live

#### Delete Form

```bash DELETE /tally/forms/{formId} ```

Moves the form to trash.

### Form Questions

#### List Questions

```bash GET /tally/forms/{formId}/questions ```

**Response:** ```json { "questions": [ { "uuid": "33333333-3333-3333-3333-333333333333", "type": "INPUT_TEXT", "title": "Your name" } ], "hasResponses": true } ```

### Form Submissions

#### List Submissions

```bash GET /tally/forms/{formId}/submissions ```

**Query Parameters:** - `page` - Page number (default: 1) - `limit` - Items per page (default: 50) - `startDate` - Filter by start date (ISO 8601) - `endDate` - Filter by end date (ISO 8601) - `afterId` - Get submissions after this ID (cursor pagination)

**Response:** ```json { "page": 1, "limit": 50, "hasMore": false, "totalNumberOfSubmissionsPerFilter": { "all": 42, "completed": 40, "partial": 2 }, "questions": [ { "uuid": "33333333-3333-3333-3333-333333333333", "type": "INPUT_TEXT", "title": "Your name" } ], "submissions": [ { "id": "sub123", "respondentId": "resp456", "formId": "GxdRaQ", "createdAt": "2026-02-09T10:00:00.000Z", "isCompleted": true, "responses": [ { "questionId": "33333333-3333-3333-3333-333333333333", "value": "John Doe" } ] } ] } ```

#### Get Submission

```bash GET /tally/forms/{formId}/submissions/{submissionId} ```

#### Delete Submission

```bash DELETE /tally/forms/{formId}/submissions/{submissionId} ```

### Workspaces

#### List Workspaces

```bash GET /tally/workspaces ```

**Response:** ```json { "items": [ { "id": "3jW9Q1", "name": "My Workspace", "createdByUserId": "w2lBkb", "createdAt": "2026-02-09T08:35:53.000Z", "updatedAt": "2026-02-09T08:35:53.000Z" } ], "page": 1, "limit": 50, "total": 1, "hasMore": false } ```

#### Get Workspace

```bash GET /tally/workspaces/{workspaceId} ```

**Response:** ```json { "id": "3jW9Q1", "name": "My Workspace", "createdByUserId": "w2lBkb", "createdAt": "2026-02-09T08:35:53.000Z", "members": [ { "id": "w2lBkb", "firstName": "John", "lastName": "Doe", "email": "[email protected]" } ] } ```

#### Create Workspace

```bash POST /tally/workspaces Content-Type: application/json

{ "name": "New Workspace" } ```

**Note:** Creating workspaces requires a Pro subscription.

#### Update Workspace

```bash PATCH /tally/workspaces/{workspaceId} Content-Type: application/json

{ "name": "Updated Workspace Name" } ```

#### Delete Workspace

```bash DELETE /tally/workspaces/{workspaceId} ```

Moves the workspace and all its forms to trash.

### Organization Users

#### List Users

```bash GET /tally/organizations/{organizationId}/users ```

**Response:** ```json [ { "id": "w2lBkb", "firstName": "John", "lastName": "Doe", "email": "[email protected]", "createdAt": "2026-02-07T20:58:54.000Z" } ] ```

#### Remove User

```bash DELETE /tally/organizations/{organizationId}/users/{userId} ```

### Organization Invites

#### List Invites

```bash GET /tally/organizations/{organizationId}/invites ```

#### Create Invite

```bash POST /tally/organizations/{organizationId}/invites Content-Type: application/json

{ "email": "[email protected]", "workspaceIds": ["3jW9Q1"] } ```

#### Cancel Invite

```bash DELETE /tally/organizations/{organizationId}/invites/{inviteId} ```

### Webhooks

#### List Webhooks

```bash GET /tally/webhooks ```

**Note:** Listing webhooks may require specific permissions.

#### Create Webhook

```bash POST /tally/webhooks Content-Type: application/json

{ "formId": "GxdRaQ", "url": "https://your-endpoint.com/webhook", "eventTypes": ["FORM_RESPONSE"] } ```

**Webhook Event Types:** - `FORM_RESPONSE` - Triggered when a new form response is submitted

#### Update Webhook

```bash PATCH /tally/webhooks/{webhookId} Content-Type: application/json

{ "url": "https://new-endpoint.com/webhook" } ```

#### Delete Webhook

```bash DELETE /tally/webhooks/{webhookId} ```

#### List Webhook Events

```bash GET /tally/webhooks/{webhookId}/events ```

#### Retry Webhook Event

```bash POST /tally/webhooks/{webhookId}/events/{eventId} ```

## Pagination

Tally uses page-based pagination:

```bash GET /tally/forms?page=1&limit=50 ```

Response includes pagination info:

```json { "items": [...], "page": 1, "limit": 50, "total": 100, "hasMore": true } ```

For submissions, cursor-based pagination is also available using `afterId`.

## Code Examples

### JavaScript

```javascript const response = await fetch( 'https://gateway.maton.ai/tally/forms', { headers: { 'Authorization': `Bearer ${process.env.MATON_API_KEY}`, 'User-Agent': 'Maton/1.0' } } ); const data = await response.json(); console.log(data.items); ```

### Python

```python import os import requests

response = requests.get( 'https://gateway.maton.ai/tally/forms', headers={ 'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}', 'User-Agent': 'Maton/1.0' } ) data = response.json() print(data['items']) ```

### Create Form and Get Submissions

```python import os import requests import uuid

headers = { 'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}', 'User-Agent': 'Maton/1.0' }

# Create a simple form form_data = { 'status': 'DRAFT', 'blocks': [ { 'type': 'FORM_TITLE', 'uuid': str(uuid.uuid4()), 'groupUuid': str(uuid.uuid4()), 'groupType': 'FORM_TITLE', 'title': 'Contact Form', 'payload': {} }, { 'type': 'INPUT_EMAIL', 'uuid': str(uuid.uuid4()), 'groupUuid': str(uuid.uuid4()), 'groupType': 'INPUT_EMAIL', 'title': 'Your email', 'payload': {} } ] }

response = requests.post( 'https://gateway.maton.ai/tally/forms', headers=headers, json=form_data ) form = response.json() print(f"Created form: {form['id']}")

# Get submissions for a form response = requests.get( f'https://gateway.maton.ai/tally/forms/{form["id"]}/submissions', headers=headers ) submissions = response.json() print(f"Total submissions: {submissions['totalNumberOfSubmissionsPerFilter']['all']}") ```

## Notes

- Form and workspace IDs are short alphanumeric strings (e.g., `GxdRaQ`) - Block `uuid` and `groupUuid` fields must be valid UUIDs (GUIDs) - Creating workspaces requires a Pro subscription - The API is in public beta and subject to changes - Rate limit: 100 requests per minute - Use webhooks instead of polling for real-time submission notifications - IMPORTANT: When piping curl output to `jq` or other commands, environment variables like `$MATON_API_KEY` may not expand correctly in some shell environments

## Error Handling

| Status | Meaning | |--------|---------| | 400 | Missing Tally connection or validation error | | 401 | Invalid or missing Maton API key | | 403 | Insufficient permissions | | 404 | Resource not found | | 429 | Rate limited (100 req/min) | | 4xx/5xx | Passthrough error from Tally API |

### Troubleshooting: API Key Issues

1. Check that the `MATON_API_KEY` environment variable is set:

```bash echo $MATON_API_KEY ```

2. Verify the API key is valid by listing connections:

```bash python <<'EOF' import urllib.request, os, json req = urllib.request.Request('https://ctrl.maton.ai/connections') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF ```

### Troubleshooting: Invalid App Name

1. Ensure your URL path starts with `tally`. For example:

- Correct: `https://gateway.maton.ai/tally/forms` - Incorrect: `https://gateway.maton.ai/forms`

## Resources

- [Tally API Introduction](https://developers.tally.so/api-reference/introduction) - [Tally API Reference](https://developers.tally.so/llms.txt) - [Tally Help Center](https://help.tally.so/) - [Maton Community](https://discord.com/invite/dBfFAcefs2) - [Maton Support](mailto:[email protected])

More Products