Skip to main content

Workspace Settings

Workspace settings allow you to customize email templates, team contact information, and timezone preferences at the workspace level. These settings apply to all signing requests and templates within the workspace.

Use cases

  • Custom email branding: Personalize email headers and body text for signing request invitations
  • Team contact info: Set a team email for recipient support questions
  • Timezone management: Configure timezone for date/time displays and reminders
  • Multi-tenant applications: Separate settings per workspace for white-label solutions
Rate limits:
  • GET requests: 200 requests per minute
  • PUT requests: 100 requests per minute

Get workspace settings

Retrieve current workspace settings including email templates, team email, and timezone configuration.

Endpoint

GET /workspace/{workspace_id}/settings

Parameters

  • workspace_id (string, required) - UUID of the workspace

Example — cURL

curl -X GET "https://api.firma.dev/functions/v1/signing-request-api/workspace/123e4567-e89b-12d3-a456-426614174000/settings" \
  -H "Authorization: Bearer YOUR_API_KEY"

Response (200 OK)

{
  "workspace_id": "123e4567-e89b-12d3-a456-426614174000",
  "email_header": "You've been invited to sign a document",
  "email_body": "Please review and sign the document at your earliest convenience. If you have any questions, contact our team.",
  "team_email": "support@yourcompany.com",
  "timezone": "America/New_York",
  "updated_at": "2024-03-20T10:30:00Z"
}

Response fields

  • workspace_id (string) - Workspace UUID
  • email_header (string) - Custom header text for signing invitation emails
  • email_body (string) - Custom body text for signing invitation emails
  • team_email (string) - Contact email displayed to recipients
  • timezone (string) - IANA timezone identifier (e.g., “America/New_York”)
  • updated_at (string) - ISO 8601 timestamp of last update

Rate limit headers

X-RateLimit-Limit: 200
X-RateLimit-Remaining: 199
X-RateLimit-Reset: 1729180800

Update workspace settings

Update workspace settings. You can update one or more fields — only provided fields will be updated.

Endpoint

PUT /workspace/{workspace_id}/settings

Parameters

  • workspace_id (string, required) - UUID of the workspace

Request body

All fields are optional — only include fields you want to update:
{
  "email_header": "You've been invited to sign a document",
  "email_body": "Please review and sign the document at your earliest convenience.",
  "team_email": "support@yourcompany.com",
  "timezone": "America/New_York"
}

Field descriptions

  • email_header (string, optional) - Custom header text for signing emails (max 255 characters)
  • email_body (string, optional) - Custom body text for signing emails (max 2000 characters)
  • team_email (string, optional) - Valid email address for recipient support
  • timezone (string, optional) - IANA timezone identifier

Example — cURL

curl -X PUT "https://api.firma.dev/functions/v1/signing-request-api/workspace/123e4567-e89b-12d3-a456-426614174000/settings" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email_header": "Action Required: Sign Your Agreement",
    "email_body": "Hello! We need your signature on an important document. Please click the link below to review and sign. Contact us at support@acmecorp.com if you have questions.",
    "team_email": "support@acmecorp.com",
    "timezone": "America/Los_Angeles"
  }'

Response (200 OK)

Returns the updated workspace settings:
{
  "workspace_id": "123e4567-e89b-12d3-a456-426614174000",
  "email_header": "Action Required: Sign Your Agreement",
  "email_body": "Hello! We need your signature on an important document. Please click the link below to review and sign. Contact us at support@acmecorp.com if you have questions.",
  "team_email": "support@acmecorp.com",
  "timezone": "America/Los_Angeles",
  "updated_at": "2024-03-20T16:45:00Z"
}

Rate limit headers

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 99
X-RateLimit-Reset: 1729180800

Implementation examples

Node.js (Express) — Get settings

import express from 'express'
import fetch from 'node-fetch'

const app = express()
const FIRMA_API_BASE = process.env.FIRMA_API_BASE || 'https://api.firma.dev/functions/v1/signing-request-api'
const API_KEY = process.env.FIRMA_API_KEY

