Overview


Base URL

The following URLs are for our environments:

  • develop: /dev.api.vilio.co/version
  • production: /api.vilio.co/version

The bolded parameters may have the following values:

Paramater

Values

Description

versionAny of the existing versions, such as v1Target API version
http is not supported on production.

Certificates

If you need the vilio.crt file for development, it is in the engineering subdirectory of this project in Google Drive.


Request Examples

Many of the requests examples are made use httpie, a more friendly curl. Add the --verify=no flag if you are using httpie with self signed certificates.


Responses

All responses are returned in JSON unless otherwise specified.


Models

Overview of the models present in the application

Type Conversion for JSON

ParameterJSON Payload
ObjectIdString

AccessToken

{
  _id             : { type: ObjectId, required: true, index: true, unique: true },
  userId          : { type: String, required: true, index: true },
  clientId        : { type: String, unique: true, required: true },
  token           : { type: String, unique: true, required: true },
  created         : { type: Number, default: Date.now }
}

Address

{
  _id             : false,
  line1           : { type: String, required: true, match: line1RegExp },
  line2           : { type: String, required: false },
  city            : { type: String, required: false },
  country         : { type: String, required: false },
  postal          : { type: String, required: false }
}

line1RegExp: /^\w{1,}[^]{0,1000}$/

Amenity

{
  _id             : { type: ObjectId, required: true, index: true, unique: true },
  title           : { type: String, required: true, match: /^\w{1,}[^]{0,200}$/ },
  description     : { type: String, sparse: true, match: /^\w{1,}[^]{0,2500}$/ },
  icon            : { type: String, required: true, match: /^\w{1,}[^]{0,200}$/ },
  maxBookingTime  : { type: Number, default: 7200000, required: true }, / Milliseconds
  bookingInterval : { type: Number, default: 1800000, required: true }, / Milliseconds
  fields          : { type: Schema.Types.Mixed, sparse: true },
  color           : { type: Schema.Types.Mixed, default: { r:0, g:0, b:0, a:1, name: '' } },
  property        : { type: Schema.Types.ObjectId, ref: 'Property', required: true, index: true },
  availability    : { type: Schema.Types.Mixed, default: { }, required: true },
  created         : { type: Number, default: Date.now }
}

Booking

{
  _id             : { type: ObjectId, required: true, index: true, unique: true },
  property        : { type: Schema.Types.ObjectId, ref: 'Property', required: true, index: true },
  amenity         : { type: Schema.Types.ObjectId, ref: 'Amenity', required: true, index: true  },
  user            : { type: Schema.Types.ObjectId, ref: 'User', required: true, index: true  },
  fields          : { type: Schema.Types.Mixed, sparse: true },
  startDate       : { type: Number, required: true },
  endDate         : { type: Number, required: true },
  cancelledBy     : { type: Schema.Types.ObjectId, ref: 'User', sparse: true },
  created         : { type: Number, default: Date.now }
}

Chat

{
  _id             : { type: ObjectId, required: true, index: true, unique: true },
  title           : { type: String, required: true, match: /^\w{1,}[^]{0,200}$/ },
  members         : { type: [Schema.Types.ObjectId], ref: 'User', required: true, index: true },
  property        : { type: Schema.Types.ObjectId, ref: 'Property', required: true, index: true },
  icon            : { type: String, required: true },
  created         : { type: Number, default: Date.now }
}

Client

{
  _id             : { type: ObjectId, required: true, index: true, unique: true },
  clientId        : { type: String, unique: true, required: true },
  clientSecret    : { type: String, required: true },
  created         : { type: Number, default: Date.now }
}

Company

{
  _id             : { type: ObjectId, required: true, index: true, unique: true },
  name            : { type: String, unique: true, required: true, match: /^\w{1,}[^]{0,200}$/ },
  created         : { type: Number, default: Date.now }
}

Event

{
  _id             : { type: ObjectId, required: true, index: true, unique: true },
  property        : { type: Schema.Types.ObjectId, ref: 'Property', required: true, index: true },
  creator         : { type: Schema.Types.ObjectId, ref: 'User', required: true, index: true },
  / Empty users lists are property wide events
  users           : { type: [Schema.Types.ObjectId], ref: 'User', sparse: true },
  title           : { type: String, required: true, match: /^\w{1,}[^]{0,200}$/ },
  description     : { type: String, sparse: true, match: /^\w{1,}[^]{0,2500}$/ },
  color           : { type: Schema.Types.Mixed, default: Color.randomColor },
  category        : { type: String, required: true, validate: validateCategory },
  start           : { type: Number, required: true },
  end             : { type: Number, required: true },
  created         : { type: Number, default: Date.now }
}

