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.
https://app.workskedge.com/api/v1Authentication: API Key via
X-API-Key headerFormat: 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:
Get API Key
Generate your API key from WorkSkedge Settings → Apps & Integrations
Make Request
Include your key in the X-API-Key header with each request
Handle Response
Process JSON responses and handle standard HTTP status codes
Example Request
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"
{
"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
GETPOSTPUTDELETE/employeesGETPOSTPUTDELETE/contacts
Work Management
Projects, Work Orders, Schedules
GETPOSTPUTDELETE/projectsGETPOSTPUTDELETE/work-ordersGET/schedules
Time & Attendance
Timesheets, Time Off
GETPOSTPUTDELETE/timesheetsGETPOSTPUTDELETE/time-off
Supply Chain
Vendors, Locations, Purchase Orders
GETPOSTPUTDELETE/vendorsGETPOSTPUTDELETE/vendor-locationsGETPOSTPUT/purchase-orders
Business Data
Customers, Locations
GETPOSTPUTDELETE/customersGETPOSTPUTDELETE/locations
Reporting
Daily Reports, Completion Reports
GETPOST/daily-reportsGETPOST/completion-reports
Pagination
All list endpoints support pagination using limit and offset query parameters.
Query Parameters
Number of records to return per page. Default: 50, Maximum: 100
Number of records to skip before returning results. Default: 0
Pagination Response
All paginated responses include a pagination object with metadata:
{
"data": [...],
"pagination": {
"limit": 50,
"offset": 0,
"total": 156
}
}
Example: Fetching All Pages
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.
Request succeeded
Resource created successfully
Invalid request parameters or body
Missing or invalid API key
API key lacks required permissions
Resource does not exist
Rate limit exceeded
Unexpected server error
Error Response Format
{
"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.
Getting Your API Key
Company administrators can generate API keys from the WorkSkedge dashboard. Follow these steps:
Navigate to Settings
Log in to WorkSkedge and click Settings in the main navigation menu
Access Apps & Integrations
Select "Apps & Integrations" from the settings sidebar
Generate API Key
Click "Generate API Key" and configure your desired scopes and permissions
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 -X GET "https://app.workskedge.com/api/v1/projects" \
-H "X-API-Key: wsk_your_api_key_here" \
-H "Content-Type: application/json"
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();
}
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()
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
Read access to projects and related schedules
Create, update, and delete projects
Read access to work orders and assignments
Create, update, and delete work orders
Read access to employee records
Create, update, and delete employee records
Read access to timesheet entries
Create, update, and delete timesheet entries
Read access to customer and location data
Create, update, and delete customers and locations
Read access to vendor and purchase order data
Create, update, and manage vendors and purchase orders
Read access to daily and completion reports
Submit and approve reports
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 file (add to .gitignore!)
WORKSKEDGE_API_KEY=wsk_live_abc123def456...
WORKSKEDGE_WEBHOOK_SECRET=whsec_xyz789...
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();
}
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.
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();
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.
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.
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 });
}
);
API Key Rotation Strategy
Implement regular key rotation to minimize the impact if a key is compromised.
Generate New Key
Create a new API key with the same permissions as the old key
Deploy New Key
Update your application configuration with the new key
Monitor
Watch for errors and ensure all services use the new key
Revoke Old Key
After confirming success, revoke the old key
Zero-Downtime Rotation
Support multiple API keys during rotation to prevent service interruption:
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.
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
Related Resources
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:
The maximum number of requests allowed in the current time window
The number of requests remaining in the current time window
Unix timestamp (in seconds) when the rate limit window resets
Number of seconds to wait before making another request (only present when rate limited)
Example Response 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
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:
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:
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
limitparameter (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:
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.