app.get('/api/workspace/:workspaceId/settings', async (req, res) => {
  const { workspaceId } = req.params
  
  try {
    const response = await fetch(
      `${FIRMA_API_BASE}/workspace/${workspaceId}/settings`,
      {
        headers: {
          'Authorization': `Bearer ${API_KEY}`
        }
      }
    )
    
    if (!response.ok) {
      const error = await response.text()
      return res.status(response.status).json({ error })
    }
    
    const settings = await response.json()
    res.json(settings)
  } catch (error) {
    console.error('Failed to get workspace settings:', error)
    res.status(500).json({ error: 'Failed to retrieve settings' })
  }
})

app.listen(3000)

Node.js (Express) — Update settings

app.put('/api/workspace/:workspaceId/settings', express.json(), async (req, res) => {
  const { workspaceId } = req.params
  const { email_header, email_body, team_email, timezone } = req.body
  
  // Validate input
  if (team_email && !isValidEmail(team_email)) {
    return res.status(400).json({ error: 'Invalid email format' })
  }
  
  try {
    const response = await fetch(
      `${FIRMA_API_BASE}/workspace/${workspaceId}/settings`,
      {
        method: 'PUT',
        headers: {
          'Authorization': `Bearer ${API_KEY}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          email_header,
          email_body,
          team_email,
          timezone
        })
      }
    )
    
    if (!response.ok) {
      const error = await response.text()
      return res.status(response.status).json({ error })
    }
    
    const settings = await response.json()
    res.json(settings)
  } catch (error) {
    console.error('Failed to update workspace settings:', error)
    res.status(500).json({ error: 'Failed to update settings' })
  }
})

function isValidEmail(email) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
}

Python (Flask) — Get settings

from flask import Flask, jsonify
import os
import requests

app = Flask(__name__)

FIRMA_API_BASE = os.getenv('FIRMA_API_BASE', 'https://api.firma.dev/functions/v1/signing-request-api')
API_KEY = os.getenv('FIRMA_API_KEY')

@app.route('/api/workspace/<workspace_id>/settings', methods=['GET'])
def get_workspace_settings(workspace_id):
    try:
        response = requests.get(
            f'{FIRMA_API_BASE}/workspace/{workspace_id}/settings',
            headers={
                'Authorization': f'Bearer {API_KEY}'
            }
        )
        response.raise_for_status()
        return jsonify(response.json())
    except requests.exceptions.RequestException as e:
        return jsonify({'error': 'Failed to retrieve settings'}), 500

if __name__ == '__main__':
    app.run(port=3000)

Python (Flask) — Update settings

from flask import request
import re

@app.route('/api/workspace/<workspace_id>/settings', methods=['PUT'])
def update_workspace_settings(workspace_id):
    data = request.json
    
    # Validate email if provided
    if 'team_email' in data:
        if not re.match(r'^[^\s@]+@[^\s@]+\.[^\s@]+$', data['team_email']):
            return jsonify({'error': 'Invalid email format'}), 400
    
    try:
        response = requests.put(
            f'{FIRMA_API_BASE}/workspace/{workspace_id}/settings',
            headers={
                'Authorization': f'Bearer {API_KEY}',
                'Content-Type': 'application/json'
            },
            json={
                'email_header': data.get('email_header'),
                'email_body': data.get('email_body'),
                'team_email': data.get('team_email'),
                'timezone': data.get('timezone')
            }
        )
        response.raise_for_status()
        return jsonify(response.json())
    except requests.exceptions.RequestException as e:
        return jsonify({'error': 'Failed to update settings'}), 500

React — Settings management component

import { useState, useEffect } from 'react'

function WorkspaceSettings({ workspaceId }) {
  const [settings, setSettings] = useState(null)
  const [loading, setLoading] = useState(true)
  const [saving, setSaving] = useState(false)
  const [error, setError] = useState(null)
  
  // Load current settings
  useEffect(() => {
    async function loadSettings() {
      try {
        const response = await fetch(`/api/workspace/${workspaceId}/settings`)
        if (!response.ok) throw new Error('Failed to load settings')
        const data = await response.json()
        setSettings(data)
      } catch (err) {
        setError(err.message)
      } finally {
        setLoading(false)
      }
    }
    loadSettings()
  }, [workspaceId])
  
  // Update settings
  async function handleSave(updatedSettings) {
    setSaving(true)
    setError(null)
    
    try {
      const response = await fetch(`/api/workspace/${workspaceId}/settings`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(updatedSettings)
      })
      
      if (!response.ok) throw new Error('Failed to save settings')
      
      const data = await response.json()
      setSettings(data)
    } catch (err) {
      setError(err.message)
    } finally {
      setSaving(false)
    }
  }
  
  if (loading) return <div>Loading settings...</div>
  if (error) return <div>Error: {error}</div>
  
  return (
    <div>
      <h2>Workspace Settings</h2>
      
      <form onSubmit={(e) => {
        e.preventDefault()
        const formData = new FormData(e.target)
        handleSave({
          email_header: formData.get('email_header'),
          email_body: formData.get('email_body'),
          team_email: formData.get('team_email'),
          timezone: formData.get('timezone')
        })
      }}>
        <div>
          <label>Email Header</label>
          <input
            name="email_header"
            defaultValue={settings.email_header}
            maxLength={255}
          />
        </div>
        
        <div>
          <label>Email Body</label>
          <textarea
            name="email_body"
            defaultValue={settings.email_body}
            maxLength={2000}
            rows={5}
          />
        </div>
        
        <div>
          <label>Team Email</label>
          <input
            name="team_email"
            type="email"
            defaultValue={settings.team_email}
          />
        </div>
        
        <div>
          <label>Timezone</label>
          <select name="timezone" defaultValue={settings.timezone}>
            <option value="America/New_York">Eastern Time</option>
            <option value="America/Chicago">Central Time</option>
            <option value="America/Denver">Mountain Time</option>
            <option value="America/Los_Angeles">Pacific Time</option>
            <option value="Europe/London">London</option>
            <option value="Europe/Paris">Paris</option>
            <option value="Asia/Tokyo">Tokyo</option>
            {/* Add more timezones as needed */}
          </select>
        </div>
        
        <button type="submit" disabled={saving}>
          {saving ? 'Saving...' : 'Save Settings'}
        </button>
      </form>
    </div>
  )
}

Email template customization

Best practices

Email header (max 255 characters):
  • Keep it concise and action-oriented
  • Clearly state the purpose (“Sign your agreement”, “Review document”)
  • Avoid generic text like “You have a notification”
Email body (max 2000 characters):
  • Explain what the recipient needs to do
  • Include support contact information
  • Set expectations (urgency, deadline if applicable)
  • Keep tone professional but friendly

Example templates

Professional services:
Header: "Action Required: Review and Sign Your Contract"
Body: "Thank you for choosing our services. Please review and sign the attached contract at your earliest convenience. If you have any questions or concerns, please contact us at contracts@company.com or call (555) 123-4567."
Real estate:
Header: "Your Property Documents Are Ready to Sign"
Body: "Your documents are ready for electronic signature. Please review carefully before signing. Contact your agent at agent@realty.com if you need clarification on any terms."
HR onboarding:
Header: "Welcome to [Company]! Complete Your Onboarding Documents"
Body: "Welcome to the team! As part of your onboarding, please review and sign the attached documents. If you have questions, reach out to hr@company.com. We're excited to have you aboard!"
Generic/flexible:
Header: "Document Ready for Your Signature"
Body: "A document requires your signature. Please review the contents and sign electronically. Contact us at support@company.com with any questions."

Supported timezones

Workspace settings support all IANA timezone identifiers. Common timezones:

United States

  • America/New_York - Eastern Time
  • America/Chicago - Central Time
  • America/Denver - Mountain Time
  • America/Los_Angeles - Pacific Time
  • America/Anchorage - Alaska Time
  • Pacific/Honolulu - Hawaii Time

Europe

  • Europe/London - GMT/BST
  • Europe/Paris - Central European Time
  • Europe/Berlin - Central European Time
  • Europe/Madrid - Central European Time
  • Europe/Rome - Central European Time

Asia Pacific

  • Asia/Tokyo - Japan Standard Time
  • Asia/Shanghai - China Standard Time
  • Asia/Singapore - Singapore Time
  • Asia/Dubai - Gulf Standard Time
  • Australia/Sydney - Australian Eastern Time

Americas

  • America/Toronto - Eastern Time (Canada)
  • America/Vancouver - Pacific Time (Canada)
  • America/Mexico_City - Central Time (Mexico)
  • America/Sao_Paulo - Brasilia Time
Full list of IANA timezones

Rate limiting

Get workspace settings

  • Limit: 200 requests per minute
  • Use case: Frequent reads for dashboard displays
  • Recommendation: Cache settings client-side for 5-10 minutes

Update workspace settings

  • Limit: 100 requests per minute
  • Use case: Admin configuration changes
  • Recommendation: Debounce updates in UI (wait 1-2 seconds after user stops typing)

Rate limit headers

Every response includes:
X-RateLimit-Limit: 200          # Max requests per minute
X-RateLimit-Remaining: 195       # Remaining in current window
X-RateLimit-Reset: 1729180800    # Unix timestamp of reset

Handling rate limits

If you exceed the limit:
429 Too Many Requests
Best practices:
  • Implement client-side caching
  • Debounce frequent updates
  • Check X-RateLimit-Remaining before making requests
  • Implement exponential backoff for retries

Error responses

400 Bad Request — Validation error

Invalid input data (e.g., malformed email, invalid timezone):
{
  "error": "Validation Error",
  "message": "Invalid email format",
  "field": "team_email"
}

401 Unauthorized

Invalid or missing API key:
{
  "error": "Unauthorized",
  "message": "Invalid API key"
}

404 Not Found

Workspace doesn’t exist or you don’t have access:
{
  "error": "Not Found",
  "message": "Workspace not found"
}

429 Too Many Requests

Rate limit exceeded:
{
  "error": "Rate Limit Exceeded",
  "message": "Too many requests. Please try again later."
}
Check X-RateLimit-Reset header for when you can retry.

Multi-tenant best practices

For multi-tenant applications (multiple workspaces):

1. Cache settings per workspace

const settingsCache = new Map()
const CACHE_TTL = 5 * 60 * 1000 // 5 minutes

async function getWorkspaceSettings(workspaceId) {
  const cached = settingsCache.get(workspaceId)
  if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
    return cached.settings
  }
  
  const settings = await fetchWorkspaceSettings(workspaceId)
  settingsCache.set(workspaceId, {
    settings,
    timestamp: Date.now()
  })
  
  return settings
}

2. Validate workspace access

Always verify the authenticated user has access to the workspace:
async function updateSettings(userId, workspaceId, updates) {
  // Verify user has admin access to workspace
  const hasAccess = await checkWorkspaceAccess(userId, workspaceId, 'admin')
  if (!hasAccess) {
    throw new Error('Forbidden: Insufficient permissions')
  }
  
  // Update settings
  return await updateWorkspaceSettings(workspaceId, updates)
}

3. Audit logging

Log all settings changes for compliance:
async function updateWithAudit(workspaceId, userId, updates) {
  const oldSettings = await getWorkspaceSettings(workspaceId)
  const newSettings = await updateWorkspaceSettings(workspaceId, updates)
  
  // Log the change
  await auditLog.create({
    workspace_id: workspaceId,
    user_id: userId,
    action: 'workspace_settings_updated',
    changes: {
      old: oldSettings,
      new: newSettings
    },
    timestamp: new Date()
  })
  
  return newSettings
}

4. Default settings on workspace creation

Set sensible defaults when creating new workspaces:
async function createWorkspace(name, ownerId) {
  const workspace = await db.workspaces.create({ name, owner_id: ownerId })
  
  // Set default settings
  await updateWorkspaceSettings(workspace.id, {
    email_header: "You've been invited to sign a document",
    email_body: "Please review and sign the document at your earliest convenience.",
    team_email: "support@yourcompany.com",
    timezone: "America/New_York"
  })
  
  return workspace
}

Troubleshooting

Settings not applying to emails

Symptom: Updated settings don’t appear in signing emails Possible causes:
  • Email template cache not cleared
  • Wrong workspace ID used
  • Updates didn’t save (check API response)
Solution:
  • Verify update was successful (check 200 response)
  • Test with a new signing request (not existing draft)
  • Check workspace ID matches signing request

Invalid timezone error

Symptom: 400 error when setting timezone Solution: Use IANA timezone identifiers (e.g., “America/New_York”, not “EST”)

Team email validation failure

Symptom: 400 error on team email update Solution: Ensure valid email format (contains @ and domain)

Rate limit exceeded

Symptom: 429 errors when updating settings Solution:
  • Implement debouncing on form inputs
  • Cache settings client-side
  • Wait for X-RateLimit-Reset before retrying

API reference

For complete details on workspace operations, see:

Workspace Management

Workspace Settings


Next steps

I