REST API

Complete REST API documentation for WorkSkedge. Access all resources programmatically with simple HTTP requests.

REST API Overview

The WorkSkedge REST API provides comprehensive programmatic access to all your construction management data. Use standard HTTP methods to create, read, update, and delete resources across 8 major domains.

Base URL: https://app.workskedge.com/api/v1
Authentication: API Key via X-API-Key header
Format: JSON request and response bodies
Security: See the Authentication tab for security best practices

Quick Start

Get started with your first API request in three simple steps:

1

Get API Key

Generate your API key from WorkSkedge Settings → Apps & Integrations

2

Make Request

Include your key in the X-API-Key header with each request

3

Handle Response

Process JSON responses and handle standard HTTP status codes

Example Request

GET /api/v1/projects
curl -X GET "https://app.workskedge.com/api/v1/projects?limit=10&offset=0" \
  -H "X-API-Key: wsk_your_api_key_here" \
  -H "Content-Type: application/json"
Response
{
  "data": [
    {
      "id": "proj_123",
      "name": "Downtown Office Renovation",
      "status": "active",
      "customer_id": "cust_456",
      "start_date": "2024-01-15",
      "created_at": "2024-01-10T08:00:00Z"
    }
  ],
  "pagination": {
    "limit": 10,
    "offset": 0,
    "total": 42
  }
}

Resource Domains

Access all your WorkSkedge data through 8 comprehensive resource domains:

People Management

Employees, Contacts

  • GET POST PUT DELETE /employees
  • GET POST PUT DELETE /contacts

Work Management

Projects, Work Orders, Schedules

  • GET POST PUT DELETE /projects
  • GET POST PUT DELETE /work-orders
  • GET /schedules

Time & Attendance

Timesheets, Time Off

  • GET POST PUT DELETE /timesheets
  • GET POST PUT DELETE /time-off

Supply Chain

Vendors, Locations, Purchase Orders

  • GET POST PUT DELETE /vendors
  • GET POST PUT DELETE /vendor-locations
  • GET POST PUT /purchase-orders

Business Data

Customers, Locations

  • GET POST PUT DELETE /customers
  • GET POST PUT DELETE /locations

Reporting

Daily Reports, Completion Reports

  • GET POST /daily-reports
  • GET POST /completion-reports

Pagination

All list endpoints support pagination using limit and offset query parameters.

Query Parameters

limit

Number of records to return per page. Default: 50, Maximum: 100

offset

Number of records to skip before returning results. Default: 0

Pagination Response

All paginated responses include a pagination object with metadata:

Pagination Object
{
  "data": [...],
  "pagination": {
    "limit": 50,
    "offset": 0,
    "total": 156
  }
}

Example: Fetching All Pages

JavaScript
async function fetchAllProjects() {
  const allProjects = [];
  let offset = 0;
  const limit = 100;

  while (true) {
    const response = await fetch(
      `https://app.workskedge.com/api/v1/projects?limit=${limit}&offset=${offset}`,
      {
        headers: {
          'X-API-Key': 'wsk_your_api_key_here',
          'Content-Type': 'application/json'
        }
      }
    );

    const result = await response.json();
    allProjects.push(...result.data);

    if (allProjects.length >= result.pagination.total) {
      break;
    }

    offset += limit;
  }

  return allProjects;
}

Error Handling

The API uses standard HTTP status codes and returns detailed error messages in JSON format.

200
OK

Request succeeded

201
Created

Resource created successfully

400
Bad Request

Invalid request parameters or body

401
Unauthorized

Missing or invalid API key

403
Forbidden

API key lacks required permissions

404
Not Found

Resource does not exist

429
Too Many Requests

Rate limit exceeded

500
Internal Server Error

Unexpected server error

Error Response Format

Error Response
{
  "error": {
    "code": "INVALID_REQUEST",
    "message": "The 'start_date' field is required",
    "details": {
      "field": "start_date",
      "reason": "Field is required but was not provided"
    }
  }
}

API Reference

Interactive API reference with live testing capabilities. Explore all endpoints, view request/response schemas, and try API calls directly from your browser.

