Shift Marketplace API Documentation

System Features Special Features Api
Last updated: January 26, 2026 β€’ Version: 1.0

Shift Marketplace API Documentation

Complete API reference for integrating with the Shift Marketplace endpoints. This documentation covers the unified marketplace API for managing shift listings, trade applications, and direct peer-to-peer offers.

API Version: v1
Base URL: https://your-domain.com/api/v1/shift_marketplace
Authentication: Required (Bearer Token)
Rate Limit: 100 requests per minute


🎯 Overview

The Shift Marketplace API provides three integrated modules for shift trading and coverage:

  1. Listings API - Post shifts for pickup or trade
  2. Applications API - Apply for trades and manage applications
  3. Direct Offers API - Send peer-to-peer shift offers

All endpoints are organized under the unified /api/v1/shift_marketplace/ namespace for consistency.


πŸ” Authentication

API Token Authentication

All API requests require authentication using a Bearer token:

Authorization: Bearer your-api-token-here
Content-Type: application/json

Getting Your API Token

  1. Navigate to Account Settings β†’ API Tokens
  2. Click Create New Token
  3. Select appropriate scopes (e.g., shift_marketplace:read, shift_marketplace:write)
  4. Copy the token (shown only once)
  5. Store securely in your application

Required Permissions

  • Shift Marketplace app must be enabled for your business
  • User must have access to the Shift Marketplace feature
  • Token must include appropriate scopes for the operations

πŸ“Š Unified Marketplace Structure

graph TD A[Shift Marketplace API] --> B[Listings API] A --> C[Applications API] A --> D[Direct Offers API] B --> B1[Pickup Listings] B --> B2[Trade-Only Listings] B --> B3[Both Types] C --> C1[Submit Application] C --> C2[Accept/Reject] C --> C3[Withdraw] D --> D1[Send Offer] D --> D2[Accept/Decline] D --> D3[Track Status] style A fill:#4A90E2,color:#fff style B fill:#50C878,color:#fff style C fill:#F39C12,color:#fff style D fill:#9B59B6,color:#fff

πŸ› οΈ API Endpoints

1️⃣ Listings API (Pickup/Trade-Only/Both)

Base URL: /api/v1/shift_marketplace/listings

List All Listings

GET /api/v1/shift_marketplace/listings

Query Parameters:

Parameter Type Required Description Example
page integer No Page number for pagination 1
per_page integer No Items per page (max 100) 25
status string No Filter by status open, filled, closed, cancelled
listing_type string No Filter by type pickup, trade_only, both
location_id integer No Filter by location 123
min_price decimal No Minimum price filter 10.00
max_price decimal No Maximum price filter 50.00

Example Request:

curl -X GET "https://your-domain.com/api/v1/shift_marketplace/listings?listing_type=pickup&status=open" \
  -H "Authorization: Bearer your-token" \
  -H "Content-Type: application/json"

Example Response:

{
  "success": true,
  "data": {
    "items": [
      {
        "id": 123,
        "status": "open",
        "listing_type": "pickup",
        "price": 25.00,
        "currency": "USD",
        "notes": "Need someone to cover this shift",
        "created_at": "2025-11-21T10:00:00Z",
        "updated_at": "2025-11-21T10:00:00Z",
        "shift": {
          "id": 456,
          "name": "Evening Shift",
          "start_time": "2025-12-01T17:00:00Z",
          "end_time": "2025-12-01T23:00:00Z",
          "location": "Main Office"
        },
        "user": {
          "id": 1,
          "name": "John Doe",
          "email": "john@example.com"
        },
        "created_by": {
          "id": 1,
          "name": "John Doe",
          "email": "john@example.com"
        },
        "claimed_by": null,
        "can_claim": true,
        "accepts_applications": false,
        "is_owner": false,
        "can_update": false,
        "can_delete": false
      }
    ],
    "meta": {
      "pagination": {
        "current_page": 1,
        "total_pages": 5,
        "total_count": 120,
        "per_page": 25
      }
    }
  }
}

Get Single Listing

GET /api/v1/shift_marketplace/listings/{id}

Create Listing