Invite

{
  _id             : { type: ObjectId, required: true, index: true, unique: true },
  email           : { type: String, required: true },
  role            : { type: Schema.Types.ObjectId, ref: 'Role' },
  token           : { type: String, unique: true, required: true },
  created         : { type: Number, default: Date.now },
  invalid         : { type: Number, sparse: true },
  accepted        : { type: Number, sparse: true }
}

Message

{
  _id             : { type: ObjectId, required: true, index: true, unique: true },
  user            : { type: Schema.Types.ObjectId, ref: 'User', required: true, index: true },
  chat            : { type: Schema.Types.ObjectId, ref: 'Chat', required: true, index: true },
  body            : { type: String, required: true, match: /^\w{1,}[^]{0,2500}$/ },
  created         : { type: Number, default: Date.now }
}

MessageRead

{
  property        : { type: Schema.Types.ObjectId, ref: 'Property', required: true, index: true },
  user            : { type: Schema.Types.ObjectId, ref: 'User', required: true, index: true },
  chat            : { type: Schema.Types.ObjectId, ref: 'Chat', sparse: true },
  message         : { type: Schema.Types.ObjectId, ref: 'Message', sparse: true },
  parent          : { type: Schema.Types.ObjectId, ref: 'Post', sparse: true },
  post            : { type: Schema.Types.ObjectId, ref: 'Post', sparse: true },
  ticket          : { type: Schema.Types.ObjectId, ref: 'Ticket', sparse: true },
  comment         : { type: Schema.Types.ObjectId, ref: 'TicketComment', sparse: true },
  read            : { type: Boolean, required: true },
  created         : { type: Number, default: Date.now }
}

Notification

{
  _id             : { type: ObjectId, required: true, index: true, unique: true },
  preview         : { type: String, sparse: true, match: /^.{1,}[^]{0,200}$/ },
  action          : { type: String, required: true, match: actionRegEx },
  data            : { type: Schema.Types.Mixed, required: true },
  model           : { type: String, required: true },
  property        : { type: Schema.Types.ObjectId, ref: 'Property', sparse: true, index: true },
  company         : { type: Schema.Types.ObjectId, ref: 'Company', sparse: true, index: true },
  reads           : { type: [Schema.Types.Mixed], default: [] },
  created         : { type: Number, default: Date.now() },
  lastModified    : { type: Number, default: Date.now },
  modifiedBy      : { type: Schema.Types.ObjectId, ref: 'User', sparse: true }
}

NotificationSettings

{
  _id                    : { type: ObjectId, required: true, index: true, unique: true },
  user                   : { type: Schema.Types.ObjectId, ref: 'User', required: true, index: true },
  ticketComment          : {
    web                    : { type: Boolean, required: true, default: true }
  },
  ticketStatusChanged    : {
    web                    : { type: Boolean, required: true, default: true }
  },
  ticketAssigneeChanged  : {
    web                    : { type: Boolean, required: true, default: true }
  },
  dailyEmail             : {
    email                  : { type: Boolean, required: true, default: true }
  },
  ticketCreation         : {
    web                    : { type: Boolean, required: true, default: true },
    email                  : { type: Boolean, required: true, default: true }
  },
  ticketNote             : {
    web                    : { type: Boolean, required: true, default: true }
  },
  ticketResolved         : {
    email                  : { type: Boolean, required: true, default: true }
  }
}

Post

Post has two forms: Parent and Child post.A child post will have a valid ObjectId parent value but no title. A Parent post requires a title but has no parent value.

{
  _id             : { type: ObjectId, required: true, index: true, unique: true },
  parent          : { type: Schema.Types.ObjectId, ref: 'Post', sparse: true, index: true },
  user            : { type: Schema.Types.ObjectId, ref: 'User', required: true, index: true },
  property        : { type: Schema.Types.ObjectId, ref: 'Property', required: true, index: true },
  title           : { type: String, sparse: true, match: /^\w{1,}[^]{0,200}$/ },
  body            : { type: String, required: true, match: /^\w{1,}[^]{0,2500}$/ },
  deleted         : { type: Boolean, sparse: true },
  created         : { type: Number, default: Date.now }
}

Property

{
  _id             : { type: ObjectId, required: true, index: true, unique: true },
  company         : { type: Schema.Types.ObjectId, ref: 'Company', required: true, index: true },
  name            : { type: String, required: true, match: /^\w{1,}[^]{0,200}$/ },
  address         : { type: Schema.Types.Mixed, default: { }, required: true },
  fields          : { type: Schema.Types.Mixed, sparse: true },
  created         : { type: Number, default: Date.now }
}