Loading API reference...

Authentication

The WorkSkedge REST API uses API keys for authentication. Each request must include your API key in the X-API-Key header to authorize access to your company's data.

Important: API keys provide full access to your WorkSkedge data. Keep them secure and never commit them to version control or expose them in client-side code.

Getting Your API Key

Company administrators can generate API keys from the WorkSkedge dashboard. Follow these steps:

1

Navigate to Settings

Log in to WorkSkedge and click Settings in the main navigation menu

2

Access Apps & Integrations

Select "Apps & Integrations" from the settings sidebar

3

Generate API Key

Click "Generate API Key" and configure your desired scopes and permissions

4

Save Securely

Copy and store your key immediately - it won't be shown again

Using Your API Key

Include your API key in the X-API-Key header with every API request:

curl
curl -X GET "https://app.workskedge.com/api/v1/projects" \
  -H "X-API-Key: wsk_your_api_key_here" \
  -H "Content-Type: application/json"
JavaScript (Fetch API)
const API_KEY = 'wsk_your_api_key_here';
const BASE_URL = 'https://app.workskedge.com/api/v1';

async function getProjects() {
  const response = await fetch(`${BASE_URL}/projects`, {
    method: 'GET',
    headers: {
      'X-API-Key': API_KEY,
      'Content-Type': 'application/json'
    }
  });

  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }

  return await response.json();
}
Python (Requests)
import requests

API_KEY = 'wsk_your_api_key_here'
BASE_URL = 'https://app.workskedge.com/api/v1'

def get_projects():
    headers = {
        'X-API-Key': API_KEY,
        'Content-Type': 'application/json'
    }

    response = requests.get(f'{BASE_URL}/projects', headers=headers)
    response.raise_for_status()

    return response.json()
PHP (cURL)

API Key Scopes

When generating an API key, you can configure specific scopes to limit access to certain resource types. This follows the principle of least privilege for improved security.

Available Scopes

projects:read

Read access to projects and related schedules

projects:write

Create, update, and delete projects

work-orders:read

Read access to work orders and assignments

work-orders:write

Create, update, and delete work orders

employees:read

Read access to employee records

employees:write

Create, update, and delete employee records

timesheets:read

Read access to timesheet entries

timesheets:write

Create, update, and delete timesheet entries

customers:read

Read access to customer and location data

customers:write

Create, update, and delete customers and locations

vendors:read

Read access to vendor and purchase order data

vendors:write

Create, update, and manage vendors and purchase orders

reports:read

Read access to daily and completion reports

reports:write

Submit and approve reports

webhooks:manage

Configure and manage webhook subscriptions

Security Best Practices

Follow these security guidelines to protect your API keys and WorkSkedge data:

  • Never commit API keys to version control. Use environment variables or secure secret management systems instead.
  • Store keys securely. Keep API keys encrypted at rest and only decrypt them when needed.
  • Use server-side requests only. Never expose API keys in client-side JavaScript, mobile apps, or browser extensions.
  • Apply the principle of least privilege. Only grant the minimum scopes required for your integration.
  • Rotate keys regularly. Generate new API keys periodically and revoke old ones.
  • Monitor API usage. Review API logs regularly to detect unusual activity or unauthorized access.
  • Revoke compromised keys immediately. If you suspect a key has been exposed, revoke it right away from the dashboard.
  • Use HTTPS only. Always make API requests over HTTPS to prevent key interception.

Storing API Keys Securely

Never hardcode API keys in your source code. Use environment variables or dedicated secrets management services.

Environment Variables

The simplest secure storage method for API keys is environment variables.

.env (add to .gitignore!)
# .env file (add to .gitignore!)
WORKSKEDGE_API_KEY=wsk_live_abc123def456...
WORKSKEDGE_WEBHOOK_SECRET=whsec_xyz789...
Node.js
require('dotenv').config();

const API_KEY = process.env.WORKSKEDGE_API_KEY;

if (!API_KEY) {
  throw new Error('WORKSKEDGE_API_KEY environment variable is not set');
}