POST /api/v1/shift_marketplace/listings

Request Body:

{
  "listing": {
    "shift_id": 456,
    "price": 25.00,
    "currency": "USD",
    "listing_type": "pickup",
    "notes": "Need someone to cover this shift",
    "requirements": "Must have cashier experience"
  }
}

Listing Types:

  • pickup - Direct claim, no trade required
  • trade_only - Requires shift trade application
  • both - Accepts both direct claims and trade applications

Update Listing

PATCH /api/v1/shift_marketplace/listings/{id}

Request Body: (All fields optional)

{
  "listing": {
    "price": 30.00,
    "notes": "Updated notes",
    "status": "open"
  }
}

Delete Listing

DELETE /api/v1/shift_marketplace/listings/{id}

Response:

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

Claim Listing (Pickup)

POST /api/v1/shift_marketplace/listings/{id}/claim

Use Case: Immediately claim a pickup-type listing without needing to trade.

Response:

{
  "success": true,
  "data": {
    "listing": { /* updated listing with claimed_by */ },
    "message": "Shift claimed successfully"
  }
}

2️⃣ Applications API (Trade Workflow)

Base URL: /api/v1/shift_marketplace/applications

List Applications

GET /api/v1/shift_marketplace/applications

Query Parameters:

Parameter Type Required Description Example
page integer No Page number 1
per_page integer No Items per page 25
status string No Filter by status pending, accepted, rejected, withdrawn

Response includes:

  • Applications you’ve submitted (where you’re the applicant)
  • Applications received on your listings (where you’re the listing owner)

Example Response:

{
  "success": true,
  "data": {
    "items": [
      {
        "id": 789,
        "status": "pending",
        "notes": "I can swap my Tuesday morning shift for this",
        "created_at": "2025-11-21T11:00:00Z",
        "updated_at": "2025-11-21T11:00:00Z",
        "listing": {
          "id": 123,
          "status": "open",
          "listing_type": "trade_only",
          "shift": {
            "id": 456,
            "name": "Wednesday Evening",
            "start_time": "2025-12-03T17:00:00Z",
            "end_time": "2025-12-03T23:00:00Z",
            "location": "Main Office"
          },
          "user": {
            "id": 1,
            "name": "John Doe",
            "email": "john@example.com"
          }
        },
        "user": {
          "id": 2,
          "name": "Jane Smith",
          "email": "jane@example.com"
        },
        "offered_shift": {
          "id": 789,
          "name": "Tuesday Morning",
          "start_time": "2025-12-02T08:00:00Z",
          "end_time": "2025-12-02T12:00:00Z",
          "location": "Warehouse"
        },
        "is_applicant": false,
        "is_listing_owner": true,
        "can_accept": true,
        "can_reject": true,
        "can_withdraw": false
      }
    ]
  }
}

Submit Application

POST /api/v1/shift_marketplace/applications

Request Body:

{
  "application": {
    "listing_id": 123,
    "notes": "I can swap my Tuesday morning shift for this",
    "offered_shift_id": 789
  }
}

Response:

{
  "success": true,
  "data": {
    "application": { /* created application */ },
    "message": "Application submitted successfully"
  }
}

Get Application Details

GET /api/v1/shift_marketplace/applications/{id}

Accept Application (Listing Owner)

POST /api/v1/shift_marketplace/applications/{id}/accept

Use Case: Listing owner accepts a trade application. The system will:

  1. Swap shift assignments between both users
  2. Update listing status to filled
  3. Send notifications to both parties

Response:

{
  "success": true,
  "data": {
    "application": { /* updated application */ },
    "message": "Application accepted and shifts swapped successfully"
  }
}

Reject Application (Listing Owner)

POST /api/v1/shift_marketplace/applications/{id}/reject

Response:

{
  "success": true,
  "data": {
    "application": { /* updated application */ },
    "message": "Application rejected"
  }
}

Withdraw Application (Applicant)

POST /api/v1/shift_marketplace/applications/{id}/withdraw

Use Case: Applicant withdraws their pending application.

Response:

