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:
- Listings API - Post shifts for pickup or trade
- Applications API - Apply for trades and manage applications
- 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
- Navigate to Account Settings β API Tokens
- Click Create New Token
- Select appropriate scopes (e.g.,
shift_marketplace:read,shift_marketplace:write) - Copy the token (shown only once)
- 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
π οΈ 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 requiredtrade_only- Requires shift trade applicationboth- 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:
- Swap shift assignments between both users
- Update listing status to
filled - 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:
- Transfer shift assignment from sender to recipient
- Update offer status to
accepted - Mark shift as covered
- 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
Trade Application Workflow
Direct Offer Workflow
π‘ 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
- Use pagination for large result sets
- Filter aggressively to reduce response size
- Cache responses when appropriate (use ETag headers)
- Batch read operations when possible
Permission Management
- Always check permission flags before showing UI actions
- Handle 403 errors gracefully with helpful messages
- Refresh permissions after state changes
Real-Time Updates
- Poll for updates on active screens (every 30-60 seconds)
- Use webhooks for server-to-server integrations
- Implement optimistic UI updates for better UX
Error Recovery
- Implement retry logic with exponential backoff
- Show contextual error messages to users
- 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
pickuporbothtype - 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
Related Documentation
- API Configuration Guide: Initial API setup
- API Authentication: Authentication details
- Custom API Guide: Building custom integrations
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.