async function makeRequest(endpoint) {
  const response = await fetch(`https://app.workskedge.com/api/v1${endpoint}`, {
    headers: {
      'X-API-Key': API_KEY,
      'Content-Type': 'application/json'
    }
  });
  return response.json();
}
Python
import os
from dotenv import load_dotenv

load_dotenv()

API_KEY = os.getenv('WORKSKEDGE_API_KEY')

if not API_KEY:
    raise ValueError('WORKSKEDGE_API_KEY environment variable is not set')

def make_request(endpoint):
    headers = {
        'X-API-Key': API_KEY,
        'Content-Type': 'application/json'
    }
    response = requests.get(f'https://app.workskedge.com/api/v1{endpoint}', headers=headers)
    return response.json()

Secrets Management Services

For production environments, use dedicated secrets management services like AWS Secrets Manager or HashiCorp Vault.

AWS Secrets Manager
const AWS = require('aws-sdk');
const secretsManager = new AWS.SecretsManager({ region: 'us-east-1' });

async function getAPIKey() {
  try {
    const data = await secretsManager.getSecretValue({
      SecretId: 'workskedge/api-key'
    }).promise();

    if ('SecretString' in data) {
      const secret = JSON.parse(data.SecretString);
      return secret.api_key;
    }
  } catch (error) {
    console.error('Error retrieving secret:', error);
    throw error;
  }
}

const API_KEY = await getAPIKey();
HashiCorp Vault
const vault = require('node-vault')({
  endpoint: 'https://vault.example.com:8200',
  token: process.env.VAULT_TOKEN
});

async function getAPIKey() {
  try {
    const result = await vault.read('secret/data/workskedge');
    return result.data.data.api_key;
  } catch (error) {
    console.error('Error retrieving secret from Vault:', error);
    throw error;
  }
}

const API_KEY = await getAPIKey();

Secure Authentication Wrapper

Create a centralized authentication wrapper to manage API keys and request headers consistently across your application.

WorkSkedge Client Class
class WorkSkedgeClient {
  constructor(apiKey, options = {}) {
    if (!apiKey) {
      throw new Error('API key is required');
    }

    this.apiKey = apiKey;
    this.baseUrl = options.baseUrl || 'https://app.workskedge.com/api/v1';
    this.timeout = options.timeout || 30000;
  }

  getHeaders() {
    return {
      'X-API-Key': this.apiKey,
      'Content-Type': 'application/json',
      'User-Agent': 'MyIntegration/1.0'
    };
  }

  maskApiKey(key) {
    if (!key || key.length < 12) return '***';
    return `${key.substring(0, 4)}...${key.substring(key.length - 4)}`;
  }

  log(message, data = {}) {
    const safeData = { ...data };
    if (safeData.apiKey) {
      safeData.apiKey = this.maskApiKey(safeData.apiKey);
    }
    console.log(message, safeData);
  }

  async request(endpoint, options = {}) {
    const url = `${this.baseUrl}${endpoint}`;
    const config = {
      ...options,
      headers: {
        ...this.getHeaders(),
        ...options.headers
      }
    };

    this.log('Making API request', {
      method: config.method || 'GET',
      endpoint,
      hasAuth: !!config.headers['X-API-Key']
    });

    try {
      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), this.timeout);

      const response = await fetch(url, {
        ...config,
        signal: controller.signal
      });

      clearTimeout(timeoutId);

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }

      return await response.json();
    } catch (error) {
      this.log('API request failed', {
        endpoint,
        error: error.message
      });
      throw error;
    }
  }

  async get(endpoint, params = {}) {
    const queryString = new URLSearchParams(params).toString();
    const url = queryString ? `${endpoint}?${queryString}` : endpoint;
    return this.request(url, { method: 'GET' });
  }

  async post(endpoint, data) {
    return this.request(endpoint, {
      method: 'POST',
      body: JSON.stringify(data)
    });
  }

  async put(endpoint, data) {
    return this.request(endpoint, {
      method: 'PUT',
      body: JSON.stringify(data)
    });
  }

  async delete(endpoint) {
    return this.request(endpoint, { method: 'DELETE' });
  }
}