{
  "success": true,
  "data": {
    "application": { /* updated application */ },
    "message": "Application withdrawn successfully"
  }
}

3️⃣ Direct Offers API (Peer-to-Peer)

Base URL: /api/v1/shift_marketplace/direct_offers

List Direct Offers

GET /api/v1/shift_marketplace/direct_offers

Query Parameters:

Parameter Type Required Description Example
filter_type string No Filter by direction sent, received, (omit for all)
status string No Filter by status pending, accepted, declined, expired, cancelled
page integer No Page number 1
per_page integer No Items per page 25

Example Request:

curl -X GET "https://your-domain.com/api/v1/shift_marketplace/direct_offers?filter_type=received&status=pending" \
  -H "Authorization: Bearer your-token"

Example Response:

{
  "success": true,
  "data": {
    "items": [
      {
        "id": 1,
        "status": "pending",
        "notes": "Can you take this shift for me?",
        "expires_at": "2025-11-28T10:00:00Z",
        "created_at": "2025-11-21T09:00:00Z",
        "updated_at": "2025-11-21T09:00:00Z",
        "expired": false,
        "shift": {
          "id": 123,
          "name": "Morning Shift",
          "start_time": "2025-11-27T09:00:00Z",
          "end_time": "2025-11-27T17:00:00Z",
          "location": {
            "id": 1,
            "name": "Downtown Store"
          }
        },
        "from_user": {
          "id": 1,
          "name": "John Doe",
          "email": "john@example.com"
        },
        "to_user": {
          "id": 2,
          "name": "Jane Smith",
          "email": "jane@example.com"
        },
        "is_sender": false,
        "is_recipient": true,
        "can_accept": true,
        "can_decline": true
      }
    ]
  }
}

Get Single Offer

GET /api/v1/shift_marketplace/direct_offers/{id}

Create Direct Offer

POST /api/v1/shift_marketplace/direct_offers

Request Body:

{
  "direct_offer": {
    "shift_id": 123,
    "to_user_id": 2,
    "notes": "Can you take this shift for me?",
    "expires_at": "2025-11-28T10:00:00Z"
  }
}

Validations:

  • Sender must be assigned to the shift
  • Recipient must belong to the same business
  • Cannot offer shift to yourself

Response:

{
  "success": true,
  "data": {
    "offer": { /* created offer */ },
    "message": "Direct offer sent successfully"
  }
}

Accept Offer (Recipient)

POST /api/v1/shift_marketplace/direct_offers/{id}/accept

Use Case: Recipient accepts the direct offer. The system will:

  1. Transfer shift assignment from sender to recipient
  2. Update offer status to accepted
  3. Mark shift as covered
  4. Send notifications

Response:

{
  "success": true,
  "data": {
    "offer": { /* updated offer */ },
    "message": "Shift offer accepted successfully"
  }
}

Decline Offer (Recipient)

POST /api/v1/shift_marketplace/direct_offers/{id}/decline

Response:

{
  "success": true,
  "data": {
    "offer": { /* updated offer */ },
    "message": "Shift offer declined"
  }
}

🎯 Common Use Cases

Use Case 1: Employee Posts Shift for Pickup

Scenario: John can’t work his evening shift and wants anyone to claim it.

Implementation:

// Step 1: Create pickup listing
const response = await fetch('/api/v1/shift_marketplace/listings', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    listing: {
      shift_id: 456,
      listing_type: 'pickup',
      price: 25.00,
      notes: 'Can\'t make it tonight, need coverage'
    }
  })
});

