UCL Workspaces API
Fetch workspace availablility and maps throughout UCL
Fetch workspace availablility and maps throughout UCL
openapi: 3.0.0
info:
title: UCL Analytics Workspaces API
description: "An API generated by a team of student developers to interact with student and university data at UCL.\n\nThe API is made up of several services, each of which will be separately explained. Every service will be documented here with important information, tips and examples.\n\n## Get Your API Key\n\nBefore you can use the API you should head to the API Dashboard and sign up using your UCL user account. Once logged into the dashboard simply name your app and you’ll be given a key that you can use to access all the services. Simple!\n\n## API Rate Limits\n\nRate limiting of the API is primarily on a per-user basis. The limit is calculated against the user, across all their access tokens.\n\nThe limit is 10,000 requests per day and resets every day at midnight, London time.\n\nWhen a request is throttled, the response returned by the server has HTTP Status Code “429 Too Many Requests”. It includes a Retry-After header with the number of seconds the client is throttled for.\n\nIf you would like your rate limit to be increased, contact us at isd.apiteam@ucl.ac.uk\n\n## API Data Freshness\n\nMuch of the data available from the API is served from cache. Bookings and Timetable data are updated every twenty minutes from UCL, and we update the [Library Study Spaces (Workspaces) API](https://uclapi.com/docs/#workspaces) every two minutes. The `Last-Modified` header will provide the time that the most recent caching operation completed in [RFC 2616](https://stackoverflow.com/a/21121453) format. Endpoints that do not rely on cached data will return the current timestamp in this field instead.\n\nThis allows your application to judge whether the data is stale, or might need refreshing. For example, if a booking is added to the database and the data you are using is more than twenty minutes old, it may be that the booking is not visible to you yet. Consider creating a fresh request in this case.\n\nIf you notice that the `Last-Modified` timestamp you see is unreasonably old, please [get in contact with us](mailto:isd.apiteam@ucl.ac.uk) ASAP to report this as it may indicate very stale data and an issue at our end.\n\n## OAuth\n\nOAuth is a protocol that lets external apps request secure access to private UCL account data without getting your password. This can be done with a \"Sign In With UCL\" button on your website or app which saves UCL users the trouble of registering a new account with you. It also allows your app or website to retrieve a user's personal timetable.\n\nCheck out a JS web app demo [here](https://uclapi-oauth-demo.glitch.me/). The source code for the demo is available [here](https://glitch.com/edit/#!/uclapi-oauth-demo).\n\nFor an example of a mobile app implementation, check out [UCL Assistant](https://github.com/uclapi/ucl-assistant-app) (written in React Native) and the accompanying [UCL Assistant API backend](https://github.com/uclapi/ucl-assistant-api/tree/master/src/oauth) (written in Node.JS).\n\n### Sign In With UCL Button\nIf you want to add a \"Sign In With UCL\" button to your site, which looks like this:\n\n<a href=\"https://uclapi.com/oauth/authorise?client_id=CLIENT_ID&state=STATE\">\n <img width=\"20%\" src=\"https://s3.eu-west-2.amazonaws.com/uclapi-static/SignInWithUCLSmall.png\">\n</a>\n\nyou can copy the following the code below and replace CLIENT_ID and STATE by the `client_id` of your app and a random `state`:\n\n```\n<a href=\"https://uclapi.com/oauth/authorise?client_id=CLIENT_ID&state=STATE\">\n <img src=\"https://s3.eu-west-2.amazonaws.com/uclapi-static/SignInWithUCLSmall.png\">\n</a>\n```\n\n### Scopes\n\nOAuth scopes specify what access your app needs to a UCL user’s account. As an app developer, you set the desired scopes in the API Dashboard. When a user is responding to your authorisation request, the requested scopes will be displayed to the user.\n\n### OAuth Workflow\n\nIf your application uses OAuth, you must set a callback URL in the dashboard. Then the app should follow this procedure:\n\n1. Redirect the user to `https://uclapi.com/oauth/authorise` with `state` and the application’s `client_id` as query parameters.\n\n2. The user logs in with their UCL credentials on the UCL Single Sign-on website (if not logged in already).\n\n3. The user reviews the OAuth scopes requested and either authorises or denies the application's request. If the application is authorised, the callback URL receives `client_id`, `state` (specified in 1.), `result`, and `code`.\n\nIf the application is denied, the callback URL receives `result` and `state`, and no private data will be provided to the application.\n\n4. To obtain a OAuth user token (necessary for retrieving personal data and certain API endpoints), we require `code` (from 3.), `client_id`, and `client_secret`. These should then be sent to `https://uclapi.com/oauth/token`, which will return a response containing `state`, `ok`, `client_id`, `token` (OAuth user token), and `scope` (OAuth scopes the app can access on the user’s behalf).\n\n**Note**: OAuth tokens and general API tokes are different. Whilst general API tokens can be used for all non-personal, generic data (such as room bookings), OAuth tokens must be used with an app's client_secret in order to retrieve personal data for a user. To make things easier, you can use personal OAuth tokens in place of general tokens once a user has logged into your app to retrieve generic data too.\n\n### Tokens\n\nTokens uniquely identify an app that is requesting data from the API.\n\nTokens are a long string variable of numbers and letters. e.g. `uclapi-5d58c3c4e6bf9c-c2910ad3b6e054-7ef60f44f1c14f-a05147bfd17fdb`.\n\nThere are two different kinds of tokens you can work with:\n\n1. Generic Tokens: These are tokens that are used to request non-personal data. These tokens are used between applications and the API to request any sort of data that the app may need that is not tied to a specific student. For example, UCL API’s Room booking service uses tokens to return information about rooms - when they are booked and which UCL rooms are free.\n\n2. OAuth Tokens: This type of token is used when an app requires personal data from users. One of the most common uses of this type of token is when you sign in via UCL on an app. The app will then use a token to request a user’s personal data such as:\n\n- Department\n- Email\n- Full name\n- Given name\n- UPI\n- If they are a student or not\n- Student number (*note:* to get this, you need to tick the relevant scope in the dashboard before a user logs in; more on scopes above).\n\nNote that you can also use OAuth Tokens to access all the same data that generic app tokens can access.\n\nEach token is uniquely generated for each user logging into each app.\n\nPlease note, access to any of this data needs to be approved by the user first.\n\nTo use this type of token for your app, you need to redirect the user to the \"Authorise\" endpoint at: `https://uclapi.com/oauth/authorise` which can be done directly or by including a “Sign in With UCL Button” in your app, such as the one provided below, which links users to the authorisation endpoint with your app’s Client ID (accessible via the dashboard) and a random state number included in the GET parameters.\n\nThe users then sign in with their UCL credentials and, if they authorise your app to use their personal data, a token will be generated which your app can use to get user’s personal data in JSON format from the oauth/user/data. Your application will receive the token at the callback URL you provided in the [Dashboard](https://uclapi.com/dashboard).\n\n## Code Samples\n\nAfter authorisation, calling most of the API endpoints will be fairly similar to each other. Authorisation code samples are provided in their respective descriptions.\n\nIn addition, please find short code examples of how you might call the `/roombookings/equipment` endpoint in your own code (you will find that only the request parameters will change between most different endpoints):\n\nPython:\n```python\nimport requests\n\nparams = {\n \"token\": \"uclapi-5d58c3c4e6bf9c-c2910ad3b6e054-7ef60f44f1c14f-a05147bfd17fdb\",\n \"roomid\": \"433\"\n \"siteid\": \"086\"\n}\n\nr = requests.get(\"https://uclapi.com/roombookings/equipment\", params=params)\nprint(r.json())\n```\n\nShell:\n```shell\ncurl -G https://uclapi.com/roombookings/equipment -d token=uclapi-5d58c3c4e6bf9c-c2910ad3b6e054-7ef60f44f1c14f-a05147bfd17fdb -d roomid=433 -d siteid=086\n```\n\nJavaScript:\n```js\nfetch(\"https://uclapi.com/roombookings/equipment?token=uclapi-5d58c3c4e6bf9c-c2910ad3b6e054-7ef60f44f1c14f-a05147bfd17fdb&roomid=433&siteid=086\")\n .then(response => response.json())\n .then(json => console.log(json));\n```"
contact:
name: API Support
email: isd.apiteam@ucl.ac.uk
url: https://uclapi.com
version: 1.0.0-oas3
termsOfService: https://github.com/uclapi/uclapi/blob/master/backend/uclapi/uclapi/UCLAPIAcceptableUsePolicy.txt
license:
url: https://github.com/uclapi/uclapi/blob/master/LICENSE
name: MIT License
servers:
- url: https://uclapi.com
description: Deployment server
- url: https://staging.ninja
description: Staging server
tags:
- name: Workspaces
description: Fetch workspace availablility and maps throughout UCL
paths:
/workspaces/surveys:
get:
summary: Returns all UCL libraries with the Cad-Capture devices fitted to the seats. Each library is known as a 'survey', as it is a survey of the building. Within each survey there are multiple 'maps', each of which corresponds to a section such as a level, wing or separated library work area. Each sensor is tied to a specific map, and each map belongs to a survey.
tags:
- Workspaces
security:
- OAuthSecurity: []
OAuthToken: []
parameters:
- $ref: '#/components/parameters/survey_filter'
responses:
'200':
description: Success
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
surveys:
type: array
items:
$ref: '#/components/schemas/survey'
'400':
description: Request error
content:
application/json:
schema:
$ref: '#/components/schemas/error'
examples:
'1':
$ref: '#/components/examples/ErrorNoToken'
'2':
$ref: '#/components/examples/ErrorTokenNotExist'
'3':
$ref: '#/components/examples/ErrorInvalidToken'
'4':
$ref: '#/components/examples/ErrorThrottling'
'5':
$ref: '#/components/examples/ErrorNoClientSecret'
'6':
$ref: '#/components/examples/ErrorInvalidClientSecret'
'7':
$ref: '#/components/examples/ErrorInactiveToken'
Invalid filter:
value:
ok: false
error: 'The survey filter you provided is invalid. Valid survey filters are: all,staff,student'
/workspaces/sensors:
get:
summary: Provides a list of every sensor within every map in a survey/library. It can optionally provide the current state of a sensor (e.g. Occupied / Absent), but by default it will only list the sensors without their states
tags:
- Workspaces
security:
- OAuthSecurity: []
OAuthToken: []
parameters:
- $ref: '#/components/parameters/survey_id'
- name: return_states
in: query
description: Whether or not to return the latest trigger state of each sensor. Defaults to false. For live data, set this to true.
required: false
schema:
type: boolean
responses:
'200':
description: Success
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
survey_name:
type: string
survey_id:
type: number
maps:
type: array
items:
$ref: '#/components/schemas/map_with_sensors'
'400':
description: Request error
content:
application/json:
schema:
$ref: '#/components/schemas/error'
examples:
'1':
$ref: '#/components/examples/ErrorNoToken'
'2':
$ref: '#/components/examples/ErrorTokenNotExist'
'3':
$ref: '#/components/examples/ErrorInvalidToken'
'4':
$ref: '#/components/examples/ErrorThrottling'
'5':
$ref: '#/components/examples/ErrorNoClientSecret'
'6':
$ref: '#/components/examples/ErrorInvalidClientSecret'
'7':
$ref: '#/components/examples/ErrorInactiveToken'
Missing parameter:
value:
ok: false
error: Please specify a survey_id.
Invalid parameter:
value:
ok: false
error: The survey_id you specified was not valid.
/workspaces/sensors/averages/time:
get:
summary: Provides a list of every sensor within every map in a survey/library. It can optionally provide the current state of a sensor (e.g. Occupied / Absent), but by default it will only list the sensors without their states
tags:
- Workspaces
security:
- OAuthSecurity: []
OAuthToken: []
parameters:
- name: days
in: query
description: An integer number of days (either 1, 7 or 30) from which to deliver average data. The format of the data returned does not change based on this value, but the actual averaged figures do. When days is 1, the API will return the data from the previous complete day; when days is 7 the API will return data from the last week and when it is set to 30 the API will return data from the last 30 days, which is approx. one month.
required: true
schema:
type: string
- $ref: '#/components/parameters/survey_ids'
- $ref: '#/components/parameters/survey_filter'
responses:
'200':
description: Success
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
surveys:
type: array
items:
$ref: '#/components/schemas/historical_survey'
'400':
description: Request error
content:
application/json:
schema:
$ref: '#/components/schemas/error'
examples:
'1':
$ref: '#/components/examples/ErrorNoToken'
'2':
$ref: '#/components/examples/ErrorTokenNotExist'
'3':
$ref: '#/components/examples/ErrorInvalidToken'
'4':
$ref: '#/components/examples/ErrorThrottling'
'5':
$ref: '#/components/examples/ErrorNoClientSecret'
'6':
$ref: '#/components/examples/ErrorInvalidClientSecret'
'7':
$ref: '#/components/examples/ErrorInactiveToken'
Invalid filter:
value:
ok: false
error: 'The survey filter you provided is invalid. Valid survey filters are: all,staff,student'
Missing days parameter:
value:
ok: false
error: 'You did not specify how many days of historical data should be returned. Valid options are: 1,7,30'
Invalid days parameter:
value:
ok: false
error: 'You did not specify an integer number of days of historical days, Valid options are: 1,7,30'
Unsupported days parameter:
value:
ok: false
error: 'You did not specify a valid number of days of historical days, Valid options are: 1,7,30'
Invalid survey IDs:
value:
ok: false
error: One or more of the survey_ids you requested is not valid.
/workspaces/sensors/lastupdated:
get:
summary: Replies with the timestamp of the last time the sensor data was updated for a given survey. This allows integrations that poll the API to save on request time by only requesting the full set of sensor information for a survey once it has been updated.
tags:
- Workspaces
security:
- OAuthSecurity: []
OAuthToken: []
parameters:
- $ref: '#/components/parameters/survey_id'
responses:
'200':
description: Success
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
last_updated:
type: string
example: '2018-02-16T15:33:01'
survey_id:
type: number
example: '46'
'400':
description: Request error
content:
application/json:
schema:
$ref: '#/components/schemas/error'
examples:
'1':
$ref: '#/components/examples/ErrorNoToken'
'2':
$ref: '#/components/examples/ErrorTokenNotExist'
'3':
$ref: '#/components/examples/ErrorInvalidToken'
'4':
$ref: '#/components/examples/ErrorThrottling'
'5':
$ref: '#/components/examples/ErrorNoClientSecret'
'6':
$ref: '#/components/examples/ErrorInvalidClientSecret'
'7':
$ref: '#/components/examples/ErrorInactiveToken'
Missing survey ID:
value:
ok: false
error: Please specify a survey_id
Invalid survey ID:
value:
ok: false
error: The survey_id you specified is not valid.
/workspaces/sensors/summary:
get:
summary: 'This endpoint summarises, with a one-minute accuracy, the number of seats within each library region that are free and occupied. It is best suited to integrations which show cumulative, live data. Developers can use this endpoint to avoid making many parallel or sequential requests to fetch survey sensor counts.
This endpoint takes into account UCL''s thirty minute rule, which allows students to leave their seat unattended for up to thirty minutes at a time (e.g. to use the bathroom or get food). This means that the Summary endpoint may return a lower number of available seats when compared to reality if many students leave the library within a short period of time. The UCL Library''s website and the UCL Go! mobile application both follow this rule, so your app or integration should have parity with first party data sources by using the UCL API.'
tags:
- Workspaces
parameters:
- $ref: '#/components/parameters/survey_ids'
- $ref: '#/components/parameters/survey_filter'
security:
- OAuthSecurity: []
OAuthToken: []
responses:
'200':
description: Success
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
surveys:
type: array
items:
$ref: '#/components/schemas/survey_with_maps'
'400':
description: Request error
content:
application/json:
schema:
$ref: '#/components/schemas/error'
examples:
'1':
$ref: '#/components/examples/ErrorNoToken'
'2':
$ref: '#/components/examples/ErrorTokenNotExist'
'3':
$ref: '#/components/examples/ErrorInvalidToken'
'4':
$ref: '#/components/examples/ErrorThrottling'
'5':
$ref: '#/components/examples/ErrorNoClientSecret'
'6':
$ref: '#/components/examples/ErrorInvalidClientSecret'
'7':
$ref: '#/components/examples/ErrorInactiveToken'
Invalid filter:
value:
ok: false
error: 'The survey filter you provided is invalid. Valid survey filters are: all,staff,student'
Invalid survey IDs:
value:
ok: false
error: One or more of the survey_ids you requested is not valid.
/workspaces/images/map:
get:
summary: Returns the image specified by the passed in image_id. Image IDs are provided by the /workspaces/surveys endpoint within the array of maps. Each map has an image.
tags:
- Workspaces
parameters:
- name: image_id
in: query
description: The ID of the image to obtain.
required: true
schema:
type: number
- name: image_format
in: query
description: The format of the response. This can either be base64, which returns a JSON object as shown in the example, or raw which will respond with a raw image. In the case of a raw image, the Content-Type header will define the data type, such as image/png.
required: false
schema:
type: string
security:
- OAuthSecurity: []
OAuthToken: []
responses:
'200':
description: Success
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
content_type:
type: string
example: image/png
data:
type: string
example: iVBORw0KGgoAAAANSUhEUgAAE2AAAAVOCAIAAAA...
'400':
description: Request error
content:
application/json:
schema:
$ref: '#/components/schemas/error'
examples:
'1':
$ref: '#/components/examples/ErrorNoToken'
'2':
$ref: '#/components/examples/ErrorTokenNotExist'
'3':
$ref: '#/components/examples/ErrorInvalidToken'
'4':
$ref: '#/components/examples/ErrorThrottling'
'5':
$ref: '#/components/examples/ErrorNoClientSecret'
'6':
$ref: '#/components/examples/ErrorInvalidClientSecret'
'7':
$ref: '#/components/examples/ErrorInactiveToken'
Missing image ID parameter:
value:
ok: false
error: No Image ID provided.
Invalid image ID:
value:
ok: false
error: The image with the ID you requested does not exist.
Unsupported image format:
value:
ok: false
error: You specified a response format that was not either raw or base64.
/workspaces/images/map/live:
get:
summary: Takes Survey ID and Map ID as parameters and displays a dynamically generated SVG map, correct as of the time of the API call (aside from caching overhead), showing the map's plan image with a circle overlaid on each seat. This circle is coloured based on whether the seat is occupied or absent (e.g. it's free).
tags:
- Workspaces
security:
- OAuthSecurity: []
OAuthToken: []
parameters:
- $ref: '#/components/parameters/survey_id'
- name: map_id
in: query
description: The ID of the library's survey which contains the map you want to obtain.
required: true
schema:
type: number
- name: image_scale
in: query
description: The SVG image's scale. The default is 0.02. The scale is implemented as an SVG transform scale, and is applied to both the x and the y axes of the image.
required: false
schema:
type: number
- name: circle_radius
in: query
description: The size of the circle. This must be a positive float value. The default is 128.
required: false
schema:
type: number
- name: absent_colour
in: query
description: TThe colour of the circle designating a free seat. This must be provided as a hex colour code, including the preceeding
required: false
schema:
type: number
- name: occupied_colour
in: query
description: The colour of the circle designating a taken, or occupied, seat. This must be provided as a hex colour code, including the preceeding
required: false
schema:
type: number
responses:
'200':
description: Success
content:
application/xml:
schema:
$ref: '#/components/schemas/svg'
'400':
description: Request error
content:
application/json:
schema:
$ref: '#/components/schemas/error'
examples:
'1':
$ref: '#/components/examples/ErrorNoToken'
'2':
$ref: '#/components/examples/ErrorTokenNotExist'
'3':
$ref: '#/components/examples/ErrorInvalidToken'
'4':
$ref: '#/components/examples/ErrorThrottling'
'5':
$ref: '#/components/examples/ErrorNoClientSecret'
'6':
$ref: '#/components/examples/ErrorInvalidClientSecret'
'7':
$ref: '#/components/examples/ErrorInactiveToken'
Missing parameters:
value:
ok: false
error: You must provide a Survey ID and a Map ID to get a live sensor status image.
Invalid colours:
value:
ok: false
error: 'The custom colours you specified did not match the formal of HTML hex colours. Colours must either be in the format #ABC or #ABCDEF.'
Invalid scale:
value:
ok: false
error: The scale you specified is not valid. It must be a floating point number, such as 1 or 0.02.
Invalid circle radius:
value:
ok: false
error: The circle radius you specified is not valid. It must be a floating point number, such as 128 or 100.5.
Invalid survey IDs:
value:
ok: false
error: Either the IDs you sent were not integers, or they do not exist.
components:
schemas:
svg:
type: object
properties:
xmlns:ev:
type: string
example: http://www.w3.org/2001/xml-events
xml:
attribute: true
xmlns:xlink:
type: string
example: http://www.w3.org/1999/xlink
xml:
attribute: true
xmlns:
type: string
example: http://www.w3.org/2000/svg
xml:
attribute: true
g:
type: object
properties:
transform:
type: string
example: scale(0.02, 0.02)
xml:
attribute: true
image:
type: object
properties:
width:
type: string
example: '28329.0'
xml:
attribute: true
height:
type: string
example: '29882.0'
xml:
attribute: true
xlink:href:
type: string
example: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA7QAAA...
xml:
attribute: true
g:
type: array
items:
$ref: '#/components/schemas/seat_image'
survey_with_maps:
properties:
name:
type: string
example: Teaching Rooms
id:
type: string
example: '73'
sensors_absent:
type: number
example: 72
sensors_total:
type: number
example: 80
sensors_occupied:
type: number
example: 3
maps:
type: array
items:
$ref: '#/components/schemas/average_with_name_and_id'
survey:
properties:
id:
type: number
example: 46
name:
type: string
example: UCL Institute of Education Library
active:
type: boolean
example: true
start_time:
type: string
example: 09:00
end_time:
type: string
example: '17:00'
staff_survey:
type: number
example: 0
maps:
type: array
items:
$ref: '#/components/schemas/map'
location:
type: object
properties:
coordinates:
type: object
properties:
lat:
type: string
example: '51.522897'
lng:
type: string
example: '-0.127864'
address:
type: array
items:
type: string
example:
- Newsam Library and Archives
- 20 Bedford Way
- London
- WC1H 0AL
sensor:
properties:
description_2:
type: string
example: ''
floor:
type: string
example: 'False'
y_pos:
type: string
example: '14893.0'
description_3:
type: string
example: ''
device_type:
type: string
example: Desk
host_address:
type: string
example: '584'
building_name:
type: string
example: IOE (20 B-Way)
room_description:
type: string
example: ''
last_trigger_type:
type: string
example: Occupied
survey_id:
type: string
example: '46'
room_type:
type: string
example: Open Plan
room_name:
type: string
example: '324'
room_id:
type: string
example: '291'
location:
type: string
example: ''
survey_device_id:
type: string
example: '8420'
share_id:
type: string
example: None
x_pos:
type: string
example: '32432.0'
description_1:
type: string
example: ''
hardware_id:
type: string
example: '584001'
pir_address:
type: string
example: '1'
last_trigger_timestamp:
type: string
example: '2018-02-15T22:42:28+00:00'
occupied:
type: boolean
example: true
# --- truncated at 32 KB (36 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/ucl/refs/heads/main/openapi/ucl-workspaces-api-openapi.yml