const client = new WorkSkedgeClient(process.env.WORKSKEDGE_API_KEY);

const projects = await client.get('/projects');
const newWorkOrder = await client.post('/work-orders', {
  title: 'Install HVAC',
  project_id: 'proj_123'
});

Webhook Signature Verification

Always verify webhook signatures to ensure requests come from WorkSkedge and haven't been tampered with.

Webhook Verification
const crypto = require('crypto');

class WebhookVerifier {
  constructor(webhookSecret) {
    if (!webhookSecret) {
      throw new Error('Webhook secret is required');
    }
    this.secret = webhookSecret;
  }

  verify(payload, signature, timestamp) {
    const now = Math.floor(Date.now() / 1000);
    if (Math.abs(now - timestamp) > 300) {
      console.error('Webhook timestamp is too old');
      return false;
    }

    const signedPayload = `${timestamp}.${payload}`;
    const expectedSignature = crypto
      .createHmac('sha256', this.secret)
      .update(signedPayload)
      .digest('hex');

    try {
      return crypto.timingSafeEqual(
        Buffer.from(signature),
        Buffer.from(expectedSignature)
      );
    } catch (error) {
      console.error('Signature comparison failed:', error);
      return false;
    }
  }

  middleware() {
    return (req, res, next) => {
      const signature = req.headers['x-workskedge-signature'];
      const timestamp = req.headers['x-workskedge-timestamp'];
      const payload = JSON.stringify(req.body);

      if (!signature || !timestamp) {
        return res.status(401).json({
          error: 'Missing signature or timestamp headers'
        });
      }

      if (!this.verify(payload, signature, parseInt(timestamp))) {
        return res.status(401).json({
          error: 'Invalid webhook signature'
        });
      }

      next();
    };
  }
}

const verifier = new WebhookVerifier(process.env.WORKSKEDGE_WEBHOOK_SECRET);

app.post('/webhooks/workskedge',
  express.json(),
  verifier.middleware(),
  async (req, res) => {
    const event = req.body;
    console.log('Verified webhook event:', event.event_type);
    res.status(200).json({ received: true });
  }
);
For more details on webhook setup and event types, see the Webhooks documentation.

API Key Rotation Strategy

Implement regular key rotation to minimize the impact if a key is compromised.

1

Generate New Key

Create a new API key with the same permissions as the old key

2

Deploy New Key

Update your application configuration with the new key

3

Monitor

Watch for errors and ensure all services use the new key

4

Revoke Old Key

After confirming success, revoke the old key

Zero-Downtime Rotation

Support multiple API keys during rotation to prevent service interruption:

Multi-Key Support
class WorkSkedgeClientWithRotation {
  constructor(primaryKey, fallbackKeys = []) {
    this.keys = [primaryKey, ...fallbackKeys].filter(Boolean);
    if (this.keys.length === 0) {
      throw new Error('At least one API key is required');
    }
  }

  async requestWithFallback(endpoint, options = {}) {
    let lastError;

    for (const key of this.keys) {
      try {
        const response = await fetch(endpoint, {
          ...options,
          headers: {
            'X-API-Key': key,
            'Content-Type': 'application/json',
            ...options.headers
          }
        });

        if (response.ok) {
          return await response.json();
        }

        if (response.status === 401) {
          console.warn('Authentication failed, trying next key...');
          continue;
        }

        throw new Error(`HTTP ${response.status}`);
      } catch (error) {
        lastError = error;
        console.warn('Request failed, trying fallback key...');
      }
    }

    throw lastError || new Error('All API keys failed');
  }
}

const client = new WorkSkedgeClientWithRotation(
  process.env.WORKSKEDGE_API_KEY_NEW,
  [process.env.WORKSKEDGE_API_KEY_OLD]
);

Monitoring and Alerting

Set up monitoring to detect suspicious API key usage or potential security issues.