PushToken

{
  _id             : { type: ObjectId, required: true, index: true, unique: true },
  user            : { type: Schema.Types.ObjectId, ref: 'User', required: true, index: true },
  token           : { type: String, unique: true, required: true },
  type            : { type: String, required: true, index: true, match: RegEx },
  created         : { type: Number, default: Date.now }
}

/**
 * PushToken.type can be one of the following values:
 *
 * 'apn'
 * 'gcm'
 *
 * and is matched against the following regular expression:
 */

/^(apn|gcm)$/

RefreshToken

{
  _id             : { type: ObjectId, required: true, index: true, unique: true },
  userId       : { type: String, required: true, index: true },
  clientId     : { type: String, unique: true, required: true },
  token        : { type: String, unique: true, required: true },
  created      : { type: Number, default: Date.now }
}

Role

/ Embedded in the user object
{
  _id             : { type: ObjectId, required: true, index: true, unique: true },
  title           : { type: String, required: true, match: /^\w{1,}[^]{0,400}$/ },
  group           : { type: Number, required: true, match: groupRegex }, / Grouping
  company         : { type: Schema.Types.ObjectId, ref: 'Company', sparse: true },
  property        : { type: Schema.Types.ObjectId, ref: 'Property', sparse: true },
  unit            : { type: Schema.Types.ObjectId, ref: 'Unit', sparse: true },
  permissions     : { type: Schema.Types.Mixed, default: { } },
  isCustom        : { type: Boolean, sparse: true },
  hasDiverged     : { type: Number, sparse: true },
  created         : { type: Number, default: Date.now },
  lastModified    : { type: Number, default: Date.now },
  modifiedBy      : { Type: Schema.Types.ObjectId, ref: 'User', sparse: true }
}

Groups:
ADMIN         : 1,
DEVELOPER     : 100,
MANAGER       : 200,
RESIDENT      : 300,
GUEST         : 400

Permisssions:
amenity: { create: true, read: true, update: true, delete: true },
booking: { create: true, read: true, update: true, delete: true },
chat: { create: true, read: true, update: true },
company: { create: true, read: true, update: true, delete: true },
event: { create: true, read: true, update: true, delete: true },
invite: { create: true, read: true, update: true, delete: true },
message: { create: true, read: true },
post: { create: true, read: true, delete: true },
property: { create: true, read: true, update: true, delete: true },
ticket: { create: true, read: true, update: true },
ticketComment: { create: true, read: true },
ticketNote: { create: true, read: true },
ticketType: { create: true, read: true, update: true, delete: true },
unit: { create: true, read: true, update: true, delete: true },
unitType: { create: true, read: true, update: true, delete: true },
user: { create: true, read: true },
role: { create: true, read: true, update: true, delete: true }

Unique Scenarios:
/ Can only create a chat with a manager or a developer
chat: { create: { limit: { to: ['Manager', 'Developer'] } } }
/ Can only invite a resident
invite: { create: { limit: { to: ['Resident'] } } }
/ Can only delete your own posts
post: { delete: { limit: { to: { own: true } } } }
/ Can only update ticket status
ticket: { update: { limit: { to: ['status'] } } }
/ Can only update a resident's role
role: { update: { limit: { to: ['Resident'] } } }
/ Can only create a managers's role
role: { create: { limit: { to: ['Manager'] } } }

Ticket

{
  _id             : { type: ObjectId, required: true, index: true, unique: true },
  title           : { type: String, required: true, match: /^\w{1,}[^]{0,200}$/ },
  number          : { type: Number, required: true },
  body            : { type: String, required: true, match: /^\w{1,}[^]{0,2500}$/ },
  type            : { type: Schema.Types.ObjectId, ref: 'TicketType', required: true, index: true },
  status          : { type: String, required: true, match: statusRegEx, default: Status.OPEN },
  fields          : { type: Schema.Types.Mixed, sparse: true },
  reporter        : { type: Schema.Types.ObjectId, ref: 'User', required: true, index: true },
  property        : { type: Schema.Types.ObjectId, ref: 'Property', required: true, index: true },
  unit            : { type: Schema.Types.ObjectId, ref: 'Unit', sparse: true },
  / If no assignees are provided we default to ticket type assignees
  assignees       : { type: [Schema.Types.ObjectId], ref: 'User', required: false, index: true },
  managerRead     : { type: Number, sparse: true },
  created         : { type: Number, default: Date.now },
  createdBy       : { type: Schema.Types.ObjectId, ref: 'User', required: true },
  lastModified    : { type: Number, default: Date.now },
  modifiedBy      : { type: Schema.Types.ObjectId, ref: 'User', sparse: true }
}