// Step 2: Anyone can claim it directly
const claimResponse = await fetch('/api/v1/shift_marketplace/listings/123/claim', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${claimerToken}`,
    'Content-Type': 'application/json'
  }
});

Business Value:

  • βœ… Quick shift coverage
  • βœ… Reduced no-shows
  • βœ… Employee flexibility
  • βœ… Manager peace of mind

Use Case 2: Trade-Only Listing with Applications

Scenario: Jane wants to trade her Wednesday shift for a Tuesday shift.

Implementation:

import requests

# Step 1: Create trade-only listing
response = requests.post(
    'https://your-domain.com/api/v1/shift_marketplace/listings',
    headers={'Authorization': f'Bearer {token}'},
    json={
        'listing': {
            'shift_id': 456,
            'listing_type': 'trade_only',
            'notes': 'Looking to trade for Tuesday morning shift'
        }
    }
)

# Step 2: Another user applies with their shift
apply_response = requests.post(
    'https://your-domain.com/api/v1/shift_marketplace/applications',
    headers={'Authorization': f'Bearer {applicant_token}'},
    json={
        'application': {
            'listing_id': 123,
            'offered_shift_id': 789,
            'notes': 'I can swap my Tuesday morning shift'
        }
    }
)

# Step 3: Original poster reviews and accepts
accept_response = requests.post(
    f'https://your-domain.com/api/v1/shift_marketplace/applications/{application_id}/accept',
    headers={'Authorization': f'Bearer {token}'}
)

Business Value:

  • βœ… Fair shift trading
  • βœ… Maintains coverage requirements
  • βœ… Employee autonomy
  • βœ… Reduced manager workload

Use Case 3: Direct Peer-to-Peer Offer

Scenario: John knows Jane wants extra hours and offers his shift directly.

Implementation:

// Step 1: Send direct offer
const offerResponse = await fetch('/api/v1/shift_marketplace/direct_offers', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${johnToken}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    direct_offer: {
      shift_id: 123,
      to_user_id: 2, // Jane's ID
      notes: 'Thought you might want this shift',
      expires_at: '2025-11-28T10:00:00Z'
    }
  })
});

// Step 2: Jane accepts the offer
const acceptResponse = await fetch(`/api/v1/shift_marketplace/direct_offers/${offerId}/accept`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${janeToken}`,
    'Content-Type': 'application/json'
  }
});

Business Value:

  • βœ… Targeted shift coverage
  • βœ… Builds team collaboration
  • βœ… Fast resolution
  • βœ… Personal relationships matter

Use Case 4: Manager Monitoring Dashboard

Scenario: Manager wants to monitor all marketplace activity for their team.

Implementation:

import requests

def get_marketplace_overview(token):
    """Get complete marketplace overview"""
    
    # Get all open listings
    listings = requests.get(
        'https://your-domain.com/api/v1/shift_marketplace/listings',
        headers={'Authorization': f'Bearer {token}'},
        params={'status': 'open'}
    ).json()
    
    # Get pending applications
    applications = requests.get(
        'https://your-domain.com/api/v1/shift_marketplace/applications',
        headers={'Authorization': f'Bearer {token}'},
        params={'status': 'pending'}
    ).json()
    
    # Get active direct offers
    direct_offers = requests.get(
        'https://your-domain.com/api/v1/shift_marketplace/direct_offers',
        headers={'Authorization': f'Bearer {token}'},
        params={'status': 'pending'}
    ).json()
    
    return {
        'open_listings': len(listings['data']['items']),
        'pending_applications': len(applications['data']['items']),
        'active_offers': len(direct_offers['data']['items']),
        'total_activity': len(listings['data']['items']) + 
                          len(applications['data']['items']) + 
                          len(direct_offers['data']['items'])
    }

# Usage
overview = get_marketplace_overview(manager_token)
print(f"Marketplace Overview: {overview}")

Business Value:

  • βœ… Real-time visibility
  • βœ… Proactive management
  • βœ… Identify coverage gaps
  • βœ… Team engagement metrics

Use Case 5: Mobile App Integration

Scenario: Mobile app showing marketplace notifications and quick actions.

Implementation:

class ShiftMarketplaceService {
  constructor(apiToken) {
    this.baseUrl = 'https://your-domain.com/api/v1/shift_marketplace';
    this.token = apiToken;
  }

  async getMyActivity() {
    // Get received direct offers (action required)
    const receivedOffers = await this.fetch('/direct_offers?filter_type=received&status=pending');
    
    // Get applications on my listings (manager needs to review)
    const myApplications = await this.fetch('/applications?status=pending');
    
    // Get my open listings
    const myListings = await this.fetch('/listings?status=open');
    
    return {
      pendingActions: receivedOffers.data.items.length + myApplications.data.items.length,
      openListings: myListings.data.items.length,
      needsAttention: receivedOffers.data.items.filter(o => !o.expired),
      receivedOffers: receivedOffers.data.items,
      applications: myApplications.data.items
    };
  }