API Key Monitor
class APIKeyMonitor {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.requestCounts = new Map();
    this.failedAttempts = 0;
    this.alertThreshold = 10;
  }

  logRequest(endpoint, success, statusCode) {
    const hour = new Date().toISOString().slice(0, 13);
    const key = `${hour}:${endpoint}`;

    if (!this.requestCounts.has(key)) {
      this.requestCounts.set(key, { success: 0, failure: 0 });
    }

    const stats = this.requestCounts.get(key);
    if (success) {
      stats.success++;
      this.failedAttempts = 0;
    } else {
      stats.failure++;
      this.failedAttempts++;

      if (this.failedAttempts >= this.alertThreshold) {
        this.sendAlert('High number of failed API requests', {
          endpoint,
          statusCode,
          failedAttempts: this.failedAttempts
        });
      }
    }

    if (stats.failure > 50) {
      this.sendAlert('Unusually high failure rate', {
        endpoint,
        hour,
        stats
      });
    }
  }

  sendAlert(message, details) {
    console.error('SECURITY ALERT:', message, details);
  }

  getStats() {
    return Array.from(this.requestCounts.entries()).map(([key, stats]) => ({
      timeEndpoint: key,
      ...stats
    }));
  }
}

const monitor = new APIKeyMonitor(process.env.WORKSKEDGE_API_KEY);

async function monitoredRequest(endpoint) {
  try {
    const response = await fetch(endpoint, {
      headers: {
        'X-API-Key': monitor.apiKey,
        'Content-Type': 'application/json'
      }
    });

    monitor.logRequest(endpoint, response.ok, response.status);
    return await response.json();
  } catch (error) {
    monitor.logRequest(endpoint, false, null);
    throw error;
  }
}

Security Checklist

Use this comprehensive checklist to ensure your API integration follows security best practices:

Managing API Keys

You can view, regenerate, and revoke API keys at any time from the WorkSkedge dashboard.