TicketComment

{
  _id             : { type: ObjectId, required: true, index: true, unique: true },
  body            : { type: String, required: true, match: /^\S{1,}[^]{0,2500}$/ },
  user            : { type: Schema.Types.ObjectId, ref: 'User', required: true },
  ticket          : { type: Schema.Types.ObjectId, ref: 'Ticket', required: true },
  systemGenerated : { type: Boolean, sparse: true },
  created         : { type: Number, default: Date.now }
}

TicketNote

{
  _id             : { type: ObjectId, required: true, index: true, unique: true },
  note            : { type: String, match: /^\S{1,}[^]{0,2500}$/, sparse: true },
  cost            : { type: Number, sparse: true },
  material        : { type: String, match: /^\S{1,}[^]{0,1000}$/, sparse: true },
  timeTakenHours  : { type: Number, sparse: true },
  ticket          : { type: Schema.Types.ObjectId, ref: 'Ticket', required: true },
  created         : { type: Number, default: Date.now },
  createdBy       : { type: Schema.Types.ObjectId, ref: 'User' }
}

TicketType

{
  priority        : { type: String, required: true, default: 'normal', match: priorityRegEx },
  title           : { type: String, required: true, match: /^\w{1,}[^]{0,200}$/ },
  description     : { type: String, required: true, match: /^\w{1,}[^]{0,2500}$/ },
  createdBy       : { type: Schema.Types.ObjectId, ref: 'User', required: true, index: true },
  / required: true is missing because logic exists to ensure this cannot be empty
  assignees       : { type: [Schema.Types.ObjectId], ref: 'User', index: true },
  property        : { type: Schema.Types.ObjectId, ref: 'Property', required: true, index: true },
  isDeleted       : { type: Boolean, sparse: true },
  created         : { type: Number, default: Date.now }
}

/**
 * TicketType.priority can be one of the following values:
 *
 * 'high'
 * 'normal'
 *
 * and is matched against the following regular expression:
 */

/^(high|normal)$/


Unit

{
  _id             : { type: ObjectId, required: true, index: true, unique: true },
  property        : { type: Schema.Types.ObjectId, ref: 'Property', required: true, index: true },
  title           : { type: String, required: true, match: /^\w{1,}[^]{0,200}$/ },
  address         : { type: Schema.Types.Mixed, required: true, validate: [Address.validate, 'Invalid address'] },
  type            : { type: Schema.Types.ObjectId, ref: 'UnitType', required: true },
  isDeleted       : { type: Boolean, sparse: true },
  created         : { type: Number, default: Date.now }
}

UnitType

{
  _id             : { type: ObjectId, required: true, index: true, unique: true },
  property        : { type: Schema.Types.ObjectId, ref: 'Property', required: true, index: true },
  name            : { type: String, required: true, match: /^\w{1,}[^]{0,200}$/ },
  sqrft           : { type: Number, required: true },
  bedrooms        : { type: Number, required: true },
  bathrooms       : { type: Number, required: true },
  description     : { type: String, match: /^\w{1,}[^]{0,2500}$/ }
}

User

{
  _id               : { type: ObjectId, required: true, index: true, unique: true },
  email                 : { type: String, unique: true, required: true, match: emailRegex },
  firstName             : { type: String, index: true, match: /^\w{1,}[^]{0,200}$/ },
  lastName              : { type: String, index: true, match: /^\w{1,}[^]{0,200}$/ },
  fullName              : { type: String, index: true, match: /^\w{1,}[^]{0,400}$/ },
  phone                 : { type: String, match: /^(\d-?){8,30}$/ },
  hashedPassword        : { type: String, required: true },
  salt                  : { type: String, required: true },
  /socialProfile       : { type: Schema.Types.ObjectId, ref: 'SocialProfile', sparse: true },
  color                 : { type: Schema.Types.Mixed, default: Color.randomColor },
  joined                : { type: Number, sparse: true, index: true },
  created               : { type: Number, default: Date.now },
  roles                 : { type: [Schema.Types.Mixed], default: [] }
}

Verification

{
  _id          : { type: ObjectId, required: true, index: true, unique: true },
  / Who made the request
  user     : { type: ObjectId, ref: 'User', required: true },
  / email reflects who the message was sent to
  email    : { type: String, required: true, match: emailRegex },
  token    : { type: String, required: true, default: uuid.v4 },
  expires  : { type: Number, required: true, default: expires },
  type     : { type: String, match: /^(password reset|change email)$/ },
  status   : { type: String, default: 'sent', match: statusRegex },
  created  : { type: Number, default: Date.now }
}