  async fetch(endpoint, options = {}) {
    const response = await fetch(`${this.baseUrl}${endpoint}`, {
      ...options,
      headers: {
        'Authorization': `Bearer ${this.token}`,
        'Content-Type': 'application/json',
        ...options.headers
      }
    });
    return response.json();
  }
}

// Usage in mobile app
const marketplace = new ShiftMarketplaceService(userToken);
const activity = await marketplace.getMyActivity();

// Show notification badge
showNotificationBadge(activity.pendingActions);

Business Value:

  • βœ… Real-time engagement
  • βœ… Reduce response time
  • βœ… Improve coverage rates
  • βœ… Better user experience

⚠️ Error Handling

Standard Error Response

{
  "success": false,
  "error": {
    "code": "validation_error",
    "message": "The request could not be processed",
    "details": [
      "Shift must be assigned to you to offer it",
      "Cannot offer shift to yourself"
    ]
  },
  "request_id": "req_abc123"
}

Common Error Codes

HTTP Status Error Code Description Solution
400 validation_error Request validation failed Check required fields and formats
401 unauthorized Invalid or missing authentication Verify API token
403 forbidden Insufficient permissions Check user has marketplace access
403 marketplace_disabled Marketplace not enabled Enable app for business
404 not_found Resource not found Verify listing/offer/application ID
409 conflict Resource conflict Check for existing offers/applications
422 invalid_action Action not allowed in current state Review status and permissions
429 rate_limited Too many requests Implement exponential backoff
500 internal_error Server error Contact support with request_id

Error Handling Best Practices

async function safeApiCall(endpoint, options) {
  try {
    const response = await fetch(endpoint, options);
    const data = await response.json();
    
    if (!response.ok) {
      // Handle specific error codes
      switch (response.status) {
        case 403:
          if (data.error.code === 'marketplace_disabled') {
            throw new Error('Shift Marketplace is not enabled for your business');
          }
          break;
        case 422:
          if (data.error.code === 'invalid_action') {
            throw new Error(`Action not allowed: ${data.error.message}`);
          }
          break;
        case 429:
          // Implement retry with exponential backoff
          await sleep(5000);
          return safeApiCall(endpoint, options);
      }
      
      throw new Error(data.error.message || 'API request failed');
    }
    
    return data;
  } catch (error) {
    console.error('API Error:', error);
    throw error;
  }
}

πŸ“‹ Data Models

Listing Object

{
  "id": 123,
  "status": "open",
  "listing_type": "pickup",
  "price": 25.00,
  "currency": "USD",
  "notes": "Optional notes",
  "created_at": "2025-11-21T10:00:00Z",
  "updated_at": "2025-11-21T10:00:00Z",
  "shift": { /* Shift object */ },
  "user": { /* Original shift owner */ },
  "created_by": { /* User who created listing */ },
  "claimed_by": { /* User who claimed (if applicable) */ },
  "can_claim": true,
  "accepts_applications": false,
  "is_owner": false,
  "can_update": false,
  "can_delete": false
}

Application Object

{
  "id": 789,
  "status": "pending",
  "notes": "Optional notes",
  "created_at": "2025-11-21T11:00:00Z",
  "updated_at": "2025-11-21T11:00:00Z",
  "listing": { /* Listing object */ },
  "user": { /* Applicant */ },
  "offered_shift": { /* Shift offered in trade */ },
  "is_applicant": true,
  "is_listing_owner": false,
  "can_accept": false,
  "can_reject": false,
  "can_withdraw": true
}

Direct Offer Object

{
  "id": 1,
  "status": "pending",
  "notes": "Optional notes",
  "expires_at": "2025-11-28T10:00:00Z",
  "created_at": "2025-11-21T09:00:00Z",
  "updated_at": "2025-11-21T09:00:00Z",
  "expired": false,
  "shift": { /* Shift object */ },
  "from_user": { /* Sender */ },
  "to_user": { /* Recipient */ },
  "is_sender": true,
  "is_recipient": false,
  "can_accept": false,
  "can_decline": false
}

