Overview
The Workways Public API provides access to information about public coworking sites and meeting rooms. This API is designed for developers who want to integrate Workways public spaces data into their applications.
https://api.workways.com
Authentication
- GET endpoints (reading data): Public, no authentication required
- POST endpoints (creating/modifying data): Require API token authentication
Obtaining API Tokens
To access POST endpoints, you need an API token. Here's how to get one:
- Contact Workways: Request API access by emailing appsupport@workways.com
- Account Setup: Your account will be configured with the
api-developerorapp-adminrole - Generate Token: Log in to your Workways account and navigate to Settings → API Developer Settings
- Create Token: Click "Create Token", give it a descriptive name, and copy the generated token
- Store Securely: Save the token in a secure location (e.g., environment variables). The token is only shown once and cannot be retrieved later.
- Format:
ww_dev_<64-character-hex-string> - Security: Tokens are stored as SHA-256 hashes and never exposed in plain text after creation
- Management: Create multiple tokens, track last usage, revoke tokens anytime
- Optional Quotas: Daily and monthly API usage limits can be configured per user
Token Management
Token Regeneration
If your token is compromised or lost, you can regenerate it while keeping the same token ID and tracking history:
- Log in to your Workways account
- Navigate to Settings → API Developer Settings
- Click the refresh icon next to the token you want to regenerate
- Confirm the regeneration (the old token will become immediately invalid)
- Copy and securely store the new token (shown only once)
- Keeps the same token ID for tracking purposes
- Preserves the original creation date
- Generates a new token hash and prefix
- Old token becomes invalid immediately
- New token is shown once and cannot be retrieved later
Token Deletion and Invalidation
Tokens can be removed in two ways depending on who performs the action:
- User deletes their own token: The token is permanently deleted from the database
- Admin deletes another user's token: The token is invalidated (isActive=false) but remains in the database for tracking purposes
Using API Tokens
Include your API token in the Authorization header of your requests using the Bearer
authentication scheme:
curl -X POST "https://api.workways.com/api/register" \
-H "Authorization: Bearer ww_dev_your_token_here" \
-H "Content-Type: application/json" \
-d '{...}'
Endpoints Requiring Authentication
| Endpoint | Method | Authentication Required |
|---|---|---|
/api/sites |
GET | ⚡ Optional |
/api/rooms |
GET | ⚡ Optional |
/api/countries |
GET | ❌ No |
/api/icsfeed |
GET | ❌ No |
/api/register |
POST | ✅ Yes |
/api/meeting-request |
POST | ✅ Yes |
/api/check-email |
POST | ✅ Yes |
/api/room-price |
GET | ✅ Yes |
/api/contract-options |
GET | ✅ Yes |
/api/user-info |
GET | ✅ Yes |
/api/organization-info |
GET | ✅ Yes |
/api/merge-contract |
POST | ✅ Yes |
Authentication Error Responses
Missing Authorization Header
{
"retcode": -1,
"message": "Unauthorized: Authorization header is required. Use: Authorization: Bearer <your_token>"
}
Invalid Authorization Format
{
"retcode": -1,
"message": "Unauthorized: Invalid authorization format. Use: Authorization: Bearer <your_token>"
}
Invalid or Revoked Token
{
"retcode": -1,
"message": "Unauthorized: Invalid or revoked token"
}
Inactive/Invalidated Token
{
"retcode": -1,
"message": "Votre token est expiré, connectez-vous à votre compte pour le renouveler"
}
Archived Account
{
"retcode": -1,
"message": "Votre compte est désactivé, contactez Workways"
}
Archived User Account
{
"retcode": -1,
"message": "Unauthorized: User account is archived"
}
- Never commit API tokens to version control
- Use environment variables to store tokens
- Rotate tokens periodically
- Revoke tokens immediately if compromised
- Use different tokens for different applications/environments
- Monitor token usage via the "Last Used" timestamp in your account settings
Response Format
All responses follow a standard JSON format:
{
"retcode": 1, // 1 for success, -1 for error
"message": "OK", // Status message
"data": [...] // Response data (array)
}
Country Input Flexibility
- ISO codes (case-insensitive): "FR", "fr", "BE", "ie"
- English names (case-insensitive): "France", "belgium", "IRELAND"
- French names (case-insensitive): "France", "belgique", "IRLANDE"
All formats are normalized internally to ISO codes for database queries.
Country Output Format
All responses return English country names (e.g., "Ireland", "Belgium", "France") for user-friendly display, even though ISO codes are stored internally.
Endpoints
- Without API key: Returns only public rooms (
publicOffice,publicMeeting) - With API key: Returns all rooms including private ones, with additional fields
(
actualState,otherRoomsarray)
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
country |
string | Optional | Filter sites by country. Accepts ISO codes ("IE", "BE", "FR"), English names ("Ireland", "Belgium"), or French names ("Irlande", "Belgique"). Case-insensitive. |
type |
string | Optional | Filter by room type. Values: publicOffice, publicMeeting (without auth), or
all plus any room type when authenticated. Use type=all with API key to get
all room types. |
Response Fields
| Field | Type | Description |
|---|---|---|
_id |
string | Unique site identifier |
name |
string | Site name |
country |
string | Country where the site is located (English name, e.g., "Ireland", "Belgium") |
publicRooms |
array | Array of public rooms available at this site |
publicRooms[]._id |
string | Room unique identifier |
publicRooms[].name |
string | Room internal name |
publicRooms[].publicName |
string | Room display name (falls back to name if not set) |
publicRooms[].type |
string | Room type: "publicOffice" or "publicMeeting" |
publicRooms[].surface |
number | Room surface in m² (commercial or computed), or -1 if unknown |
publicRooms[].actualState |
string | (Auth only) Current room state: "bookable", "rented", etc. |
otherRooms |
array | (Auth only) Array of non-public rooms (same structure as publicRooms) |
Example Requests
# Using English name
curl -X GET "https://apidev.workways.com/api/sites?country=Ireland"
# Using ISO code
curl -X GET "https://apidev.workways.com/api/sites?country=IE"
# Using French name
curl -X GET "https://apidev.workways.com/api/sites?country=Irlande"
# Case insensitive
curl -X GET "https://apidev.workways.com/api/sites?country=ireland"
Example Response
{
"retcode": 1,
"message": "OK",
"data": [
{
"_id": "507f1f77bcf86cd799439011",
"name": "Dublin Coworking Center",
"country": "Ireland",
"roomCount": 1,
"meetingRoomCount": 1,
"publicRooms": [
{
"_id": "507f191e810c19729de860ea",
"name": "Meeting Room A",
"publicName": "Meeting Room A",
"type": "publicMeeting",
"surface": 25
},
{
"_id": "507f191e810c19729de860eb",
"name": "Hot Desk 1",
"publicName": "Hot Desk Area",
"type": "publicOffice",
"surface": -1
}
]
}
]
}
Example Response (Authenticated with type=all)
{
"retcode": 1,
"message": "OK",
"data": [
{
"_id": "507f1f77bcf86cd799439011",
"name": "Dublin Coworking Center",
"country": "Ireland",
"roomCount": 1,
"meetingRoomCount": 1,
"publicRooms": [
{
"_id": "507f191e810c19729de860ea",
"name": "Meeting Room A",
"publicName": "Meeting Room A",
"type": "publicMeeting",
"surface": 25,
"actualState": "bookable"
}
],
"otherRooms": [
{
"_id": "507f191e810c19729de860ec",
"name": "Private Office 101",
"publicName": "Executive Suite",
"type": "privateOffice",
"surface": 45,
"actualState": "rented"
}
]
}
]
}
🚀 Try it now!
- Without API key: Returns only public rooms (
publicOffice,publicMeeting) - With API key: Returns all room types, plus organization info when applicable
(
organizationId,organizationName,contractEndDate)
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
country |
string | Optional | Filter rooms by country. Accepts ISO codes ("IE", "BE"), English names ("Ireland", "Belgium"), or French names ("Irlande", "Belgique"). Case-insensitive. |
site |
string | Optional | Filter rooms by site ID (ObjectId) |
Response Fields
| Field | Type | Description |
|---|---|---|
_id |
string | Unique room identifier |
name |
string | Room internal name |
publicName |
string | Room display name (falls back to name if not set) |
type |
string | Room type: "publicOffice", "publicMeeting", or other types when authenticated |
surface |
number | Room surface in m² (commercial or computed), or -1 if unknown |
description |
string | Room description |
actualState |
string | Current state: "bookable", "rented", "forrent", etc. |
siteId |
string | ID of the site where this room is located |
siteName |
string | Name of the site |
country |
string | Country of the site (English name, e.g., "Ireland", "Belgium") |
minPerson |
number | Minimum number of persons |
maxPerson |
number | Maximum number of persons |
nbSeat |
number | Number of seats |
configuration |
string | Room configuration type |
otherConfiguration |
string | Additional configuration details |
minDurationBooking |
number | Minimum booking duration (minutes) |
maxDurationBooking |
number | Maximum booking duration (minutes) |
stepDurationBooking |
number | Booking duration increment (minutes) |
slotWeek |
string | Weekly availability schedule |
slotSaturday |
string | Saturday availability schedule |
slotSunday |
string | Sunday availability schedule |
pricePerHour |
number | Price per hour (€). Returns 0 if no API key provided. |
pricePerHalfDay |
number | Price per half day (€). Returns 0 if no API key provided. |
pricePerDay |
number | Price per day (€). Returns 0 if no API key provided. |
pricePerMonth |
string | Monthly rental price |
image |
string (URL) | Room image URL (via /imageroom endpoint) |
organizationId |
string | (Auth only) Organization ID if room is rented |
organizationName |
string | (Auth only) Organization name if room is rented |
contractEndDate |
string | (Auth only) Contract end date in YYYY-MM-DD format (from room or organization), or empty string if not set |
Example Requests
# Filter by English country name
curl -X GET "https://apidev.workways.com/api/rooms?country=Ireland"
# Filter by ISO code
curl -X GET "https://apidev.workways.com/api/rooms?country=FR"
# Filter by specific site
curl -X GET "https://apidev.workways.com/api/rooms?site=507f1f77bcf86cd799439011"
# Combine filters (French name + site)
curl -X GET "https://apidev.workways.com/api/rooms?country=belgique&site=507f1f77bcf86cd799439011"
Example Response
{
"retcode": 1,
"message": "OK",
"data": [
{
"_id": "507f191e810c19729de860ea",
"name": "Meeting Room A",
"publicName": "Meeting Room A",
"type": "publicMeeting",
"description": "Modern meeting room with video conferencing",
"actualState": "bookable",
"surface": 25,
"siteId": "507f1f77bcf86cd799439011",
"siteName": "Dublin Coworking Center",
"country": "Ireland",
"minPerson": 2,
"maxPerson": 8,
"nbSeat": 8,
"configuration": "boardroom",
"otherConfiguration": "Video conferencing, whiteboard",
"minDurationBooking": 60,
"maxDurationBooking": 480,
"stepDurationBooking": 30,
"slotWeek": "09:00-18:00",
"slotSaturday": "",
"slotSunday": "",
"pricePerHour": 0,
"pricePerHalfDay": 0,
"pricePerDay": 0,
"pricePerMonth": "500",
"image": "https://apidev.workways.com/imageroom?id=imageId123"
}
]
}
Example Response (Authenticated - Private Room with Organization)
{
"retcode": 1,
"message": "OK",
"data": [
{
"_id": "507f191e810c19729de860ec",
"name": "Private Office 101",
"publicName": "Executive Suite",
"type": "privateOffice",
"description": "Fully equipped private office",
"actualState": "rented",
"surface": 45,
"siteId": "507f1f77bcf86cd799439011",
"siteName": "Dublin Coworking Center",
"country": "Ireland",
"minPerson": 1,
"maxPerson": 4,
"nbSeat": 4,
"configuration": "office",
"otherConfiguration": "",
"minDurationBooking": 0,
"maxDurationBooking": 0,
"stepDurationBooking": 0,
"slotWeek": "",
"slotSaturday": "",
"slotSunday": "",
"pricePerHour": 0,
"pricePerHalfDay": 0,
"pricePerDay": 0,
"pricePerMonth": "1500",
"image": "https://apidev.workways.com/imageroom?id=imageId456",
"organizationId": "507f1f77bcf86cd799439099",
"organizationName": "Acme Corp",
"contractEndDate": "2025-11-30"
}
]
}
🚀 Try it now!
Query Parameters
This endpoint does not accept any query parameters.
Response Fields
| Field | Type | Description |
|---|---|---|
retcode |
number | 1 for success, -1 for error |
message |
string | Response message ("OK" or error message) |
data |
array of strings | Alphabetically sorted list of country names (English) |
Example Request
curl -X GET "https://apidev.workways.com/api/countries"
Example Response
{
"retcode": 1,
"message": "OK",
"data": [
"Belgium",
"France",
"Ireland"
]
}
🚀 Try it now!
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
rooms |
string | Required | Comma-separated list of room IDs (MongoDB ObjectId format). Example: "507f191e810c19729de860ea,507f1f77bcf86cd799439011" |
Response Format
Returns an .ics file (iCalendar format) containing all meetings for the specified rooms.
Example Requests
# Single room
curl -X GET "https://apidev.workways.com/api/icsfeed?rooms=507f191e810c19729de860ea"
# Multiple rooms
curl -X GET "https://apidev.workways.com/api/icsfeed?rooms=507f191e810c19729de860ea,507f1f77bcf86cd799439011"
# Download to file
curl -X GET "https://apidev.workways.com/api/icsfeed?rooms=507f191e810c19729de860ea" -o calendar.ics
Example Response
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Workways//Meeting Calendar//EN
CALSCALE:GREGORIAN
BEGIN:VEVENT
UID:meeting123@workways.com
DTSTART:20251030T140000Z
DTEND:20251030T150000Z
SUMMARY:Team Meeting
LOCATION:Meeting Room A
DESCRIPTION:Weekly team sync
END:VEVENT
END:VCALENDAR
Calendar Integration
- Google Calendar: Go to Settings → Add calendar → From URL → Paste the ICS feed URL
- Outlook: File → Account Settings → Internet Calendars → New → Paste URL
- Apple Calendar: File → New Calendar Subscription → Paste URL
- Direct download: Open the URL in a browser to download the .ics file
Error Response
{
"retcode": -1,
"message": "Bad request: \"rooms\" query parameter is missing, empty, or contains invalid IDs."
}
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Required | Company name |
firstName |
string | Required | Contact first name |
lastName |
string | Optional | Contact last name |
email |
string | Required | Contact email (used for billing and notifications) |
phone |
string | Required | Contact phone number (with country code, e.g., +33612345678) |
country |
string | Optional | Country code: "fr" (France), "be" (Belgium), or "ie" (Ireland). If not provided, derived from the site. |
maxResidentUsers |
number | Required | Number of users with site access (minimum: 1) |
companyNumber |
string | Optional | SIRET (France), KBO (Belgium), or VAT number (Ireland) |
address |
string | Optional | Company billing address |
vatNumber |
string | Optional | VAT number (if applicable) |
language |
string | Optional | Preferred language: "fr" or "en" (default: "en", or "fr" if site country is France) |
siteId |
string | Optional | ID of the site to link with this registration (MongoDB ObjectId). Highest priority for site determination. |
roomId |
string | Optional | ID of a room of interest (MongoDB ObjectId). Used to determine the site if siteId is not provided. |
meetingId |
string | Optional | ID of a pending meeting request to link with this registration (MongoDB ObjectId). Used to determine the site if neither siteId nor roomId are provided. |
- At least one of
siteId,roomId, ormeetingIdmust be provided - Priority order:
siteId→roomId→meetingId - If multiple parameters point to different sites, all sites are added to the organization's
memberOfarray in priority order - The
countryis derived from the highest priority site if not explicitly provided
Example Request
curl -X POST "https://apidev.workways.com/api/register" \
-H "Authorization: Bearer ww_dev_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Corp",
"firstName": "Jean",
"lastName": "Dupont",
"email": "jean.dupont@acme.com",
"phone": "+33612345678",
"maxResidentUsers": 5,
"companyNumber": "12345678901234",
"address": "123 rue de la Paix, 75001 Paris, France",
"vatNumber": "FR12345678901",
"language": "fr",
"siteId": "507f1f77bcf86cd799439011",
"roomId": "507f191e810c19729de860ea"
}'
Response Scenarios
The response depends on whether the user/organization already exists:
Scenario 1: New Organization Created (CREATED)
{
"retcode": 200,
"status": "CREATED",
"message": "Organization registered successfully. A verification email has been sent.",
"data": {
"userId": "507f1f77bcf86cd799439011",
"userEmail": "jean.dupont@acme.com",
"userRole": "principal-admin",
"userCreated": true,
"organizationId": "507f191e810c19729de860ea",
"organizationName": "Acme Corp",
"organizationCreated": true,
"meetingsUpdated": 1,
"roomOfInterest": {
"_id": "507f1f77bcf86cd799439022",
"name": "Office Premium",
"siteName": "Workways Paris",
"siteId": "507f1f77bcf86cd799439033"
},
"identityVerification": {
"source": "insee_siret",
"confidence": 100,
"verifiedName": "ACME CORPORATION",
"verifiedNumber": "12345678901234"
}
}
}
Scenario 2: Existing Organization Updated (UPDATED)
{
"retcode": 200,
"status": "UPDATED",
"message": "Registration successful. Your organization has been updated.",
"data": {
"userId": "507f1f77bcf86cd799439011",
"userEmail": "jean.dupont@acme.com",
"userRole": "principal-admin",
"userCreated": false,
"organizationId": "507f191e810c19729de860ea",
"organizationName": "Acme Corp",
"organizationCreated": false,
"meetingsUpdated": 0,
"roomOfInterest": null,
"identityVerification": null
}
}
Scenario 3: User Already Exists with Organization (USER_EXISTS)
{
"retcode": 200,
"status": "USER_EXISTS",
"message": "User already registered with an organization.",
"data": {
"userId": "507f1f77bcf86cd799439011",
"organizationId": "507f191e810c19729de860ea"
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
retcode |
number | 200 = success, -1 = error |
status |
string | CREATED, UPDATED, or USER_EXISTS |
message |
string | Human-readable status message |
data.organizationId |
string | Always present. The organization ID (existing or newly created). Use this to link further operations. |
data.userId |
string | The user ID (existing or newly created) |
data.userEmail |
string | The user's email address (CREATED/UPDATED only) |
data.userRole |
string | The user's role (typically "principal-admin") (CREATED/UPDATED only) |
data.userCreated |
boolean | True if a new user was created, false if existing user was updated (CREATED/UPDATED only) |
data.organizationName |
string | The organization's name (CREATED/UPDATED only) |
data.organizationCreated |
boolean | True if a new organization was created, false if existing one was used (CREATED/UPDATED only) |
data.meetingsUpdated |
number | Number of pending meeting requests linked to the organization (CREATED/UPDATED only) |
data.roomOfInterest |
object|null | Information about the room if roomId was provided (CREATED/UPDATED only) |
data.identityVerification |
object|null | Company identity verification result if companyNumber was provided (CREATED/UPDATED only) |
organizationId field is always returned in all
success scenarios (CREATED, UPDATED, USER_EXISTS). You should store this ID to reference the organization
in subsequent API calls or for your own records.
Process Flow
- You receive a confirmation email with a validation link
- Click the link to verify your email and set your password
- Your company number (SIRET/KBO/VAT) is verified automatically (if provided)
- Once validated, you can access the Workways platform
- Your access cards are activated
Important Notes
- Existing users: Bookings are confirmed immediately (status: ACCEPTED)
- New users: Must complete registration first (status: REQUEST_ACCEPTED)
- Minimum advance for new users: Bookings must be at least 2 days in advance
- Existing users: Can book same-day (but not in the past)
- Archived accounts: Cannot book (status: ARCHIVED)
- Public rooms only: Only rooms with type "publicOffice" or "publicMeeting" can be booked
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
contactEmail |
string | Required | Contact email for this booking |
contactName |
string | Optional | Contact name |
bookedRoom |
string | Required | Room ID (MongoDB ObjectId) |
bookedAt |
string | Required | Start date/time (ISO 8601 format) |
duration |
number | Required | Duration in minutes (minimum: 30) |
bookedUser |
number | Optional | Number of attendees (default: 1) |
name |
string | Optional | Meeting subject/title |
description |
string | Optional | Meeting description or notes |
language |
string | Optional | Preferred language for user: "fr" or "en" (default: "en", or "fr" if site country is France). Only used when creating a new temp user. |
Example Request
curl -X POST "https://apidev.workways.com/api/meeting-request" \
-H "Authorization: Bearer ww_dev_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"contactEmail": "jean.dupont@acme.com",
"contactName": "Jean Dupont",
"bookedRoom": "507f191e810c19729de860ea",
"bookedAt": "2025-11-05T14:00:00Z",
"duration": 60,
"bookedUser": 4,
"name": "Quarterly Review",
"description": "Q4 2025 team meeting"
}'
Response Scenarios
Scenario 1: Existing User - Booking Accepted
{
"retcode": 200,
"status": "ACCEPTED",
"message": "Your booking has been confirmed.",
"data": {
"meetingId": "507f191e810c19729de860ea",
"roomName": "Meeting Room A",
"bookedDate": "2025-11-05",
"bookedTime": "14:00-15:00",
"duration": 60
}
}
Scenario 2: New User - Registration Required
{
"retcode": 200,
"status": "REQUEST_ACCEPTED",
"message": "Your meeting request has been received. Please complete your registration to confirm the booking.",
"data": {
"meetingRequestId": "507f191e810c19729de860ea",
"userId": "507f191e810c19729de860eb",
"roomName": "Meeting Room A",
"requestedDate": "2025-11-05",
"requestedTime": "14:00-15:00",
"duration": 60
}
}
REQUEST_ACCEPTED response,
save the meetingRequestId. You can then pass it as the meetingId parameter when
calling /api/register to automatically link the meeting to the new organization once registration
is complete.
Scenario 3: Archived User Account
{
"retcode": 200,
"status": "ARCHIVED",
"message": "Your account is no longer valid. Please contact an administrator to restore it.",
"data": null
}
Error Response Examples
// New user: same-day or past date booking attempt
{
"retcode": -1,
"message": "Cannot book meetings for today or past dates"
}
// New user: insufficient advance notice
{
"retcode": -1,
"message": "Meetings must be booked at least 2 days in advance"
}
// Existing user: past date booking attempt
{
"retcode": -1,
"message": "Cannot book meetings for past dates"
}
// Room not public
{
"retcode": -1,
"message": "This room is not available for public booking"
}
// Invalid room ID
{
"retcode": -1,
"message": "Room not found"
}
Description: Calculate the booking price for a room based on room type, booking parameters, and room state.
Authorization: Bearer ww_dev_your_token_here
Supported Room Types
publicMeeting- No state restrictionsmeeting,privateMeeting,sharedMeeting,conference- RequiresdefaultStateto bebookableoroccupied
publicOffice- No state restrictionsoffice,privateOffice,sharedOffice- RequiresdefaultStateto berentedorforrent
Query Parameters
For Meeting Rooms:
| Parameter | Type | Required | Description |
|---|---|---|---|
roomId |
string | ✅ Yes | MongoDB ObjectId of the room |
startDate |
string | ⚠️ Optional | Start date/time in flexible format (e.g., "2025-01-18T10:00:00") |
duration |
number | ❌ No | Duration in minutes (default: 60) |
nbPersons |
number | ❌ No | Number of persons (default: 2) |
For Office Rooms:
| Parameter | Type | Required | Description |
|---|---|---|---|
roomId |
string | ✅ Yes | MongoDB ObjectId of the room |
period |
string | ❌ No | Rental period: "week" or "month" (default: "month") |
Example Requests
Meeting Room Price
curl -H "Authorization: Bearer ww_dev_your_token_here" \
"https://api.workways.com/api/room-price?roomId=507f1f77bcf86cd799439011&startDate=2025-01-18T10:00:00&duration=120&nbPersons=4"
Office Room Price
curl -H "Authorization: Bearer ww_dev_your_token_here" \
"https://api.workways.com/api/room-price?roomId=507f1f77bcf86cd799439011&period=month"
Success Response Examples
Meeting Room - Price Available
{
"retcode": 1,
"message": "OK",
"data": {
"roomId": "507f1f77bcf86cd799439011",
"roomType": "privateMeeting",
"defaultState": "bookable",
"actualState": "free",
"startDate": "2025-01-18T10:00:00",
"duration": 120,
"nbPersons": 4,
"price": 100
}
}
Office Room - Price Available
{
"retcode": 1,
"message": "OK",
"data": {
"roomId": "507f1f77bcf86cd799439011",
"roomType": "privateOffice",
"defaultState": "rented",
"actualState": "rented",
"period": "month",
"price": 100
}
}
Room State Not Supported - Price Unavailable
{
"retcode": -1,
"message": "Price calculation not available for this room state/type",
"data": {
"roomId": "507f1f77bcf86cd799439011",
"roomType": "meeting",
"defaultState": "toclean",
"actualState": "toclean",
"price": -1
}
}
price is -1, the price calculation is not
available for the room's current state or type. The endpoint still returns 200 OK to allow
graceful handling on the client side.
Error Response Examples
// Missing roomId
{
"retcode": -1,
"message": "Missing required parameter: roomId"
}
// Invalid roomId format
{
"retcode": -1,
"message": "Invalid roomId format"
}
// Room not found
{
"retcode": -1,
"message": "Room not found"
}
// Unsupported room type
{
"retcode": -1,
"message": "Price calculation not supported for room type: privateAccess"
}
// Invalid period (for offices)
{
"retcode": -1,
"message": "Invalid period: must be \"week\" or \"month\""
}
// Invalid duration (for meetings)
{
"retcode": -1,
"message": "Invalid duration: must be a positive number"
}
// Unauthorized (no token)
{
"retcode": -1,
"message": "Unauthorized: Authorization header is required. Use: Authorization: Bearer "
}
Response Fields
| Field | Type | Description |
|---|---|---|
retcode |
number | 1 = success, -1 = error or price unavailable |
message |
string | Human-readable status message |
data.roomId |
string | ID of the room |
data.roomType |
string | Type of the room (e.g., "publicMeeting", "privateOffice") |
data.defaultState |
string | Room's default state (e.g., "bookable", "rented") |
data.actualState |
string | Room's current state (e.g., "free", "occupied") |
data.price |
number | Calculated price, or -1 if unavailable |
data.startDate |
string | (Meetings only) Start date/time if provided |
data.duration |
number | (Meetings only) Duration in minutes |
data.nbPersons |
number | (Meetings only) Number of persons |
data.period |
string | (Offices only) Rental period ("week" or "month") |
GET /api/contract-options 🔐 Auth Required
Returns available contract options (additional services) for a specific site. Options are based on Rate records and can include parking, equipment rentals, monthly services, etc.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
site |
string | Yes | Site ID to filter available options |
Example Request
curl -H "Authorization: Bearer ww_dev_your_token_here" \
"https://api.workways.com/api/contract-options?site=507f1f77bcf86cd799439011"
Success Response Example
{
"retcode": 1,
"message": "OK",
"data": [
{
"_id": "507f1f77bcf86cd799439011",
"key": "parking",
"name": "Parking space",
"nameFr": "Place de Parking",
"nameEn": "Parking Space",
"type": "other",
"isUnique": true,
"isGeneric": false,
"supportsQuantity": false,
"pricing": {
"publicBase": 150,
"memberBase": 135,
"publicPromo": null,
"memberPromo": null,
"promoMonthDuration": null,
"setupFees": 50
},
"vat": {
"type": "standard",
"rate": null
}
},
{
"_id": "507f1f77bcf86cd799439022",
"key": "tv_rental",
"name": "TV Rental",
"nameFr": "Location TV",
"nameEn": "TV Rental",
"type": "quantity",
"isUnique": false,
"isGeneric": true,
"supportsQuantity": true,
"pricing": {
"publicBase": 30,
"memberBase": 27,
"publicPromo": null,
"memberPromo": null,
"promoMonthDuration": null,
"setupFees": 0
},
"vat": {
"type": "standard",
"rate": null
}
},
{
"_id": "507f1f77bcf86cd799439033",
"key": "service_by_month",
"name": "Monthly Service",
"nameFr": "Service mensuel",
"nameEn": "Monthly Service",
"type": "promo",
"isUnique": false,
"isGeneric": true,
"supportsQuantity": false,
"pricing": {
"publicBase": 200,
"memberBase": 180,
"publicPromo": 150,
"memberPromo": 135,
"promoMonthDuration": 3,
"setupFees": 100
},
"vat": {
"type": "special",
"rate": 10
}
}
]
}
Option Types
| Category | Keys | Description |
|---|---|---|
| Unique (one per organization) | parking, bike_garage, meeting_room_package,
food_service, garbage_service, cleaning_service,
private_network, private_wifi |
Can only be subscribed once per organization |
| Generic (multiple allowed) | tv_rental, fridge_rental, phonebooth_rental,
appliance_rental, furniture_rental, service_by_month,
service_by_month_prepaid |
Can be subscribed multiple times per organization |
Rate Types
| Type | Description | Features |
|---|---|---|
other |
Standard service | Fixed pricing |
quote |
Quote-based service | Price determined on request |
quantity |
Quantity-based service | Price multiplied by quantity |
promo |
Promotional pricing | Discounted price for initial period |
promoWithQuantity |
Promotional + quantity | Both promotional pricing and quantity support |
Error Response Examples
// Missing site parameter
{
"retcode": -1,
"message": "Missing required parameter: site"
}
// Invalid site ID format
{
"retcode": -1,
"message": "Invalid site ID format"
}
// Site not found
{
"retcode": -1,
"message": "Site not found"
}
// Unauthorized (no token)
{
"retcode": -1,
"message": "Unauthorized: Authorization header is required. Use: Authorization: Bearer <your_token>"
}
Response Fields
| Field | Type | Description |
|---|---|---|
_id |
string | Unique rate ID |
key |
string | Option identifier key |
name |
string | Technical name |
nameFr |
string | Translated name in French |
nameEn |
string | Translated name in English |
type |
string | Rate type (other, quote, quantity, promo, promoWithQuantity) |
isUnique |
boolean | True if only one subscription allowed per organization |
isGeneric |
boolean | True if multiple subscriptions allowed |
supportsQuantity |
boolean | True if the option supports quantity (type = quantity or promoWithQuantity) |
pricing.publicBase |
number | Public base price (monthly) |
pricing.memberBase |
number | Member base price (monthly) |
pricing.publicPromo |
number|null | Public promotional price (if applicable) |
pricing.memberPromo |
number|null | Member promotional price (if applicable) |
pricing.promoMonthDuration |
number|null | Promotional period duration in months |
pricing.setupFees |
number | One-time setup/installation fees |
vat.type |
string | VAT type: "standard", "none", or "special" |
vat.rate |
number|null | Custom VAT rate (only if type = "special") |
- Member vs Public: Organizations with member status use
memberBase/memberPromoprices; others usepublicBase/publicPromo - Promotional Period: For promo types, the promo price applies for the first N months (defined by
promoMonthDuration), then switches to base price - Quantity: For quantity-based options, the total price = unit price × quantity
GET /api/user-info 🔐 Auth Required
Returns user information by ID or email address. Useful for retrieving user details and their associated organization.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
string | One of id/email required | User ID (MongoDB ObjectId). Takes priority if both parameters are provided. |
email |
string | One of id/email required | User email address (case insensitive) |
Example Requests
By User ID
curl -H "Authorization: Bearer ww_dev_your_token_here" \
"https://api.workways.com/api/user-info?id=507f1f77bcf86cd799439011"
By Email
curl -H "Authorization: Bearer ww_dev_your_token_here" \
"https://api.workways.com/api/user-info?email=jean.dupont@acme.com"
Success Response Example
{
"retcode": 1,
"message": "OK",
"data": {
"_id": "507f1f77bcf86cd799439011",
"firstName": "Jean",
"lastName": "Dupont",
"email": "jean.dupont@acme.com",
"role": "principal-admin",
"isUserArchived": false,
"organizationId": "507f191e810c19729de860ea",
"organizationName": "Acme Corp",
"isOrganizationArchived": false
}
}
User Without Organization
{
"retcode": 1,
"message": "OK",
"data": {
"_id": "507f1f77bcf86cd799439011",
"firstName": "Marie",
"lastName": "Martin",
"email": "marie.martin@example.com",
"role": "temp",
"isUserArchived": false,
"organizationId": null,
"organizationName": null,
"isOrganizationArchived": null
}
}
Error Response Examples
// Missing parameter
{
"retcode": -1,
"message": "Missing required parameter: id or email"
}
// Invalid user ID format
{
"retcode": -1,
"message": "Invalid user ID format"
}
// Invalid email format
{
"retcode": -1,
"message": "Invalid email format"
}
// User not found
{
"retcode": -1,
"message": "User not found"
}
// Unauthorized (no token)
{
"retcode": -1,
"message": "Unauthorized: Authorization header is required. Use: Authorization: Bearer <your_token>"
}
Response Fields
| Field | Type | Description |
|---|---|---|
_id |
string | Unique user ID |
firstName |
string | User's first name |
lastName |
string | User's last name (may be empty) |
email |
string | User's email address |
role |
string | User's role (e.g., "principal-admin", "admin", "user", "temp") |
isUserArchived |
boolean | True if the user account is archived |
organizationId |
string|null | ID of the user's organization (null if not associated) |
organizationName |
string|null | Name of the user's organization (null if not associated) |
isOrganizationArchived |
boolean|null | True if the organization is archived (null if no organization) |
User Roles
| Role | Description |
|---|---|
principal-admin |
Primary administrator of an organization (billing contact) |
admin |
Administrator with management privileges |
user |
Standard user with access privileges |
temp |
Temporary user (pending registration completion) |
GET /api/organization-info 🔐 Auth Required
Returns detailed organization information including contract details, allocated rooms, accessible sites, contract options, and monthly billing amount.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Organization ID (MongoDB ObjectId) |
Example Request
curl -H "Authorization: Bearer ww_dev_your_token_here" \
"https://api.workways.com/api/organization-info?id=507f1f77bcf86cd799439011"
Success Response Example
{
"retcode": 1,
"message": "OK",
"data": {
"_id": "507f1f77bcf86cd799439011",
"name": "Acme Corp",
"isArchived": false,
"contractEndDate": "2025-12-31",
"renewalMonths": 12,
"allocatedRooms": [
{
"_id": "507f191e810c19729de860ea",
"name": "Bureau Premium A12",
"contractEndDate": "2025-12-31",
"price": 850,
"siteId": "507f1f77bcf86cd799439022"
},
{
"_id": "507f191e810c19729de860eb",
"name": "Bureau Standard B05",
"contractEndDate": "",
"price": 450,
"siteId": "507f1f77bcf86cd799439022"
}
],
"accessibleSites": [
{
"_id": "507f1f77bcf86cd799439022",
"name": "Workways Paris",
"country": "fr"
},
{
"_id": "507f1f77bcf86cd799439033",
"name": "Workways Lyon",
"country": "fr"
}
],
"basePrice": 1300,
"contractOptions": [
{
"id": 1,
"name": "Place de parking",
"key": "parking",
"quantity": 2,
"price": 150,
"pricePromo": null,
"startDate": "2024-01-01",
"endDate": ""
},
{
"id": 2,
"name": "Location TV",
"key": "tv_rental",
"quantity": 1,
"price": 30,
"pricePromo": null,
"startDate": "",
"endDate": ""
}
]
}
}
Error Response Examples
// Missing parameter
{
"retcode": -1,
"message": "Missing required parameter: id"
}
// Invalid organization ID format
{
"retcode": -1,
"message": "Invalid organization ID format"
}
// Organization not found
{
"retcode": -1,
"message": "Organization not found"
}
// Unauthorized (no token)
{
"retcode": -1,
"message": "Unauthorized: Authorization header is required. Use: Authorization: Bearer <your_token>"
}
Response Fields
| Field | Type | Description |
|---|---|---|
_id |
string | Unique organization ID |
name |
string | Organization name |
isArchived |
boolean | True if the organization is archived |
contractEndDate |
string | Contract end date (YYYY-MM-DD format, empty if no end date) |
renewalMonths |
number|null | Number of months for automatic contract renewal (null if no auto-renewal) |
basePrice |
number|null | Base monthly rental price extracted from rentalPrice field (null if not set) |
allocatedRooms |
array | List of rooms allocated to this organization |
allocatedRooms[]._id |
string | Room ID |
allocatedRooms[].name |
string | Room name |
allocatedRooms[].contractEndDate |
string | Room-specific contract end date (YYYY-MM-DD, empty if inherits from organization) |
allocatedRooms[].price |
number|null | Monthly rental price for this room |
allocatedRooms[].siteId |
string|null | ID of the site where the room is located |
accessibleSites |
array | List of sites the organization has access to |
accessibleSites[]._id |
string | Site ID |
accessibleSites[].name |
string | Site name |
accessibleSites[].country |
string | Country code (e.g., "fr", "be", "ie") |
contractOptions |
array | List of subscribed contract options |
contractOptions[].id |
number | Option ID (unique within the organization) |
contractOptions[].name |
string | Option display name |
contractOptions[].key |
string | Option key (e.g., "parking", "tv_rental") |
contractOptions[].quantity |
number | Quantity subscribed |
contractOptions[].price |
number|null | Custom price (null = uses default rate price) |
contractOptions[].pricePromo |
number|null | Custom promotional price (null = uses default rate promo) |
contractOptions[].startDate |
string | Option start date (YYYY-MM-DD, empty = inherits from organization) |
contractOptions[].endDate |
string | Option end date (YYYY-MM-DD, empty = inherits from organization) |
POST /api/merge-contract 🔐 Auth Required
Merge/update contract information for an organization. Allows updating rental details, contract options, and room allocations.
Body Parameters (JSON)
| Parameter | Type | Required | Description |
|---|---|---|---|
organizationId |
string | Yes | Organization ID (MongoDB ObjectId) |
rentalPrice |
string | No | Rental price (e.g., "1500€") |
rentalComment |
string | No | Comment about the rental contract |
rentalStartAt |
string | No | Contract start date (YYYY-MM-DD format) |
rentalEndAt |
string | No | Contract end date (YYYY-MM-DD format) |
rentalUrl |
string | No | URL to Google Drive folder containing client documents |
rentalRenewal |
number | No | Number of months for automatic renewal |
contractOptionsToAdd |
array | No | List of contract options to add (see format below) |
contractOptionsToRemove |
array | No | List of contract option IDs to remove |
contractOptions |
array | No | Replace all contract options with this list |
rooms |
array | No | Replace all room allocations with this list of room IDs |
roomsToAdd |
array | No | List of room IDs to add to the organization |
roomsToRemove |
array | No | List of room IDs to remove from the organization |
Contract Option Format
When adding contract options via contractOptionsToAdd or contractOptions, each option should have:
{
"optionModelId": "507f1f77bcf86cd799439099", // Required - Option ID from /api/contract-options response
"key": "parking", // Option key (e.g., "parking", "tv_rental")
"name": "Place de parking", // Display name
"quantity": 2, // Quantity subscribed
"price": 150, // Custom price (optional, -1 = use default rate)
"pricePromo": -1, // Custom promo price (optional, -1 = use default)
"promoMonthDuration": 3, // Number of months for promo price (optional)
"startAt": "2024-01-01", // Option start date (YYYY-MM-DD, optional)
"endAt": "" // Option end date (YYYY-MM-DD, optional, empty = inherits from org)
}
Example Request
curl -X POST \
-H "Authorization: Bearer ww_dev_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"organizationId": "507f1f77bcf86cd799439011",
"rentalPrice": "1500€",
"rentalEndAt": "2025-12-31",
"rentalRenewal": 12,
"contractOptionsToAdd": [
{
"optionModelId": "507f1f77bcf86cd799439099",
"key": "parking",
"name": "Place de parking",
"quantity": 2,
"price": 150
}
],
"roomsToAdd": ["507f191e810c19729de860ea"]
}' \
"https://api.workways.com/api/merge-contract"
Success Response Example
{
"retcode": 1,
"message": "OK",
"data": {
"organizationId": "507f1f77bcf86cd799439011",
"organizationName": "Acme Corp"
}
}
Error Response Examples
// Missing required parameter
{
"retcode": -1,
"message": "Missing required parameter: organizationId"
}
// Invalid organization ID format
{
"retcode": -1,
"message": "Invalid organizationId format"
}
// Organization not found
{
"retcode": -1,
"message": "Organization not found"
}
// Unauthorized (no token)
{
"retcode": -1,
"message": "Unauthorized: Authorization header is required. Use: Authorization: Bearer <your_token>"
}
Notes
- Incremental updates: Use
contractOptionsToAdd/contractOptionsToRemoveandroomsToAdd/roomsToRemoveto modify existing lists incrementally. - Full replacement: Use
contractOptionsorroomsto completely replace the existing lists. - Dates: All date parameters use YYYY-MM-DD format (e.g., "2025-12-31").
Interactive Demo Application
Try the Workways Public API Demo
Experience the full booking workflow with our interactive demo application. Test all API endpoints in a user-friendly interface.
🚀 Launch Demo App (English) 🚀 Lancer la démo (Français)Features: Browse sites and rooms, view availability calendars, submit booking requests, register organizations
Booking Workflows
The Workways Public API supports two main booking workflows, with different behavior based on room type and user registration status.
Workflow 1: Meeting Room Booking (publicMeeting)
Step 1: Select a Room and Time Slot
Use the /api/rooms endpoint to find available meeting rooms, then check availability using the
/api/icsfeed endpoint to view occupied time slots.
Step 2: Submit Booking Request
POST to /api/meeting-request with room ID, date/time, and contact email.
Step 3: Response Depends on User Status
Scenario A: Existing User (Email Found)- Response Status: ACCEPTED
- Booking State: Confirmed immediately
- Advance Notice: Can book same-day (but not in the past)
- Next Step: Booking is confirmed, user receives notification
- Calendar: Immediately visible in ICS feed
- Response Status: REQUEST_ACCEPTED
- Booking State: Pending registration
- Advance Notice: Minimum 2 days in advance
- Next Step: User must complete registration via
/api/register - Registration: Includes email validation and password setup
- Final Confirmation: Booking confirmed after account validation
- Response Status: ARCHIVED
- Action Required: Contact administrator to restore account
- Booking: Not allowed until account is restored
Workflow 2: Office Space Rental (publicOffice)
Step 1: Browse Office Spaces
Use the /api/rooms endpoint with type=publicOffice filter to find available office
spaces.
Step 2: Direct Registration
Office spaces require a rental contract and cannot be booked via the booking endpoint. Users are directed to the registration form immediately.
Step 3: Submit Organization Registration
POST to /api/register with company details. Include the office space context in the request
(optional field or comments).
Step 4: Manual Processing
- Registration request creates a pending organization
- User receives email validation link
- Company number (SIRET/KBO/VAT) is verified if provided
- Workways team prepares rental contract
- Once validated, access cards are activated
Note: Office rentals are monthly contracts and require manual approval, unlike meeting rooms which can be instantly booked.
User States and Permissions
| User State | Same-Day Booking | Advance Booking | Registration Required |
|---|---|---|---|
| Existing User (Active) | ✅ Allowed | ✅ No minimum | ❌ Not required |
| New User | ❌ Not allowed | ✅ Minimum 2 days | ✅ Required |
| Archived User | ❌ Not allowed | ❌ Not allowed | ⚠️ Contact admin |
Integration Best Practices
- Room Discovery: Use
/api/sitesand/api/roomsto list available spaces - Differentiate Room Types: Show different UI for
publicMeetingvspublicOffice- Meeting rooms → Calendar booking interface
- Office spaces → Registration/contact form
- Check Availability: Use
/api/icsfeedto display occupied time slots in your calendar UI - Handle Response Status: Check the
statusfield in responses:ACCEPTED→ Booking confirmed, reload calendarREQUEST_ACCEPTED→ Redirect to registration formARCHIVED→ Show error message with admin contact
- Reload Calendar Feed: After successful booking (ACCEPTED), refresh the ICS feed to show the new occupied slot
- Email Validation: Always validate email format before submission
- Date Validation: Implement client-side date validation based on user type (if known)
Error Handling
When an error occurs, the API returns a response with retcode: -1 and an appropriate error
message.
Error Response Examples
Invalid Country
{
"retcode": -1,
"message": "Invalid country: \"XYZ\". Use ISO code (e.g., \"FR\", \"BE\") or country name (e.g., \"France\", \"Belgium\")",
"data": []
}
Invalid Site ID
{
"retcode": -1,
"message": "Invalid site ID format."
}
Internal Server Error
{
"retcode": -1,
"message": "Internal server error"
}
Common HTTP Status Codes
| HTTP Status | Description |
|---|---|
| 200 | Success - Request completed successfully |
| 400 | Bad Request - Invalid parameters (e.g., invalid country name, invalid site ID format) |
| 401 | Unauthorized - Missing, invalid, or revoked API token (POST endpoints only) |
| 500 | Internal Server Error - Something went wrong on the server |
Integration with Make.com
Make.com (formerly Integromat) allows you to create powerful automation workflows with Workways API without writing code.
Getting Started with Make.com
- Create a free account at Make.com
- Create a new scenario
- Use the HTTP module to call Workways API endpoints
Configuration for Public Endpoints (GET)
For public endpoints (sites, rooms, countries, icsfeed), no authentication is required:
- Method: GET
- URL: https://api.workways.com/api/sites
- Headers: None required
Configuration for Authenticated Endpoints (POST)
For POST endpoints (register, meeting-request, check-email), you must authenticate with your API token:
- Method: POST
- URL: https://api.workways.com/api/register
- Headers:
Authorization:Bearer ww_dev_your_token_hereContent-Type:application/json
- Body type: Raw (JSON)
- Request content: Your JSON data
Example: Register Organization Automation
Scenario: New form submission → Register in Workways → Send confirmation email
1. Trigger: Webhook (receives form data)
2. HTTP Module:
- Method: POST
- URL: https://api.workways.com/api/register
- Headers:
* Authorization: Bearer ww_dev_xxxxxxxxxx
* Content-Type: application/json
- Body:
{
"name": "{{trigger.companyName}}",
"firstName": "{{trigger.firstName}}",
"lastName": "{{trigger.lastName}}",
"email": "{{trigger.email}}",
"phone": "{{trigger.phone}}",
"country": "{{trigger.country}}",
"maxResidentUsers": {{trigger.users}}
}
3. Email Module: Send confirmation with {{response.data.requestId}}
Example: Check Room Availability
Scenario: Daily report of available rooms
1. Schedule: Every day at 8:00 AM
2. HTTP Module (Get Sites):
- Method: GET
- URL: https://api.workways.com/api/sites?country=France
3. HTTP Module (Get Rooms):
- Method: GET
- URL: https://api.workways.com/api/rooms?site={{site._id}}
4. Google Sheets: Update availability report
- Store your API token in Make.com's Data Store for security
- Use Error Handler to catch 401 errors (invalid/expired tokens)
- Add Sleep modules between requests to avoid rate limits
- Use Iterator to process multiple sites/rooms in parallel
- Enable Data Store to cache API responses and reduce API calls
Use Cases
- Build a public website showing available coworking spaces
- Create a mobile app for finding meeting rooms
- Integrate with Make.com or Zapier for automation
- Export data to Google Sheets or Airtable
- Build analytics dashboards for space availability
Rate Limits
Currently, there are no enforced rate limits on these public endpoints. However, please be respectful and avoid excessive requests.
Support
If you have questions or need support, please contact:
- Email: appsupport@workways.com
- Make.com Integration: Visit Make.com to create automation scenarios with Workways data