Stood RESTfull API v1

Generic REST API Documentation

Overview

The Generic REST API provides a unified, resource-based interface for managing all Stood resources. It follows REST best practices with standard HTTP methods and resource-based URLs.

Authentication

All API requests require authentication using your team's webFormKey. API keys must be passed in headers for security.

Required Headers:

Example:

X-Team-ID: abc123
Authorization: Bearer webform_xyz789

Security Note: API keys in headers are more secure than in URLs (which can be logged, cached, or exposed in browser history).

Getting Your API Key

  1. Log in to your Stood admin console

  2. Navigate to Team Settings

  3. Find your webFormKey (also called "Web Form API Key")

  4. Use this key in all API requests

Base URL

https://[region]-[project-id].cloudfunctions.net/api/v1

Replace [region] with your Firebase Functions region (e.g., us-central1) and [project-id] with your Firebase project ID.

Resources

Top-Level Resources

Nested Resources

API Endpoints

Top-Level Resources

List Resources

GET /api/v1/{resource}?[filters]&limit=100&offset=0
Headers:
X-Team-ID: {teamId}
Authorization: Bearer {webFormKey}

Headers (Required):

Query Parameters:

Examples:

List deals with stage filter:

GET /api/v1/deals?stage=s0&limit=50
Headers:
X-Team-ID: abc123
Authorization: Bearer webform_xyz

Search accounts by name:

GET /api/v1/accounts?search=acme&limit=20
Headers:
X-Team-ID: abc123
Authorization: Bearer webform_xyz

Search contacts by name with pagination:

GET /api/v1/contacts?q=john&limit=50&offset=0
Headers:
X-Team-ID: abc123
Authorization: Bearer webform_xyz

Response:

{
"success": true,
"data": [
{
"id": "deal-id",
"name": "Deal Name",
"stage": "s0",
"team": "team-id",
...
}
],
"count": 10,
"total": 10
}

Get Resource

GET /api/v1/{resource}/{id}
Headers:
X-Team-ID: {teamId}
Authorization: Bearer {webFormKey}

Example:

GET /api/v1/deals/deal123
Headers:
X-Team-ID: abc123
Authorization: Bearer webform_xyz

Response:

{
"success": true,
"data": {
"id": "deal-id",
"name": "Deal Name",
...
}
}

Create Resource

POST /api/v1/{resource}
Headers:
X-Team-ID: {teamId}
Authorization: Bearer {webFormKey}
Content-Type: application/json
{
"name": "...",
...
}

Example:

POST /api/v1/accounts
Headers:
X-Team-ID: abc123
Authorization: Bearer webform_xyz
Content-Type: application/json
Body:
{
"name": "Acme Corp",
"location": "New York",
"website": "https://acme.com"
}

Response:

{
"success": true,
"data": {
"id": "account-id",
"name": "Acme Corp",
"createdAt": "2024-01-15T10:30:00.000Z",
...
}
}

Update Resource

PUT /api/v1/{resource}/{id}
Headers:
X-Team-ID: {teamId}
Authorization: Bearer {webFormKey}
Content-Type: application/json
{
"name": "Updated Name",
...
}

Note: Only include fields you want to update. The team and id fields cannot be updated.

Example:

PUT /api/v1/deals/deal123
Headers:
X-Team-ID: abc123
Authorization: Bearer webform_xyz
Content-Type: application/json
Body:
{
"name": "Updated Deal Name",
"stage": "s1"
}

Response:

{
"success": true,
"data": {
"id": "deal-id",
"name": "Updated Deal Name",
"stage": "s1",
"updatedAt": "2024-01-15T11:00:00.000Z",
...
}
}

Delete Resource

DELETE /api/v1/{resource}/{id}
Headers:
X-Team-ID: {teamId}
Authorization: Bearer {webFormKey}

Example:

DELETE /api/v1/accounts/account123
Headers:
X-Team-ID: abc123
Authorization: Bearer webform_xyz

Response:

{
"success": true,
"message": "Resource deleted successfully"
}

Nested Resources

Nested resources are accessed via their parent resource path.

List Nested Resources

GET /api/v1/{parent}/{parentId}/{nested}?[filters]
Headers:
X-Team-ID: {teamId}
Authorization: Bearer {webFormKey}