Permission Flags

All responses include helpful permission flags:

Flag Description
can_claim Current user can claim this listing
accepts_applications Listing accepts trade applications
is_owner Current user owns the listing/shift
is_applicant Current user submitted this application
is_listing_owner Current user owns the listing being applied to
is_sender Current user sent this direct offer
is_recipient Current user received this direct offer
can_accept Current user can accept this item
can_reject Current user can reject this application
can_decline Current user can decline this offer
can_withdraw Current user can withdraw this application
can_update Current user can update this listing
can_delete Current user can delete this listing

πŸ”„ Workflow Diagrams

Pickup Listing Workflow

sequenceDiagram participant Owner as Shift Owner participant API as Marketplace API participant Claimer as Other User participant System as System Owner->>API: POST /listings (type: pickup) API->>System: Create listing System-->>API: Listing created API-->>Owner: Success Claimer->>API: GET /listings (status: open) API-->>Claimer: Available listings Claimer->>API: POST /listings/{id}/claim API->>System: Validate & transfer shift System->>System: Update assignments System->>System: Send notifications System-->>API: Claim successful API-->>Claimer: Success System->>Owner: Notification: Shift claimed System->>Claimer: Notification: Assignment confirmed

Trade Application Workflow

sequenceDiagram participant Owner as Listing Owner participant API as Marketplace API participant Applicant as Applicant participant System as System Owner->>API: POST /listings (type: trade_only) API->>System: Create listing System-->>API: Listing created API-->>Owner: Success Applicant->>API: GET /listings (type: trade_only) API-->>Applicant: Available listings Applicant->>API: POST /applications Note right of Applicant: Includes offered_shift_id API->>System: Create application System->>Owner: Notification: New application System-->>API: Application created API-->>Applicant: Success Owner->>API: GET /applications (pending) API-->>Owner: Pending applications Owner->>API: POST /applications/{id}/accept API->>System: Swap shift assignments System->>System: Update both shifts System->>System: Update listing (filled) System-->>API: Swap successful API-->>Owner: Success System->>Owner: Notification: Trade completed System->>Applicant: Notification: Application accepted

Direct Offer Workflow

sequenceDiagram participant Sender as Shift Owner participant API as Marketplace API participant Recipient as Recipient participant System as System Sender->>API: POST /direct_offers API->>System: Validate & create offer System->>Recipient: Notification: Offer received System-->>API: Offer created API-->>Sender: Success Recipient->>API: GET /direct_offers (filter: received) API-->>Recipient: Pending offers Recipient->>API: POST /direct_offers/{id}/accept API->>System: Transfer shift assignment System->>System: Update assignments System->>System: Update shift coverage System-->>API: Transfer successful API-->>Recipient: Success System->>Sender: Notification: Offer accepted System->>Recipient: Notification: Assignment confirmed

πŸ’‘ Code Examples

JavaScript/Node.js - Complete Integration

class ShiftMarketplaceClient {
  constructor(apiToken, baseUrl) {
    this.token = apiToken;
    this.baseUrl = baseUrl || 'https://your-domain.com/api/v1';
  }

  // Listings
  async getListings(params = {}) {
    return this.request('GET', '/shift_marketplace/listings', params);
  }

  async createListing(listingData) {
    return this.request('POST', '/shift_marketplace/listings', { listing: listingData });
  }

  async claimListing(listingId) {
    return this.request('POST', `/shift_marketplace/listings/${listingId}/claim`);
  }

  // Applications
  async getApplications(params = {}) {
    return this.request('GET', '/shift_marketplace/applications', params);
  }

  async submitApplication(applicationData) {
    return this.request('POST', '/shift_marketplace/applications', { application: applicationData });
  }

  async acceptApplication(applicationId) {
    return this.request('POST', `/shift_marketplace/applications/${applicationId}/accept`);
  }