Key Management Actions

  • View active keys: See all API keys associated with your company account
  • Generate new key: Create additional keys for different integrations or environments
  • Update scopes: Modify permissions for existing API keys
  • Revoke key: Immediately disable a key to prevent further access
  • View usage: Monitor API call volume and track which keys are being used
  • Rate Limits

    The WorkSkedge API implements rate limiting to ensure fair usage and maintain service quality for all users. Rate limits are applied per API key and reset on a rolling basis.

    Current Rate Limits

    • 60 requests per minute per API key
    • 1,000 requests per hour per API key
    • 10,000 requests per day per API key

    Rate limits apply to all API endpoints collectively. If you need higher limits for your integration, please contact our support team.

    Rate Limit Headers

    Every API response includes headers that provide information about your current rate limit status:

    X-RateLimit-Limit

    The maximum number of requests allowed in the current time window

    X-RateLimit-Remaining

    The number of requests remaining in the current time window

    X-RateLimit-Reset

    Unix timestamp (in seconds) when the rate limit window resets

    Retry-After

    Number of seconds to wait before making another request (only present when rate limited)

    Example Response Headers

    HTTP Headers
    HTTP/1.1 200 OK
    X-RateLimit-Limit: 60
    X-RateLimit-Remaining: 42
    X-RateLimit-Reset: 1704140400
    Content-Type: application/json

    429 Too Many Requests

    When you exceed the rate limit, the API returns a 429 Too Many Requests response. Your application should handle this gracefully by implementing exponential backoff.

    Rate Limit Error Response

    429 Response
    HTTP/1.1 429 Too Many Requests
    X-RateLimit-Limit: 60
    X-RateLimit-Remaining: 0
    X-RateLimit-Reset: 1704140460
    Retry-After: 60
    Content-Type: application/json
    
    {
      "error": {
        "code": "RATE_LIMIT_EXCEEDED",
        "message": "Rate limit exceeded. Please retry after 60 seconds.",
        "details": {
          "limit": 60,
          "window": "1 minute",
          "reset_at": "2024-01-01T15:01:00Z"
        }
      }
    }

    Handling Rate Limits

    Implement these strategies to work within rate limits and handle 429 responses gracefully:

    1. Monitor Rate Limit Headers

    Check the X-RateLimit-Remaining header before making additional requests:

    JavaScript Example
    async function makeApiRequest(url) {
      const response = await fetch(url, {
        headers: {
          'X-API-Key': 'wsk_your_api_key_here',
          'Content-Type': 'application/json'
        }
      });
    
      const remaining = parseInt(response.headers.get('X-RateLimit-Remaining'));
      const reset = parseInt(response.headers.get('X-RateLimit-Reset'));
    
      console.log(`Requests remaining: ${remaining}`);
    
      if (remaining < 5) {
        const waitTime = (reset * 1000) - Date.now();
        console.warn(`Approaching rate limit. Reset in ${waitTime}ms`);
      }
    
      return await response.json();
    }

    2. Implement Exponential Backoff

    When you receive a 429 response, wait before retrying with increasing delays:

    JavaScript Example
    async function makeRequestWithRetry(url, maxRetries = 3) {
      let retries = 0;
    
      while (retries < maxRetries) {
        const response = await fetch(url, {
          headers: {
            'X-API-Key': 'wsk_your_api_key_here',
            'Content-Type': 'application/json'
          }
        });
    
        if (response.status === 429) {
          const retryAfter = parseInt(response.headers.get('Retry-After')) || 60;
          const backoffTime = Math.min(retryAfter * Math.pow(2, retries), 300);
    
          console.log(`Rate limited. Retrying in ${backoffTime} seconds...`);
          await new Promise(resolve => setTimeout(resolve, backoffTime * 1000));
    
          retries++;
          continue;
        }
    
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
    
        return await response.json();
      }
    
      throw new Error('Max retries exceeded');
    }

    3. Batch Requests Efficiently

    Minimize API calls by using pagination effectively and batching operations:

    • Use the maximum limit parameter (100) for list endpoints
    • Cache responses when data doesn't change frequently
    • Combine multiple operations into single requests where possible
    • Use webhooks to receive updates instead of polling for changes

    4. Distribute Load Across Time

    Space out non-urgent requests to avoid hitting rate limits:

    JavaScript Example
    class RateLimiter {
      constructor(requestsPerMinute = 60) {
        this.requestsPerMinute = requestsPerMinute;
        this.queue = [];
        this.processing = false;
      }
    
      async scheduleRequest(fn) {
        return new Promise((resolve, reject) => {
          this.queue.push({ fn, resolve, reject });
          this.processQueue();
        });
      }
    
      async processQueue() {
        if (this.processing || this.queue.length === 0) return;
    
        this.processing = true;
        const delayBetweenRequests = (60 * 1000) / this.requestsPerMinute;
    
        while (this.queue.length > 0) {
          const { fn, resolve, reject } = this.queue.shift();
    
          try {
            const result = await fn();
            resolve(result);
          } catch (error) {
            reject(error);
          }
    
          if (this.queue.length > 0) {
            await new Promise(r => setTimeout(r, delayBetweenRequests));
          }
        }
    
        this.processing = false;
      }
    }
    
    const limiter = new RateLimiter(60);
    
    async function getProject(id) {
      return limiter.scheduleRequest(async () => {
        const response = await fetch(`https://app.workskedge.com/api/v1/projects/${id}`, {
          headers: {
            'X-API-Key': 'wsk_your_api_key_here',
            'Content-Type': 'application/json'
          }
        });
        return await response.json();
      });
    }

    Best Practices

    Follow these guidelines to optimize your API usage and avoid hitting rate limits:

    • Cache responses: Store API responses locally and reuse them when the data hasn't changed
    • Use webhooks: Subscribe to webhooks instead of polling endpoints for changes
    • Optimize pagination: Request larger pages (up to 100 records) to reduce total API calls
    • Implement proper error handling: Always handle 429 responses with retry logic
    • Monitor your usage: Track rate limit headers to understand your consumption patterns
    • Schedule bulk operations: Run large data syncs during off-peak hours when possible
    • Contact support for higher limits: If your integration requires more capacity, reach out to discuss options

    Need Higher Limits?

    If your integration requires higher rate limits, contact our support team to discuss your use case. We can work with you to find a solution that meets your needs.