Example - List folders for a deal:

GET /api/v1/deals/deal123/folders
Headers:
X-Team-ID: abc123
Authorization: Bearer webform_xyz

Response:

{
"success": true,
"data": [
{
"id": "folder-id",
"dealId": "deal123",
"name": "Dropbox",
"url": "https://dropbox.com/folder",
"preview": "# Summary...",
"createdAt": "2024-01-15T10:30:00.000Z"
}
],
"count": 1
}

Get Nested Resource

GET /api/v1/{parent}/{parentId}/{nested}/{nestedId}
Headers:
X-Team-ID: {teamId}
Authorization: Bearer {webFormKey}

Example:

GET /api/v1/deals/deal123/folders/folder456
Headers:
X-Team-ID: abc123
Authorization: Bearer webform_xyz

Create Nested Resource

POST /api/v1/{parent}/{parentId}/{nested}
Headers:
X-Team-ID: {teamId}
Authorization: Bearer {webFormKey}
Content-Type: application/json
{
"name": "...",
...
}

Example - Create folder:

POST /api/v1/deals/deal123/folders
Headers:
X-Team-ID: abc123
Authorization: Bearer webform_xyz
Content-Type: application/json
Body:
{
"name": "Dropbox",
"url": "https://dropbox.com/folder",
"preview": "# Summary\n\nImportant documents."
}

Update Nested Resource

PUT /api/v1/{parent}/{parentId}/{nested}/{nestedId}
Headers:
X-Team-ID: {teamId}
Authorization: Bearer {webFormKey}
Content-Type: application/json
{
"name": "Updated Name",
...
}

Delete Nested Resource

DELETE /api/v1/{parent}/{parentId}/{nested}/{nestedId}
Headers:
X-Team-ID: {teamId}
Authorization: Bearer {webFormKey}

Direct Nested Resource Access

For nested resources like folders, you can access them directly by ID without knowing the parent deal ID. The API will search for the resource across all deals in your team.

Note: This is less efficient than using the full path, but useful when you only have the nested resource ID.

GET /api/v1/folders/{folderId}
Headers:
X-Team-ID: {teamId}
Authorization: Bearer {webFormKey}

Example:

GET /api/v1/folders/folder456
Headers:
X-Team-ID: abc123
Authorization: Bearer webform_xyz

Response:

{
"success": true,
"data": {
"id": "folder-id",
"dealId": "deal123",
"name": "Dropbox",
...
},
"parentId": "deal123",
"parentCollection": "deals"
}

Resource-Specific Details

Accounts

Required fields (create):

Optional fields:

Contacts

Required fields (create):

Optional fields:

Deals

Required fields (create):

Optional fields:

Posts

Required fields (create):

Optional fields:

Activities

Required fields (create):

Optional fields:

Folders (Nested under Deals)

Required fields (create):

Optional fields:

Custom Sub-Collections

Custom sub-collections are defined in datamodel.json under deal.subCollections. They can be accessed the same way as folders:

GET /api/v1/deals/{dealId}/{subCollectionName}
POST /api/v1/deals/{dealId}/{subCollectionName}
GET /api/v1/deals/{dealId}/{subCollectionName}/{itemId}
PUT /api/v1/deals/{dealId}/{subCollectionName}/{itemId}
DELETE /api/v1/deals/{dealId}/{subCollectionName}/{itemId}

Error Responses

All errors follow this format:

{
"success": false,
"error": "Error message description"
}

HTTP Status Codes:

Examples

cURL Examples

List all deals:

curl "https://us-central1-your-project.cloudfunctions.net/api/v1/deals" \
-H "X-Team-ID: abc123" \
-H "Authorization: Bearer webform_xyz"

Get a specific deal:

curl "https://us-central1-your-project.cloudfunctions.net/api/v1/deals/deal123" \
-H "X-Team-ID: abc123" \
-H "Authorization: Bearer webform_xyz"

Create an account:

curl -X POST https://us-central1-your-project.cloudfunctions.net/api/v1/accounts \
-H "X-Team-ID: abc123" \
-H "Authorization: Bearer webform_xyz" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Corp",
"location": "New York"
}'