  // Direct Offers
  async getDirectOffers(params = {}) {
    return this.request('GET', '/shift_marketplace/direct_offers', params);
  }

  async sendDirectOffer(offerData) {
    return this.request('POST', '/shift_marketplace/direct_offers', { direct_offer: offerData });
  }

  async acceptDirectOffer(offerId) {
    return this.request('POST', `/shift_marketplace/direct_offers/${offerId}/accept`);
  }

  // Helper method
  async request(method, endpoint, data = null) {
    const url = new URL(this.baseUrl + endpoint);
    
    const options = {
      method,
      headers: {
        'Authorization': `Bearer ${this.token}`,
        'Content-Type': 'application/json'
      }
    };

    if (method === 'GET' && data) {
      Object.keys(data).forEach(key => url.searchParams.append(key, data[key]));
    } else if (data) {
      options.body = JSON.stringify(data);
    }

    const response = await fetch(url.toString(), options);
    const result = await response.json();

    if (!response.ok) {
      throw new Error(result.error?.message || 'API request failed');
    }

    return result;
  }
}

// Usage Example
const client = new ShiftMarketplaceClient('your-api-token');

// Create a pickup listing
const listing = await client.createListing({
  shift_id: 456,
  listing_type: 'pickup',
  price: 25.00,
  notes: 'Need coverage for tonight'
});

// Claim a listing
const claimed = await client.claimListing(123);

// Send a direct offer
const offer = await client.sendDirectOffer({
  shift_id: 789,
  to_user_id: 2,
  notes: 'Can you take this?',
  expires_at: '2025-11-28T10:00:00Z'
});

Python - Complete Integration

import requests
from typing import Dict, List, Optional
from datetime import datetime

class ShiftMarketplaceClient:
    def __init__(self, api_token: str, base_url: str = None):
        self.token = api_token
        self.base_url = base_url or 'https://your-domain.com/api/v1'
        self.headers = {
            'Authorization': f'Bearer {api_token}',
            'Content-Type': 'application/json'
        }

    # Listings
    def get_listings(self, **params) -> Dict:
        """Get marketplace listings with optional filters"""
        return self._request('GET', '/shift_marketplace/listings', params=params)

    def create_listing(self, shift_id: int, listing_type: str, 
                      price: float = None, notes: str = None) -> Dict:
        """Create a new marketplace listing"""
        data = {
            'listing': {
                'shift_id': shift_id,
                'listing_type': listing_type,
                'price': price,
                'notes': notes
            }
        }
        return self._request('POST', '/shift_marketplace/listings', json=data)

    def claim_listing(self, listing_id: int) -> Dict:
        """Claim a pickup listing"""
        return self._request('POST', f'/shift_marketplace/listings/{listing_id}/claim')

    # Applications
    def get_applications(self, status: str = None) -> Dict:
        """Get trade applications"""
        params = {'status': status} if status else {}
        return self._request('GET', '/shift_marketplace/applications', params=params)

    def submit_application(self, listing_id: int, offered_shift_id: int, 
                          notes: str = None) -> Dict:
        """Submit a trade application"""
        data = {
            'application': {
                'listing_id': listing_id,
                'offered_shift_id': offered_shift_id,
                'notes': notes
            }
        }
        return self._request('POST', '/shift_marketplace/applications', json=data)

    def accept_application(self, application_id: int) -> Dict:
        """Accept a trade application"""
        return self._request('POST', f'/shift_marketplace/applications/{application_id}/accept')

    def reject_application(self, application_id: int) -> Dict:
        """Reject a trade application"""
        return self._request('POST', f'/shift_marketplace/applications/{application_id}/reject')

    # Direct Offers
    def get_direct_offers(self, filter_type: str = None, status: str = None) -> Dict:
        """Get direct offers"""
        params = {}
        if filter_type:
            params['filter_type'] = filter_type
        if status:
            params['status'] = status
        return self._request('GET', '/shift_marketplace/direct_offers', params=params)

    def send_direct_offer(self, shift_id: int, to_user_id: int, 
                         notes: str = None, expires_at: str = None) -> Dict:
        """Send a direct offer to another user"""
        data = {
            'direct_offer': {
                'shift_id': shift_id,
                'to_user_id': to_user_id,
                'notes': notes,
                'expires_at': expires_at
            }
        }
        return self._request('POST', '/shift_marketplace/direct_offers', json=data)

    def accept_direct_offer(self, offer_id: int) -> Dict:
        """Accept a direct offer"""
        return self._request('POST', f'/shift_marketplace/direct_offers/{offer_id}/accept')

    def decline_direct_offer(self, offer_id: int) -> Dict:
        """Decline a direct offer"""
        return self._request('POST', f'/shift_marketplace/direct_offers/{offer_id}/decline')

    # Helper method
    def _request(self, method: str, endpoint: str, 
                params: Dict = None, json: Dict = None) -> Dict:
        """Make API request"""
        url = self.base_url + endpoint
        response = requests.request(
            method, url, 
            headers=self.headers,
            params=params,
            json=json
        )
        response.raise_for_status()
        return response.json()

# Usage Example
client = ShiftMarketplaceClient('your-api-token')

# Get all open pickup listings
listings = client.get_listings(listing_type='pickup', status='open')
print(f"Found {len(listings['data']['items'])} open listings")

# Create a trade-only listing
listing = client.create_listing(
    shift_id=456,
    listing_type='trade_only',
    notes='Looking to trade for Tuesday shift'
)

# Get received direct offers
offers = client.get_direct_offers(filter_type='received', status='pending')
print(f"You have {len(offers['data']['items'])} pending offers")

πŸ§ͺ Testing & Development

Swagger UI

Interactive API testing available at:

  • Development: http://localhost:3001/api-docs
  • Production: https://your-domain.com/api-docs

Testing Checklist

Listings Testing

  • Create pickup listing
  • Create trade-only listing
  • Create both-type listing
  • Filter by listing_type
  • Filter by status
  • Claim pickup listing
  • Update listing details
  • Delete listing

Applications Testing

  • Submit trade application
  • List sent applications
  • List received applications
  • Accept application (listing owner)
  • Reject application (listing owner)
  • Withdraw application (applicant)

Direct Offers Testing

  • Send direct offer
  • List sent offers
  • List received offers
  • Accept offer (recipient)
  • Decline offer (recipient)
  • Verify expiration handling

πŸ“š Best Practices

Request Optimization

  1. Use pagination for large result sets
  2. Filter aggressively to reduce response size
  3. Cache responses when appropriate (use ETag headers)
  4. Batch read operations when possible

Permission Management

  1. Always check permission flags before showing UI actions
  2. Handle 403 errors gracefully with helpful messages
  3. Refresh permissions after state changes

Real-Time Updates

  1. Poll for updates on active screens (every 30-60 seconds)
  2. Use webhooks for server-to-server integrations
  3. Implement optimistic UI updates for better UX

Error Recovery

  1. Implement retry logic with exponential backoff
  2. Show contextual error messages to users
  3. Log errors with request_id for debugging

πŸ”§ Troubleshooting

Common Issues

β€œMarketplace disabled” Error

Problem: 403 with marketplace_disabled code
Solutions:

  • Verify Shift Marketplace app is enabled for the business
  • Check user has access to the marketplace feature
  • Ensure proper business context in API calls

β€œInvalid action” Error

Problem: 422 with invalid_action code
Solutions:

  • Check current status of listing/offer/application
  • Verify user has permission for the action
  • Review permission flags in response

Cannot Claim Listing

Problem: Claim fails with validation error
Solutions:

  • Verify listing is pickup or both type
  • Check listing status is open
  • Ensure user is not the listing owner

Application Rejected

Problem: Application submission fails
Solutions:

  • Verify listing accepts applications (trade_only or both)
  • Check no duplicate pending application exists
  • Ensure offered_shift_id is valid and user owns it

πŸ“– Additional Resources

Support

  • Technical Support: Contact your administrator
  • Bug Reports: Submit via Service Desk
  • Feature Requests: Marketplace feedback form

This API documentation is maintained by the MangoApps development team. Last updated: November 2025. For the latest API changes, refer to the Swagger documentation.