Update a deal:

curl -X PUT https://us-central1-your-project.cloudfunctions.net/api/v1/deals/deal123 \
-H "X-Team-ID: abc123" \
-H "Authorization: Bearer webform_xyz" \
-H "Content-Type: application/json" \
-d '{
"stage": "s1"
}'

List folders for a deal:

curl "https://us-central1-your-project.cloudfunctions.net/api/v1/deals/deal123/folders" \
-H "X-Team-ID: abc123" \
-H "Authorization: Bearer webform_xyz"

Create a folder:

curl -X POST https://us-central1-your-project.cloudfunctions.net/api/v1/deals/deal123/folders \
-H "X-Team-ID: abc123" \
-H "Authorization: Bearer webform_xyz" \
-H "Content-Type: application/json" \
-d '{
"name": "Dropbox",
"url": "https://dropbox.com/folder"
}'

Get folder directly by ID:

curl "https://us-central1-your-project.cloudfunctions.net/api/v1/folders/folder456" \
-H "X-Team-ID: abc123" \
-H "Authorization: Bearer webform_xyz"

JavaScript Example

const baseUrl = 'https://us-central1-your-project.cloudfunctions.net/api/v1';
const teamId = 'abc123';
const teamKey = 'webform_xyz';
// List deals
async function listDeals() {
const response = await fetch(`${baseUrl}/deals`, {
headers: {
'X-Team-ID': teamId,
'Authorization': `Bearer ${teamKey}`
}
});
return await response.json();
}
// Create account
async function createAccount(name) {
const response = await fetch(`${baseUrl}/accounts`, {
method: 'POST',
headers: {
'X-Team-ID': teamId,
'Authorization': `Bearer ${teamKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ name })
});
return await response.json();
}
// Update deal
async function updateDeal(dealId, updates) {
const response = await fetch(`${baseUrl}/deals/${dealId}`, {
method: 'PUT',
headers: {
'X-Team-ID': teamId,
'Authorization': `Bearer ${teamKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(updates)
});
return await response.json();
}
// List folders for deal
async function listFolders(dealId) {
const response = await fetch(`${baseUrl}/deals/${dealId}/folders`, {
headers: {
'X-Team-ID': teamId,
'Authorization': `Bearer ${teamKey}`
}
});
return await response.json();
}

Python Example

import requests
base_url = 'https://us-central1-your-project.cloudfunctions.net/api/v1'
team_id = 'abc123'
team_key = 'webform_xyz'
# List deals
def list_deals():
response = requests.get(
f'{base_url}/deals',
headers={
'X-Team-ID': team_id,
'Authorization': f'Bearer {team_key}'
}
)
return response.json()
# Create account
def create_account(name):
response = requests.post(
f'{base_url}/accounts',
headers={
'X-Team-ID': team_id,
'Authorization': f'Bearer {team_key}',
'Content-Type': 'application/json'
},
json={'name': name}
)
return response.json()
# Update deal
def update_deal(deal_id, updates):
response = requests.put(
f'{base_url}/deals/{deal_id}',
headers={
'X-Team-ID': team_id,
'Authorization': f'Bearer {team_key}',
'Content-Type': 'application/json'
},
json=updates
)
return response.json()

Best Practices

  1. Use appropriate HTTP methods: GET for reading, POST for creating, PUT for updating, DELETE for deleting

  2. Include only changed fields in updates: PUT requests should only include fields you want to update

  3. Use pagination for large lists: Use limit and offset parameters when listing resources

  4. Filter at the API level: Use query parameters to filter results rather than fetching all and filtering client-side

  5. Handle errors gracefully: Check the success field in responses and handle errors appropriately

  6. Store credentials securely: Never expose teamKey in client-side code or public repositories

Rate Limiting

Currently, there are no rate limits on API operations. However, we recommend:

Security Notes

  1. API Key Security: Keep your webFormKey secure. Never expose it in client-side code or public repositories.

  2. Team Isolation: Resources can only be accessed for your team. Cross-team access is not possible.

  3. Input Validation: All inputs are sanitized and validated. Invalid data will return 400 errors.

  4. HTTPS Only: All API calls must use HTTPS.

Support

For issues or questions:

Published with Nuclino