Akiles API Reference

Akiles API

API Endpoint
https://api.akiles.app/v2
Contact: support@akiles.app
Version: 2.0

Changelog

October 2024

  • Expanded documentation of endpoint POST /members/:id/pins.

September 2024

Added device to the API. One highlight is this now allows obtaining the online/offline status and battery level of devices via the API.

  • Added endpoints /devices
  • Added endpoints /products
  • Added device_id to /gadgets.
  • Added webhook signatures.
  • Expanded documentation for events.
  • Expanded documentation for webhook filters.

June 2024

  • Added /members/:id/cards endpoints.

April 2023

  • Added support for creating, editing, deleting member_groups.

October 2021

  • Added /members/:id/pins endpoints.

July 2020: Version 2.0

  • Members can now have multiple email addresses.
  • Members can now have multiple magic links.
  • A member can have email addresses and magic links at the same time.
  • A member can now be associated to multiple groups, with each association having its own start and end date/times.

Having multiple email addresses is useful for hotel room reserves where you have multiple guests in the same room. Now you can create the reserve as a single member that grants access to all guests.

Per-group start/end times is useful for room reservations: the same member can have multiple reservations for different rooms at different times, and he'll only be able to enter each room during the allowed time.

API changes

  • Removed member fields: type, email, member_group_ids
  • Removed endpoints /members/{member_id}/magic_link
  • Added endpoints /members/{member_id}/emails
  • Added endpoints /members/{member_id}/magic_links
  • Added endpoints /members/{member_id}/group_associations

Version 1.0

Initial API release

Versioning

The major version is indicated in the URL. For example https://api.akiles.app/v2 vs https://api.akiles.app/v2. The minor version is not indicated anywhere, since minor versions only do backwards-compatible changes. A client designed for API version v2.0 should work unmodified on version v2.1, provided it follows the compatibility policy outlined below.

We will release new minor versions with only backwards-compatible changes. We consider the following changes backwards-compatible:

  • Adding new endpoints.
  • Adding new HTTP methods to existing endpoints.
  • Adding new optional fields to request objects.
  • Adding new fields to response objects.
  • Adding new possible values to enum fields.
  • Relaxing request validation rules. For example, a field couldn't be null before and now it can.

Keep these in mind when developing your integration. For example, when parsing API responses, ignore unknown received fields instead of erroring.

Ocasionally, when backwards-incompatible changes are necessary for the healthy evolution of the platform, we will also release new major API versions. In this case, the previous major version will be supported for at least 6 months after the launch of the new major version.

Integration examples

There are many ways of integrating Akiles into your existing platform or service. There are two main categories:

-Sync integration (non whitelabel): The user end user will login with the Akiles app or website (magic link, without downloading any app). You can still customize and change all the related paramenters such as membership validity, permissions, sites, etc. The integration consists in syncing your existing objects (users, reservations) with Akiles so user creation and management can be fully automatic.

-Whitelabel integration: The end user will be able to access the diferent spaces through a custom experience in your app or website. You can implement any logic that will end up opening a door. This integration has extra cost and is only available with the whitelabel feature. If you plan to publish your integration and make it available to all Akiles customers, we suggest you to use the non whitelabel option.

Sync integration (non whitelabel)

In this kind of integration, the user will use the Akiles app and environment to interact with your spaces. The idea behind this integration is directly creating or editing other Akiles objects from your backend as needed. You will be able to create, edit and delete members or any other object or parameter based on your business logic.

The end user will have to login once the required member is created using any of the valid login methods. You will have to use the already flexible Akiles permission system to implement your logic.

This kind of integration is recommended for Property Management Systems, Booking systems, etc.

Example flow:

  1. You get a new reservation. Just gather all the relevant details.
  2. Perform a member create API call. Remember to store the returned member_id in your database
POST https://api.akiles.app/v2/members
Content-Type: application/json;charset=UTF-8

{
  "name": "John Doe",
}
  1. Based on the reservation or booking parameters, associate a group to the recently created member. The group will determine which set of spaces the guest will have access. Do a group association create API request.
POST https://api.akiles.app/v2/members/{member_id}/group_associations
Content-Type: application/json;charset=UTF-8

{
  "member_group_id": "mg_3merk33gt1692dk2p2m1",
  "starts_at": "2018-03-13T16:56:51.766836837Z",
  "ends_at": "2018-03-13T16:56:51.766836837Z",
}
  1. Choose a login method for the user and send an invite. For temporary applications we recommend using the magic link rather than the email. Do a magic link create API request.
POST https://api.akiles.app/v2/members/{member_id}/magic_links
Content-Type: application/json;charset=UTF-8

{}
  1. Reveal the link. Warning: Remember that the link is able to grant access to your space. You should protect it and distribute it using a secure channel. In order to reveal the link do a magic link reveal API request.
POST https://api.akiles.app/v2/members/{member_id}/magic_links/{member_magic_link_id}/reveal
Content-Type: application/json;charset=UTF-8

{}
  1. Send the link to the final user or display it somewhere.

Whitelabel integration

For this kind of integration the user will directly interact with the gadgets, going through a custom experience. You will be responsible for implementing a proper UX/UI and or equivalent business logic.

As an example, the final user will typically login in your platform. You will have to implement all the permissions and conditions before granting the user access to the gadget. Finally, if theese conditions are met, your backend will call the gadget action endpoint in order to perform the action. You will prevously need to call gadget list endpoint in order to get the ids.

Example flow:

  1. The end user logs in into your app or website
  2. Your platform checks against your backend or database if the end user meets certain requirements before accessing the action button (have a valid reservation, plan, account, etc...)
  3. The user clicks the action button. Your backend does the gadget action API call with the gadget id of the gadget related to the space you want the action to happen
POST https://api.akiles.app/v2/gadgets/{gadget_id}
Content-Type: application/json;charset=UTF-8

{
  "id": "open",
  "name": "Open"
}
  1. Handle response or error

Authentication types

There are two ways to authenticate to the API: API keys and OAuth.

Use API keys if you're developing integrations that you will only use with your own Akiles organization. This is the easiest and simplest way to get started, and the security benefits of OAuth don't apply to you anyway.

Use OAuth if you want other people to use your integration. The most common use case is if you are developing a SaaS and want your customers to be able to enable your integration with their own Akiles organizations.

Additionally, OAuth is required to get your application/integration listed in the Applications tab in the Akiles admin panel. Listing your application will make it discoverable to all Akiles customers.

API key authentication

API keys are static keys that can be used to make calls to the Akiles API.

To create an API key, login to the Akiles admin panel, and go to API -> API keys and create one. For security, you will only be able to see the full value of the API key once, when creating it.

To use the API key, include an Authorization: Bearer header with it:

Authorization: Bearer ak_3pbzqfe...

All API keys have full read-write permissions, equivalent to organization administrators. All the actions performed with the API key are attributed to it in the events.

OAuth authentication

OAuth authentication uses the standard RFC-6479 OAuth 2.0 protocol. It allows your app to interactively request access to customers' Akiles organizations and obtain access tokens for them. This makes the experience smoother and more secure, since the customer doesn't have to manually copy and paste a sensitive API key.

This section is not intended to be a full explanation on how OAuth works, just a quick reference to get started. For more info, consult online documentation about OAuth.

Client ID and Client Secret

Your OAuth application needs a Client ID and a Client Secret to operate.

  • The Client ID is considered public, it is OK to expose it to frontends (and is in fact necessary to do so).
  • The Client Secret must be maintained confidential since it is used to obtain access tokens. Always use it from your server, never send it or expose it to your frontend.

To obtain your application's Client ID and Secret, contact support.

Token types

  • Access token: used to make API calls, expires 1 hour after issuance.
  • Refresh token: used to obtain new access tokens when they expire.

Authorization flow

  1. Generate a cryptographically-secure random string state and store it somewhere you can access it later (e.g. a cookie)
  2. Start the flow by redirecting the user to the following URL:
https://auth.akiles.app/oauth2/auth?client_id=CLIENT_ID_HERE>&redirect_uri=https://your-app.example.com/redirect&response_type=code&scope=full_read_write offline&state=STATE_HERE
  1. The Akiles authorization server will prompt the user to choose a user account and organization.
  2. The Akiles authorization server will ask the user to authorize your requested scopes.
  3. The user gets redirected to your redirect_uri:
https://your-app.example.com/redirect?code=CODE_HERE&scope=full_read_write offline&state=STATE_HERE
  1. Check the state you have received matches the original state you stored. If it doesn't, abort. This is necessary to protect against CSRF / session poisoning attacks.
  2. Exchange the authorization code for the access and refresh tokens.
POST https://auth.akiles.app/oauth2/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
client_id=CLIENT_ID_HERE
client_secret=CLIENT_SECRET_HERE
code=CODE_HERE
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8

{
  "access_token": "ACCESS_TOKEN_HERE",
  "expires_in": 3599,
  "refresh_token": "REFRESH_TOKEN_HERE",
  "scope": "full_read_write offline",
  "token_type": "bearer"
}
  1. You're done! Save the access token and refresh token.

You can now use the access token to make API calls on behalf of the user's organization. The access token expires in 1 hour. You can obtain new, fresh access tokens with the refresh token.

Refresh token flow

POST https://auth.akiles.app/oauth2/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
client_id=CLIENT_ID_HERE
client_secret=CLIENT_SECRET_HERE
refresh_token=REFRESH_TOKEN_HERE
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8

{
  "access_token": "NEW_ACCESS_TOKEN_HERE",
  "expires_in": 3599,
  "refresh_token": "NEW_REFRESH_TOKEN_HERE",
  "scope": "full_read_write offline",
  "token_type": "bearer"
}

Store both the new access and refresh tokens. The old refresh token, once used, is no longer guaranteed to keep working forever.

Scopes

  • full_read_write: read-write access to all the objects. Same as a regular API key, or an administrator user.
  • full_read_only: read-only access to all the objects.
  • offline: allows offline access. You need this to obtain refresh tokens.

Webhooks

Webhook objects are scoped to your OAuth application. That is, webhooks created with API keys or manually from the admin panel won't be visible to you, and webhooks you create won't be visible to API keys or in the admin panel.

All your application's webhooks in an organization will be deleted when your app gets deauthorized.

Even if you have read-only access, you can still create/edit/delete webhooks. This allows you to receive real-time updates in an application that only needs to read data from the Akiles organization.

Pagination

The API uses cursor-based pagination. All list endpoints return data in reverse chronological order, and have the following common structure:

GET parameters

  • limit: How many items to fetch, between 1 and 100
  • cursor: Value of cursor_next from a previous request. If not given, it will fetch the most recent items.

Response data

  • data: The received items, in a JSON array.
  • has_next: Whether there are more items to fetch.
  • cursor_next: Cursor to fetch the next page of items. Only present if has_next is true.

Metadata

Most API objects allow you to attach your own, arbitrary key-value metadata in the metadata field. You can use it to store references to primary keys of your database, custom states, etc.

Metadata is visible to anyone with admin or API access to your organization. There is a storage limit of 1024 bytes of metadata per object.

metadata

Key-value metadata

string
Example
{
  "key1": "value1",
  "key2": "value2"
}

Organizations

Everything in Akiles APIs belongs to an Organization. An organization represents one Akiles customer and can handle multiple sites, gadgets, etc.. API keys are tied to organizations too, and they give access only to the objects belonging to the organization.

organization

id
string (organization_id)

Unique identifier.

name
string

Name.

is_deleted
boolean

Indicates if the object has been deleted.

created_at
string (date-time)

Creation time for this object.

metadata
metadata

Key-value metadata

Example
{
  "id": "org_3merk33gt1v9ypgfzrp1",
  "name": "SkyCowork",
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

Get organization

GET /organization
An instance of organization
Response Example (200 OK)
{
  "id": "org_3merk33gt1v9ypgfzrp1",
  "name": "SkyCowork",
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

Edit organization

PATCH /organization
metadata
metadata

Key-value metadata

Request Example
{
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}
An instance of organization
Response Example (200 OK)
{
  "id": "org_3merk33gt1v9ypgfzrp1",
  "name": "SkyCowork",
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

Sites

A site is a physical place where Akiles gadgets are installed. For example, it can be a building, a floor... Gadgets belong to a particular site.

End users will see one "card" in their app's home screen per site. The site information (email, contact, etc) is displayed in that card.

location

lat
number (float)

Latitude in degrees

lng
number (float)

Longitude

Example
{
  "lat": 41.290485,
  "lng": 2.1829076
}

site_geo

location
location
radius
number

Radius in meters

Example
{
  "location": {
    "lat": 41.290485,
    "lng": 2.1829076
  },
  "radius": 100
}

site_map

location
location
place_id
string

place_id from Google Maps Places API

address
string

Human-readable address

image_url
string

Map image URL, displayed in the site's header in the app.

Example
{
  "location": {
    "lat": 41.290485,
    "lng": 2.1829076
  },
  "place_id": "string",
  "address": "string",
  "image_url": "string"
}

site

id
string (site_id)

site ID

name
string

Site name

organization_id
string (organization_id)

organization ID

geo
site_geo
map
site_map
phone
string

Phone number. Shown in the site info in the app.

email
string

Email. Shown in the site info in the app.

info
string

Free-form extra information. Shown in the site info in the app.

timezone
string (timezone)

Local time zone of the site.

is_deleted
boolean

True if this object has been deleted.

created_at
string (date-time)

Creation time for this object.

metadata
metadata

Key-value metadata

Example
{
  "id": "site_3merk33gt21kym11een1",
  "name": "string",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "geo": {
    "location": {
      "lat": 41.290485,
      "lng": 2.1829076
    },
    "radius": 100
  },
  "map": {
    "location": {
      "lat": 41.290485,
      "lng": 2.1829076
    },
    "place_id": "string",
    "address": "string",
    "image_url": "string"
  },
  "phone": "string",
  "email": "string",
  "info": "string",
  "timezone": "Europe/Madrid",
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

List sites

GET /sites
cursor
in query
string

Cursor value obtained from a previous request to the same endpoint. If not present, the first page of results is returned.

limit
in query
integer

Items per page.

sort
in query
string

Sort order. Field name, followed by :asc or :desc. Defaults to id:desc. In most models, IDs are assigned in increasing order in time, so sorting by id is equivalent to sorting by created_at. Depending on the model, sorting on some fields might not be supported.

data
site
has_next
boolean
cursor_next
string (cursor)
Response Example (200 OK)
{
  "data": [
    {
      "id": "site_3merk33gt21kym11een1",
      "name": "string",
      "organization_id": "org_3merk33gt1v9ypgfzrp1",
      "geo": {
        "location": {
          "lat": 41.290485,
          "lng": 2.1829076
        },
        "radius": 100
      },
      "map": {
        "location": {
          "lat": 41.290485,
          "lng": 2.1829076
        },
        "place_id": "string",
        "address": "string",
        "image_url": "string"
      },
      "phone": "string",
      "email": "string",
      "info": "string",
      "timezone": "Europe/Madrid",
      "is_deleted": false,
      "created_at": "2024-03-13T16:56:51.766836837Z",
      "metadata": {
        "key1": "value1",
        "key2": "value2"
      }
    }
  ],
  "has_next": true,
  "cursor_next": "i9vmCnOgONT2AjUMCn1K1N5cJg=="
}

Get site

GET /sites/{site_id}
site_id
in path
string (site_id)

site ID

An instance of site
Response Example (200 OK)
{
  "id": "site_3merk33gt21kym11een1",
  "name": "string",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "geo": {
    "location": {
      "lat": 41.290485,
      "lng": 2.1829076
    },
    "radius": 100
  },
  "map": {
    "location": {
      "lat": 41.290485,
      "lng": 2.1829076
    },
    "place_id": "string",
    "address": "string",
    "image_url": "string"
  },
  "phone": "string",
  "email": "string",
  "info": "string",
  "timezone": "Europe/Madrid",
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

Edit site

PATCH /sites/{site_id}
site_id
in path
string (site_id)

site ID

metadata
metadata

Key-value metadata

Request Example
{
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}
An instance of site
Response Example (200 OK)
{
  "id": "site_3merk33gt21kym11een1",
  "name": "string",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "geo": {
    "location": {
      "lat": 41.290485,
      "lng": 2.1829076
    },
    "radius": 100
  },
  "map": {
    "location": {
      "lat": 41.290485,
      "lng": 2.1829076
    },
    "place_id": "string",
    "address": "string",
    "image_url": "string"
  },
  "phone": "string",
  "email": "string",
  "info": "string",
  "timezone": "Europe/Madrid",
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

Products

product

id
string

product ID

name
string

Human-friendly product name. Show this to the user instead of the product ID!

Example
{
  "id": "controller_ethernet",
  "name": "Controller Ethernet"
}

List products

GET /products
cursor
in query
string

Cursor value obtained from a previous request to the same endpoint. If not present, the first page of results is returned.

limit
in query
integer

Items per page.

sort
in query
string

Sort order. Field name, followed by :asc or :desc. Defaults to id:desc. In most models, IDs are assigned in increasing order in time, so sorting by id is equivalent to sorting by created_at. Depending on the model, sorting on some fields might not be supported.

data
product
has_next
boolean
cursor_next
string (cursor)
Response Example (200 OK)
{
  "data": [
    {
      "id": "controller_ethernet",
      "name": "Controller Ethernet"
    }
  ],
  "has_next": true,
  "cursor_next": "i9vmCnOgONT2AjUMCn1K1N5cJg=="
}

Get product

GET /products/{product_id}
product_id
in path
string (product_id)

product ID

An instance of product
Response Example (200 OK)
{
  "id": "controller_ethernet",
  "name": "Controller Ethernet"
}

Devices

Physical Akiles device installed in the organization.

Each device has a set of capabilities which depend on the product.

The device object also contains an overview of the current device status (online/offline, battery level).

If hardware_id = null, the device is "virtual". It is designed for API testing: it behaves as close as possible to a real device in API calls but is not backed by a real hardware device. Gadget actions always succeed.

device

id
string (device_id)

device ID

created_at
string (date-time)

Creation time for this object.

name
string

Device name

organization_id
string (organization_id)

organization ID

site_id
string (site_id)

site ID

hardware_id
string (hardware_id)

Hardware ID. This is the serial number printed in the device's label, as a QR and in some products as text.

metadata
metadata

Key-value metadata

product_id
string

Product ID. NOTE: do not show this to the user directly, it's not capitalized properly. Use the name field from the product object instead.

revision_id
string

Hardware revision ID.

input_rules
device_input_rule

Input rule array. Input rules configure a device to remotely trigger gadget actions on other devices remotely when the user inputs a PIN or an NFC card. Must be empty if capabilities.input_rules = false

capabilities
device_capabilities

Device capabilities.

status
device_status

Device status.

is_deleted
boolean

True if this object has been deleted.

Example
{
  "id": "dev_3merk33gt3l525ryhcmh",
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "name": "string",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "site_id": "site_3merk33gt21kym11een1",
  "hardware_id": "hw_3merk33gt2a7grgmljhh",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  },
  "product_id": "controller_ethernet",
  "revision_id": "06",
  "input_rules": [
    {
      "code": "*2",
      "gadget_id": "gad_3merk33gt1hnl6pvbu71",
      "action_id": "open"
    }
  ],
  "capabilities": {
    "gadgets": "multiple",
    "gadget_max_count": 6,
    "script": true,
    "internet": true,
    "ethernet": true,
    "wifi": false,
    "cellular": false,
    "pin": false,
    "nfc": false,
    "input_rules": false,
    "mains": true,
    "battery": true,
    "battery_rechargeable": true
  },
  "status": {
    "online": true,
    "mains_present": true,
    "battery_present": true,
    "battery_charging": true,
    "battery_percent": 87.2
  },
  "is_deleted": false
}

device_capabilities

Device capabilities.

gadgets
string none, single, multiple

What level of gadget support this device has. none means it can't have gadgets, single means it MUST have exactly one gadget (not zero), multiple means it can have any (0..max).

gadget_max_count
integer

Maximum amount of gadgets the device supports. Will always be 0 if gadgets="none", and 1 if gadgets="single".

script
boolean

Whether this device supports gadgets with custom scripts.

internet
boolean

Whether this device supports some form of internet connection, which means it can connect to Akiles Cloud directly instead of through gateways.

ethernet
boolean

Whether this device supports Ethernet.

wifi
boolean

Whether this device supports WiFi.

cellular
boolean

Whether this device supports cellular internet.

pin
boolean

Whether this device supports PIN entry (i.e. has a keypad).

nfc
boolean

Whether this device supports NFC (either physical cards, or emulated cards in a phone)

input_rules
boolean

Whether this device supports input rules.

mains
boolean

Whether this device supports being powered from mains (either directly or with an AC adapter).

battery
boolean

Whether this device supports being powered by batteries (rechargeable or not). It is possible for both mains and battery be true at the same time, this means the device can be powered from mains or from a battery or both (if both are present it will give preference to mains and use the battery as a backup).

battery_rechargeable
boolean

If true, this device contains a rechargeable battery, and can recharge it from mains power.

Example
{
  "gadgets": "multiple",
  "gadget_max_count": 6,
  "script": true,
  "internet": true,
  "ethernet": true,
  "wifi": false,
  "cellular": false,
  "pin": false,
  "nfc": false,
  "input_rules": false,
  "mains": true,
  "battery": true,
  "battery_rechargeable": true
}

device_status

Device status.

online
boolean

True if this device is online (meaning it's on and has a working connection to Akiles Cloud, either directly or through gateways).

mains_present
boolean

True if this device currently has mains power.

battery_present
boolean

True if this device currently has a battery inserted.

battery_charging
boolean

True if this device currently is charging the battery from mains power.

battery_percent
number

Battery percentage, 0-100.

Example
{
  "online": true,
  "mains_present": true,
  "battery_present": true,
  "battery_charging": true,
  "battery_percent": 87.2
}

device_input_rule

Input rules allow configuring "codes" that act as prefixes for PINs or cards. This allows a single device to

  • Trigger different gadgets (local or remote).
  • Trigger different actions in a gadget.
  • Mix local and remote gadgets. One input rule can point to a local gadget (in this device), and another to a remote gadget (in another device, will be triggered via AkilesNet).

Example:

"input_rules": [
    {
        "code": "1",
        "gadget_id": "gad_3z4f8pubhdcj2fvfmkxh", // street
        "action_id": "open"
    },
    {
        "code": "2",
        "gadget_id": "gad_3z4f8s623va4l9nea7u1", // garage door
        "action_id": "up"
    },
    {
        "code": "3",
        "gadget_id": "gad_3z4f8s623va4l9nea7u1", // garage door
        "action_id": "down"
    },
    {
        "code": "4",
        "gadget_id": "gad_3z4f8s623va4l9nea7u1", // garage door
        "action_id": "stop"
    }
]

Typing "1" and your PIN, or typing "1" and tapping your card/phone would open the street door. Same with "2" would move the garage door up, "3" down, etc.

Keypad rule codes accept digits 0-9, hash # and star *.

You can define one keypad rule as the "default" one by setting the code to "". This is the rule that will be triggered if you type a PIN or tap a card directly without typing anything before. A default rule can coexist with other rules at the same time, but then all the other rules can't start with a digit (only with * or #), because it'd otherwise be ambiguous whether the user is typing a rule code or a PIN.

If no input rules are defined (input_rules is an empty array), pins/cards will open the first action of the first gadget in the current device.

code
string

Keypad code prefix. String consisting of digits 0-9, pound (#) and star (*). Can be empty, in which case this input rule is the default. If there is one input rule with empty code, the others must not start with a digit. To avoid ambiguity, no input rule can have a code that is the prefix of another one.

gadget_id
string (gadget_id)

Destination gadget ID. It can be a gadget on this device, or on another device.

action_id
string

Destination action ID. It must be a valid action in the destination gadget.

Example
{
  "code": "*2",
  "gadget_id": "gad_3merk33gt1hnl6pvbu71",
  "action_id": "open"
}

List devices

GET /devices
cursor
in query
string

Cursor value obtained from a previous request to the same endpoint. If not present, the first page of results is returned.

limit
in query
integer

Items per page.

sort
in query
string

Sort order. Field name, followed by :asc or :desc. Defaults to id:desc. In most models, IDs are assigned in increasing order in time, so sorting by id is equivalent to sorting by created_at. Depending on the model, sorting on some fields might not be supported.

data
device
has_next
boolean
cursor_next
string (cursor)
Response Example (200 OK)
{
  "data": [
    {
      "id": "dev_3merk33gt3l525ryhcmh",
      "created_at": "2024-03-13T16:56:51.766836837Z",
      "name": "string",
      "organization_id": "org_3merk33gt1v9ypgfzrp1",
      "site_id": "site_3merk33gt21kym11een1",
      "hardware_id": "hw_3merk33gt2a7grgmljhh",
      "metadata": {
        "key1": "value1",
        "key2": "value2"
      },
      "product_id": "controller_ethernet",
      "revision_id": "06",
      "input_rules": [
        {
          "code": "*2",
          "gadget_id": "gad_3merk33gt1hnl6pvbu71",
          "action_id": "open"
        }
      ],
      "capabilities": {
        "gadgets": "multiple",
        "gadget_max_count": 6,
        "script": true,
        "internet": true,
        "ethernet": true,
        "wifi": false,
        "cellular": false,
        "pin": false,
        "nfc": false,
        "input_rules": false,
        "mains": true,
        "battery": true,
        "battery_rechargeable": true
      },
      "status": {
        "online": true,
        "mains_present": true,
        "battery_present": true,
        "battery_charging": true,
        "battery_percent": 87.2
      },
      "is_deleted": false
    }
  ],
  "has_next": true,
  "cursor_next": "i9vmCnOgONT2AjUMCn1K1N5cJg=="
}

Get device

GET /devices/{device_id}
device_id
in path
string (device_id)

device ID

An instance of device
Response Example (200 OK)
{
  "id": "dev_3merk33gt3l525ryhcmh",
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "name": "string",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "site_id": "site_3merk33gt21kym11een1",
  "hardware_id": "hw_3merk33gt2a7grgmljhh",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  },
  "product_id": "controller_ethernet",
  "revision_id": "06",
  "input_rules": [
    {
      "code": "*2",
      "gadget_id": "gad_3merk33gt1hnl6pvbu71",
      "action_id": "open"
    }
  ],
  "capabilities": {
    "gadgets": "multiple",
    "gadget_max_count": 6,
    "script": true,
    "internet": true,
    "ethernet": true,
    "wifi": false,
    "cellular": false,
    "pin": false,
    "nfc": false,
    "input_rules": false,
    "mains": true,
    "battery": true,
    "battery_rechargeable": true
  },
  "status": {
    "online": true,
    "mains_present": true,
    "battery_present": true,
    "battery_charging": true,
    "battery_percent": 87.2
  },
  "is_deleted": false
}

Edit device

PATCH /devices/{device_id}
device_id
in path
string (device_id)

device ID

metadata
metadata

Key-value metadata

Request Example
{
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}
An instance of device
Response Example (200 OK)
{
  "id": "dev_3merk33gt3l525ryhcmh",
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "name": "string",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "site_id": "site_3merk33gt21kym11een1",
  "hardware_id": "hw_3merk33gt2a7grgmljhh",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  },
  "product_id": "controller_ethernet",
  "revision_id": "06",
  "input_rules": [
    {
      "code": "*2",
      "gadget_id": "gad_3merk33gt1hnl6pvbu71",
      "action_id": "open"
    }
  ],
  "capabilities": {
    "gadgets": "multiple",
    "gadget_max_count": 6,
    "script": true,
    "internet": true,
    "ethernet": true,
    "wifi": false,
    "cellular": false,
    "pin": false,
    "nfc": false,
    "input_rules": false,
    "mains": true,
    "battery": true,
    "battery_rechargeable": true
  },
  "status": {
    "online": true,
    "mains_present": true,
    "battery_present": true,
    "battery_charging": true,
    "battery_percent": 87.2
  },
  "is_deleted": false
}

Gadgets

A gadget is a "thing that can be controlled" by the user. Gadgets can be doors, lights, heating, window blinds...

Gadgets are NOT physical devices. Some devices can have multiple gadgets because they have multiple inputs/outputs so they can control multiple things (Controllers). Others can have only one (the Cylinder and the Roomlock, for example). Others can have none (the Gateway Ethernet, for example.

Gadgets can have multiple actions depending on what they are. For example, a normal door will just have an open action, but a blind\ncan have raise and lower actions, and a light can have on and off actions. Actions are identified by their string ID, and they\nalso have a human-friendly name for display in the UI.

gadget_action

id
string

action ID

name
string

Action user-friendly name

Example
{
  "id": "open",
  "name": "Open"
}

gadget

id
string (gadget_id)

gadget ID

organization_id
string (organization_id)

organization ID

site_id
string (site_id)

site ID

device_id
string (device_id)

device ID

name
string

Gadget name

actions
gadget_action
is_deleted
boolean

True if this object has been deleted.

created_at
string (date-time)

Creation time for this object.

metadata
metadata

Key-value metadata

Example
{
  "id": "gad_3merk33gt1hnl6pvbu71",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "site_id": "site_3merk33gt21kym11een1",
  "device_id": "dev_3merk33gt3l525ryhcmh",
  "name": "string",
  "actions": [
    {
      "id": "open",
      "name": "Open"
    }
  ],
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

List gadgets

GET /gadgets
cursor
in query
string

Cursor value obtained from a previous request to the same endpoint. If not present, the first page of results is returned.

limit
in query
integer

Items per page.

sort
in query
string

Sort order. Field name, followed by :asc or :desc. Defaults to id:desc. In most models, IDs are assigned in increasing order in time, so sorting by id is equivalent to sorting by created_at. Depending on the model, sorting on some fields might not be supported.

data
gadget
has_next
boolean
cursor_next
string (cursor)
Response Example (200 OK)
{
  "data": [
    {
      "id": "gad_3merk33gt1hnl6pvbu71",
      "organization_id": "org_3merk33gt1v9ypgfzrp1",
      "site_id": "site_3merk33gt21kym11een1",
      "device_id": "dev_3merk33gt3l525ryhcmh",
      "name": "string",
      "actions": [
        {
          "id": "open",
          "name": "Open"
        }
      ],
      "is_deleted": false,
      "created_at": "2024-03-13T16:56:51.766836837Z",
      "metadata": {
        "key1": "value1",
        "key2": "value2"
      }
    }
  ],
  "has_next": true,
  "cursor_next": "i9vmCnOgONT2AjUMCn1K1N5cJg=="
}

Get gadget

GET /gadgets/{gadget_id}
gadget_id
in path
string (gadget_id)

gadget ID

An instance of gadget
Response Example (200 OK)
{
  "id": "gad_3merk33gt1hnl6pvbu71",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "site_id": "site_3merk33gt21kym11een1",
  "device_id": "dev_3merk33gt3l525ryhcmh",
  "name": "string",
  "actions": [
    {
      "id": "open",
      "name": "Open"
    }
  ],
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

Edit gadget

PATCH /gadgets/{gadget_id}
gadget_id
in path
string (gadget_id)

gadget ID

metadata
metadata

Key-value metadata

Request Example
{
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}
An instance of gadget
Response Example (200 OK)
{
  "id": "gad_3merk33gt1hnl6pvbu71",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "site_id": "site_3merk33gt21kym11een1",
  "device_id": "dev_3merk33gt3l525ryhcmh",
  "name": "string",
  "actions": [
    {
      "id": "open",
      "name": "Open"
    }
  ],
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

Do gadget action

POST /gadgets/{gadget_id}/actions/{action_id}

This endpoint is only available to Akiles customers that have the whitelabel integration feature which has extra cost. If you want to make your integration public we recommend using the non whitelabel option.

gadget_id
in path
string (gadget_id)

gadget ID

action_id
in path
string

gadget action ID

Request Example
{}
Response Example (200 OK)
{}

Members

member

id
string (member_id)

member ID

organization_id
string (organization_id)

organization ID

name
string

Member name shown in the admin panel. This is for the organization admins to identify the member, it's never shown to the user.

starts_at
string (date-time)

Start date of the member's access. If null, the access is valid immediately.

ends_at
string (date-time)

End date of the member's access. If null, the access is valid forever (ie, until an end date is set or the member is deleted.)

is_deleted
boolean

True if this object has been deleted.

created_at
string (date-time)

Creation time for this object.

metadata
metadata

Key-value metadata

Example
{
  "id": "mem_3merk33gt7ml3tde71f3",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "name": "John Doe",
  "starts_at": "2024-03-13T16:56:51.766836837Z",
  "ends_at": "2024-03-13T16:56:51.766836837Z",
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

member_email

id
string (member_email_id)

member email ID

organization_id
string (organization_id)

organization ID

member_id
string (member_id)

member ID

email
string (email_address)

Email address

is_deleted
boolean

True if this object has been deleted.

created_at
string (date-time)

Creation time for this object.

metadata
metadata

Key-value metadata

Example
{
  "id": "me_3rkd7ya2pnjysjqbluj1",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "member_id": "mem_3merk33gt7ml3tde71f3",
  "email": "hello@example.com",
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

member_pin

id
string (member_pin_id)

member pin ID

organization_id
string (organization_id)

organization ID

member_id
string (member_id)

member ID

length
integer

Pin length

is_deleted
boolean

True if this object has been deleted.

created_at
string (date-time)

Creation time for this object.

metadata
metadata

Key-value metadata

Example
{
  "id": "mp_3rkd2fmgmmdh1dgkvluh",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "member_id": "mem_3merk33gt7ml3tde71f3",
  "length": 6,
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

member_pin_revealed

pin
string

Pin number.

id
string (member_pin_id)

member pin ID

organization_id
string (organization_id)

organization ID

member_id
string (member_id)

member ID

length
integer

Pin length

is_deleted
boolean

True if this object has been deleted.

created_at
string (date-time)

Creation time for this object.

metadata
metadata

Key-value metadata

Example
{
  "pin": "123456",
  "id": "mp_3rkd2fmgmmdh1dgkvluh",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "member_id": "mem_3merk33gt7ml3tde71f3",
  "length": 6,
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

member_card

id
string (member_card_id)

member card ID

organization_id
string (organization_id)

organization ID

member_id
string (member_id)

member ID

card_id
string (card_id)

card ID

is_deleted
boolean

True if this object has been deleted.

created_at
string (date-time)

Creation time for this object.

metadata
metadata

Key-value metadata

Example
{
  "id": "mc_3yd55tbrelfxhjjvkdqh",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "member_id": "mem_3merk33gt7ml3tde71f3",
  "card_id": "crd_3ykdnnld7d57x83eqcm1",
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

card

id
string (card_id)

Card ID

uid
string

ISO14443 UID for the card. Always 4, 7 or 10 bytes, encoded in hex.

printed_code
string

Unique code physically printed on the card.

created_at
string (date-time)

Creation time for this object.

Example
{
  "id": "crd_3ykdnnld7d57x83eqcm1",
  "uid": "041E53F2FF6780",
  "printed_code": "WHJSA",
  "created_at": "2024-03-13T16:56:51.766836837Z"
}

member_group_association

id
string (member_group_association_id)

member group association ID

organization_id
string (organization_id)

organization ID

member_id
string (member_id)

member ID

member_group_id
string (member_group_id)

member_group ID

starts_at
string (date-time)

Start date of the member's access. If null, the access is valid immediately.

ends_at
string (date-time)

End date of the member's access. If null, the access is valid forever (ie, until an end date is set or the member is deleted.)

is_deleted
boolean

True if this object has been deleted.

created_at
string (date-time)

Creation time for this object.

metadata
metadata

Key-value metadata

Example
{
  "id": "mga_3rkd81x2hc3qluv56pl1",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "member_id": "mem_3merk33gt7ml3tde71f3",
  "member_group_id": "mg_3merk33gt1692dk2p2m1",
  "starts_at": "2024-03-13T16:56:51.766836837Z",
  "ends_at": "2024-03-13T16:56:51.766836837Z",
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

List members

GET /members
cursor
in query
string

Cursor value obtained from a previous request to the same endpoint. If not present, the first page of results is returned.

limit
in query
integer

Items per page.

sort
in query
string

Sort order. Field name, followed by :asc or :desc. Defaults to id:desc. In most models, IDs are assigned in increasing order in time, so sorting by id is equivalent to sorting by created_at. Depending on the model, sorting on some fields might not be supported.

is_deleted
in query
string true, false, any

Filter by deletion status

email
in query
string

Filter by email address

metadata.source
in query
string

Filter by metadata.source

metadata.sourceID
in query
string

Filter by metadata.sourceID

data
member
has_next
boolean
cursor_next
string (cursor)
Response Example (200 OK)
{
  "data": [
    {
      "id": "mem_3merk33gt7ml3tde71f3",
      "organization_id": "org_3merk33gt1v9ypgfzrp1",
      "name": "John Doe",
      "starts_at": "2024-03-13T16:56:51.766836837Z",
      "ends_at": "2024-03-13T16:56:51.766836837Z",
      "is_deleted": false,
      "created_at": "2024-03-13T16:56:51.766836837Z",
      "metadata": {
        "key1": "value1",
        "key2": "value2"
      }
    }
  ],
  "has_next": true,
  "cursor_next": "i9vmCnOgONT2AjUMCn1K1N5cJg=="
}

Create member

POST /members
name
string

Member name shown in the admin panel. This is for the organization admins to identify the member, it's never shown to the user.

starts_at
string (date-time)

Start date of the member's access. If null, the access is valid immediately.

ends_at
string (date-time)

End date of the member's access. If null, the access is valid forever (ie, until an end date is set or the member is deleted.)

metadata
metadata

Key-value metadata

Request Example
{
  "name": "John Doe",
  "starts_at": "2024-03-13T16:56:51.766836837Z",
  "ends_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}
An instance of member
Response Example (200 OK)
{
  "id": "mem_3merk33gt7ml3tde71f3",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "name": "John Doe",
  "starts_at": "2024-03-13T16:56:51.766836837Z",
  "ends_at": "2024-03-13T16:56:51.766836837Z",
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

Get member

GET /members/{member_id}
member_id
in path
string (member_id)

member ID

An instance of member
Response Example (200 OK)
{
  "id": "mem_3merk33gt7ml3tde71f3",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "name": "John Doe",
  "starts_at": "2024-03-13T16:56:51.766836837Z",
  "ends_at": "2024-03-13T16:56:51.766836837Z",
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

Edit member

PATCH /members/{member_id}
member_id
in path
string (member_id)

member ID

name
string

Member name shown in the admin panel. This is for the organization admins to identify the member, it's never shown to the user.

starts_at
string (date-time)

Start date of the member's access. If null, the access is valid immediately.

ends_at
string (date-time)

End date of the member's access. If null, the access is valid forever (ie, until an end date is set or the member is deleted.)

is_deleted
boolean

True if this object has been deleted.

metadata
metadata

Key-value metadata

Request Example
{
  "name": "John Doe",
  "starts_at": "2024-03-13T16:56:51.766836837Z",
  "ends_at": "2024-03-13T16:56:51.766836837Z",
  "is_deleted": false,
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}
An instance of member
Response Example (200 OK)
{
  "id": "mem_3merk33gt7ml3tde71f3",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "name": "John Doe",
  "starts_at": "2024-03-13T16:56:51.766836837Z",
  "ends_at": "2024-03-13T16:56:51.766836837Z",
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

Delete member

DELETE /members/{member_id}
member_id
in path
string (member_id)

member ID

An instance of member
Response Example (200 OK)
{
  "id": "mem_3merk33gt7ml3tde71f3",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "name": "John Doe",
  "starts_at": "2024-03-13T16:56:51.766836837Z",
  "ends_at": "2024-03-13T16:56:51.766836837Z",
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

List emails

GET /members/{member_id}/emails
cursor
in query
string

Cursor value obtained from a previous request to the same endpoint. If not present, the first page of results is returned.

limit
in query
integer

Items per page.

q
in query
string

Search parameter

sort
in query
string

Sort order. Field name, followed by :asc or :desc. Defaults to id:desc. In most models, IDs are assigned in increasing order in time, so sorting by id is equivalent to sorting by created_at. Depending on the model, sorting on some fields might not be supported.

member_id
in path
string (member_id)

member ID

data
member_email
has_next
boolean
cursor_next
string (cursor)
Response Example (200 OK)
{
  "data": [
    {
      "id": "me_3rkd7ya2pnjysjqbluj1",
      "organization_id": "org_3merk33gt1v9ypgfzrp1",
      "member_id": "mem_3merk33gt7ml3tde71f3",
      "email": "hello@example.com",
      "is_deleted": false,
      "created_at": "2024-03-13T16:56:51.766836837Z",
      "metadata": {
        "key1": "value1",
        "key2": "value2"
      }
    }
  ],
  "has_next": true,
  "cursor_next": "i9vmCnOgONT2AjUMCn1K1N5cJg=="
}

Create email

POST /members/{member_id}/emails
member_id
in path
string (member_id)

member ID

email
string (email)

Email address

metadata
metadata

Key-value metadata

Request Example
{
  "email": "string (email)",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}
An instance of member_email
Response Example (200 OK)
{
  "id": "me_3rkd7ya2pnjysjqbluj1",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "member_id": "mem_3merk33gt7ml3tde71f3",
  "email": "hello@example.com",
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

Get email

GET /members/{member_id}/emails/{member_email_id}
member_id
in path
string (member_id)

member ID

member_email_id
in path
string (member_email_id)

member email ID

An instance of member_email
Response Example (200 OK)
{
  "id": "me_3rkd7ya2pnjysjqbluj1",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "member_id": "mem_3merk33gt7ml3tde71f3",
  "email": "hello@example.com",
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

Edit email

PATCH /members/{member_id}/emails/{member_email_id}
member_id
in path
string (member_id)

member ID

member_email_id
in path
string (member_email_id)

member email ID

metadata
metadata

Key-value metadata

Request Example
{
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}
An instance of member_email
Response Example (200 OK)
{
  "id": "me_3rkd7ya2pnjysjqbluj1",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "member_id": "mem_3merk33gt7ml3tde71f3",
  "email": "hello@example.com",
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

Delete email

DELETE /members/{member_id}/emails/{member_email_id}
member_id
in path
string (member_id)

member ID

member_email_id
in path
string (member_email_id)

member email ID

An instance of member_email
Response Example (200 OK)
{
  "id": "me_3rkd7ya2pnjysjqbluj1",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "member_id": "mem_3merk33gt7ml3tde71f3",
  "email": "hello@example.com",
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

List pins

GET /members/{member_id}/pins
cursor
in query
string

Cursor value obtained from a previous request to the same endpoint. If not present, the first page of results is returned.

limit
in query
integer

Items per page.

sort
in query
string

Sort order. Field name, followed by :asc or :desc. Defaults to id:desc. In most models, IDs are assigned in increasing order in time, so sorting by id is equivalent to sorting by created_at. Depending on the model, sorting on some fields might not be supported.

member_id
in path
string (member_id)

member ID

data
member_pin
has_next
boolean
cursor_next
string (cursor)
Response Example (200 OK)
{
  "data": [
    {
      "id": "mp_3rkd2fmgmmdh1dgkvluh",
      "organization_id": "org_3merk33gt1v9ypgfzrp1",
      "member_id": "mem_3merk33gt7ml3tde71f3",
      "length": 6,
      "is_deleted": false,
      "created_at": "2024-03-13T16:56:51.766836837Z",
      "metadata": {
        "key1": "value1",
        "key2": "value2"
      }
    }
  ],
  "has_next": true,
  "cursor_next": "i9vmCnOgONT2AjUMCn1K1N5cJg=="
}

Create pin

POST /members/{member_id}/pins

When creating a PIN, you can specify it in 3 ways depending on the fields you send in the request:

  • No fields: Akiles generated a random PIN, of the length configured in the organization's settings.
  • length only: Akiles generated a random PIN of the length you specify.
  • pin only: The PIN is the one you specify.

The generated random PIN is included in the response.

It is not allowed to have two (non-deleted) members with the same PIN in a given organization. For this reason, we recommend you let Akiles generate a random PIN instead of generating one yourself if you can (Akiles generates a PIN that is different to all existing members). If you specify the PIN directly, you have to ensure there's no PIN collisions yourself.

member_id
in path
string (member_id)

member ID

length
integer

Pin length. Optional (you can either not specify it, or set it to null).

pin
string

Pin number. Optional (you can either not specify it, or set it to null).

metadata
metadata

Key-value metadata

Request Example
{
  "length": 6,
  "pin": "123456",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}
An instance of member_pin_revealed
Response Example (200 OK)
{
  "pin": "123456",
  "id": "mp_3rkd2fmgmmdh1dgkvluh",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "member_id": "mem_3merk33gt7ml3tde71f3",
  "length": 6,
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

Get pin

GET /members/{member_id}/pins/{member_pin_id}
member_id
in path
string (member_id)

member ID

member_pin_id
in path
string (member_pin_id)

member pin ID

An instance of member_pin
Response Example (200 OK)
{
  "id": "mp_3rkd2fmgmmdh1dgkvluh",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "member_id": "mem_3merk33gt7ml3tde71f3",
  "length": 6,
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

Edit pin

PATCH /members/{member_id}/pins/{member_pin_id}

NOTE: It is not possible to edit a MemberPin to change a PIN. Instead, delete it and create a new one.

member_id
in path
string (member_id)

member ID

member_pin_id
in path
string (member_pin_id)

member pin ID

metadata
metadata

Key-value metadata

Request Example
{
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}
An instance of member_pin
Response Example (200 OK)
{
  "id": "mp_3rkd2fmgmmdh1dgkvluh",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "member_id": "mem_3merk33gt7ml3tde71f3",
  "length": 6,
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

Delete pin

DELETE /members/{member_id}/pins/{member_pin_id}
member_id
in path
string (member_id)

member ID

member_pin_id
in path
string (member_pin_id)

member pin ID

An instance of member_pin
Response Example (200 OK)
{
  "id": "mp_3rkd2fmgmmdh1dgkvluh",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "member_id": "mem_3merk33gt7ml3tde71f3",
  "length": 6,
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

Reveal pin

POST /members/{member_id}/pins/{member_pin_id}/reveal
member_id
in path
string (member_id)

member ID

member_pin_id
in path
string (member_pin_id)

member pin ID

Request Example
{}
An instance of member_pin_revealed
Response Example (200 OK)
{
  "pin": "123456",
  "id": "mp_3rkd2fmgmmdh1dgkvluh",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "member_id": "mem_3merk33gt7ml3tde71f3",
  "length": 6,
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

List cards

GET /members/{member_id}/cards
cursor
in query
string

Cursor value obtained from a previous request to the same endpoint. If not present, the first page of results is returned.

limit
in query
integer

Items per page.

sort
in query
string

Sort order. Field name, followed by :asc or :desc. Defaults to id:desc. In most models, IDs are assigned in increasing order in time, so sorting by id is equivalent to sorting by created_at. Depending on the model, sorting on some fields might not be supported.

member_id
in path
string (member_id)

member ID

data
member_card
has_next
boolean
cursor_next
string (cursor)
Response Example (200 OK)
{
  "data": [
    {
      "id": "mc_3yd55tbrelfxhjjvkdqh",
      "organization_id": "org_3merk33gt1v9ypgfzrp1",
      "member_id": "mem_3merk33gt7ml3tde71f3",
      "card_id": "crd_3ykdnnld7d57x83eqcm1",
      "is_deleted": false,
      "created_at": "2024-03-13T16:56:51.766836837Z",
      "metadata": {
        "key1": "value1",
        "key2": "value2"
      }
    }
  ],
  "has_next": true,
  "cursor_next": "i9vmCnOgONT2AjUMCn1K1N5cJg=="
}

Create card

POST /members/{member_id}/cards
member_id
in path
string (member_id)

member ID

card_id
string (card_id)

Card ID. Optional. One of card_id or printed_code must be present.

printed_code
string

Unique code physically printed on the card. Optional. One of card_id or printed_code must be present.

metadata
metadata

Key-value metadata

Request Example
{
  "card_id": "crd_3ykdnnld7d57x83eqcm1",
  "printed_code": "WHJSA",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}
An instance of member_card
Response Example (200 OK)
{
  "id": "mc_3yd55tbrelfxhjjvkdqh",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "member_id": "mem_3merk33gt7ml3tde71f3",
  "card_id": "crd_3ykdnnld7d57x83eqcm1",
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

Get card

GET /members/{member_id}/cards/{member_card_id}
member_id
in path
string (member_id)

member ID

member_card_id
in path
string (member_card_id)

member card ID

An instance of member_card
Response Example (200 OK)
{
  "id": "mc_3yd55tbrelfxhjjvkdqh",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "member_id": "mem_3merk33gt7ml3tde71f3",
  "card_id": "crd_3ykdnnld7d57x83eqcm1",
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

Edit card

PATCH /members/{member_id}/cards/{member_card_id}
member_id
in path
string (member_id)

member ID

member_card_id
in path
string (member_card_id)

member card ID

metadata
metadata

Key-value metadata

Request Example
{
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}
An instance of member_card
Response Example (200 OK)
{
  "id": "mc_3yd55tbrelfxhjjvkdqh",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "member_id": "mem_3merk33gt7ml3tde71f3",
  "card_id": "crd_3ykdnnld7d57x83eqcm1",
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

Delete card

DELETE /members/{member_id}/cards/{member_card_id}
member_id
in path
string (member_id)

member ID

member_card_id
in path
string (member_card_id)

member card ID

An instance of member_card
Response Example (200 OK)
{
  "id": "mc_3yd55tbrelfxhjjvkdqh",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "member_id": "mem_3merk33gt7ml3tde71f3",
  "card_id": "crd_3ykdnnld7d57x83eqcm1",
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

List group associations

GET /members/{member_id}/group_associations
cursor
in query
string

Cursor value obtained from a previous request to the same endpoint. If not present, the first page of results is returned.

limit
in query
integer

Items per page.

sort
in query
string

Sort order. Field name, followed by :asc or :desc. Defaults to id:desc. In most models, IDs are assigned in increasing order in time, so sorting by id is equivalent to sorting by created_at. Depending on the model, sorting on some fields might not be supported.

member_id
in path
string (member_id)

member ID

data
member_group_association
has_next
boolean
cursor_next
string (cursor)
Response Example (200 OK)
{
  "data": [
    {
      "id": "mga_3rkd81x2hc3qluv56pl1",
      "organization_id": "org_3merk33gt1v9ypgfzrp1",
      "member_id": "mem_3merk33gt7ml3tde71f3",
      "member_group_id": "mg_3merk33gt1692dk2p2m1",
      "starts_at": "2024-03-13T16:56:51.766836837Z",
      "ends_at": "2024-03-13T16:56:51.766836837Z",
      "is_deleted": false,
      "created_at": "2024-03-13T16:56:51.766836837Z",
      "metadata": {
        "key1": "value1",
        "key2": "value2"
      }
    }
  ],
  "has_next": true,
  "cursor_next": "i9vmCnOgONT2AjUMCn1K1N5cJg=="
}

Create group association

POST /members/{member_id}/group_associations
member_id
in path
string (member_id)

member ID

member_group_id
string (member_group_id)

member_group ID

starts_at
string (date-time)

Start date of the member's access. If null, the access is valid immediately.

ends_at
string (date-time)

End date of the member's access. If null, the access is valid forever (ie, until an end date is set or the member is deleted.)

metadata
metadata

Key-value metadata

Request Example
{
  "member_group_id": "mg_3merk33gt1692dk2p2m1",
  "starts_at": "2024-03-13T16:56:51.766836837Z",
  "ends_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}
An instance of member_group_association
Response Example (200 OK)
{
  "id": "mga_3rkd81x2hc3qluv56pl1",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "member_id": "mem_3merk33gt7ml3tde71f3",
  "member_group_id": "mg_3merk33gt1692dk2p2m1",
  "starts_at": "2024-03-13T16:56:51.766836837Z",
  "ends_at": "2024-03-13T16:56:51.766836837Z",
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

Get group association

GET /members/{member_id}/group_associations/{member_group_association_id}
member_id
in path
string (member_id)

member ID

member_group_association_id
in path
string (member_group_association_id)

member group association ID

An instance of member_group_association
Response Example (200 OK)
{
  "id": "mga_3rkd81x2hc3qluv56pl1",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "member_id": "mem_3merk33gt7ml3tde71f3",
  "member_group_id": "mg_3merk33gt1692dk2p2m1",
  "starts_at": "2024-03-13T16:56:51.766836837Z",
  "ends_at": "2024-03-13T16:56:51.766836837Z",
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

Edit group association

PATCH /members/{member_id}/group_associations/{member_group_association_id}
member_id
in path
string (member_id)

member ID

member_group_association_id
in path
string (member_group_association_id)

member group association ID

starts_at
string (date-time)

Start date of the member's access. If null, the access is valid immediately.

ends_at
string (date-time)

End date of the member's access. If null, the access is valid forever (ie, until an end date is set or the member is deleted.)

metadata
metadata

Key-value metadata

Request Example
{
  "starts_at": "2024-03-13T16:56:51.766836837Z",
  "ends_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}
An instance of member_group_association
Response Example (200 OK)
{
  "id": "mga_3rkd81x2hc3qluv56pl1",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "member_id": "mem_3merk33gt7ml3tde71f3",
  "member_group_id": "mg_3merk33gt1692dk2p2m1",
  "starts_at": "2024-03-13T16:56:51.766836837Z",
  "ends_at": "2024-03-13T16:56:51.766836837Z",
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

Delete group association

DELETE /members/{member_id}/group_associations/{member_group_association_id}
member_id
in path
string (member_id)

member ID

member_group_association_id
in path
string (member_group_association_id)

member group association ID

An instance of member_group_association
Response Example (200 OK)
{
  "id": "mga_3rkd81x2hc3qluv56pl1",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "member_id": "mem_3merk33gt7ml3tde71f3",
  "member_group_id": "mg_3merk33gt1692dk2p2m1",
  "starts_at": "2024-03-13T16:56:51.766836837Z",
  "ends_at": "2024-03-13T16:56:51.766836837Z",
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

Member groups

member_group

id
string (member_group_id)

member_group ID

organization_id
string (organization_id)

organization ID

name
string

Group name

permissions
member_group_permission_rule

Permission rules granted to members of this group. A member can have multiple groups, and the permissions are the union of all the groups. Access is granted if at least one rule matches.

is_deleted
boolean

True if this object has been deleted.

created_at
string (date-time)

Creation time for this object.

metadata
metadata

Key-value metadata

Example
{
  "id": "mg_3merk33gt1692dk2p2m1",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "name": "string",
  "permissions": [
    {
      "site_id": "site_3merk33gt21kym11een1",
      "gadget_id": "gad_3merk33gt1hnl6pvbu71",
      "action_id": "string",
      "schedule_id": "sch_3merk33gt21kyz1f3z1",
      "presence": "string"
    }
  ],
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

member_group_permission_rule

Rule granting permissions to a member group. Examples:

  • {} matches all gadgets in all sites
  • {"site_id": "site_3merk33gt21kym11een1"} matches all gadgets in that site.
  • {"gadget_id": "gad_3merk33gt1hnl6pvbu71"} matches that gadget only.

It is not allowed to set site_id and gadget_id at the same time.

site_id
string (site_id)

Site ID. If set, the rule only matches gadgets in this site.

gadget_id
string (gadget_id)

Gadget ID. If set, the rule only matches this gadget.

action_id
string

Action ID. If set, the rule only matches this particular action within the gadget.

Only one action can be chosen. If you want to allow multiple actions in the same gadget, create one permission rule for each action.

If you set this, gadget_id must be set. That is, it's not possible to have a permission rule like "All gadgets in site X, but only action Y".

schedule_id
string (schedule_id)

Schedule ID. If set, the rule only matches if the current time is within the schedule.

presence
string none, gps

Presence check performed.

  • none: no presence check is done, the user can perform gadget actions using the Akiles app remotely, from anywhere in the world.
  • gps: the user must be within the site's GPS radius to perform gadget actions.

Actions via PINs, cards and Bluetooth are always allowed and not affected by this setting.

Example
{
  "site_id": "site_3merk33gt21kym11een1",
  "gadget_id": "gad_3merk33gt1hnl6pvbu71",
  "action_id": "string",
  "schedule_id": "sch_3merk33gt21kyz1f3z1",
  "presence": "string"
}

List member groups

GET /member_groups
cursor
in query
string

Cursor value obtained from a previous request to the same endpoint. If not present, the first page of results is returned.

limit
in query
integer

Items per page.

q
in query
string

Search parameter

sort
in query
string

Sort order. Field name, followed by :asc or :desc. Defaults to id:desc. In most models, IDs are assigned in increasing order in time, so sorting by id is equivalent to sorting by created_at. Depending on the model, sorting on some fields might not be supported.

data
member_group
has_next
boolean
cursor_next
string (cursor)
Response Example (200 OK)
{
  "data": [
    {
      "id": "mg_3merk33gt1692dk2p2m1",
      "organization_id": "org_3merk33gt1v9ypgfzrp1",
      "name": "string",
      "permissions": [
        {
          "site_id": "site_3merk33gt21kym11een1",
          "gadget_id": "gad_3merk33gt1hnl6pvbu71",
          "action_id": "string",
          "schedule_id": "sch_3merk33gt21kyz1f3z1",
          "presence": "string"
        }
      ],
      "is_deleted": false,
      "created_at": "2024-03-13T16:56:51.766836837Z",
      "metadata": {
        "key1": "value1",
        "key2": "value2"
      }
    }
  ],
  "has_next": true,
  "cursor_next": "i9vmCnOgONT2AjUMCn1K1N5cJg=="
}

Create member group

POST /member_groups
name
string

Group name

permissions
member_group_permission_rule

Permission rules granted to members of this group. A member can have multiple groups, and the permissions are the union of all the groups. Access is granted if at least one rule matches.

metadata
metadata

Key-value metadata

Request Example
{
  "name": "string",
  "permissions": [
    {
      "site_id": "site_3merk33gt21kym11een1",
      "gadget_id": "gad_3merk33gt1hnl6pvbu71",
      "action_id": "string",
      "schedule_id": "sch_3merk33gt21kyz1f3z1",
      "presence": "string"
    }
  ],
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}
An instance of member_group
Response Example (200 OK)
{
  "id": "mg_3merk33gt1692dk2p2m1",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "name": "string",
  "permissions": [
    {
      "site_id": "site_3merk33gt21kym11een1",
      "gadget_id": "gad_3merk33gt1hnl6pvbu71",
      "action_id": "string",
      "schedule_id": "sch_3merk33gt21kyz1f3z1",
      "presence": "string"
    }
  ],
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

Get member group

GET /member_groups/{member_group_id}
member_group_id
in path
string (member_group_id)

member_group ID

An instance of member_group
Response Example (200 OK)
{
  "id": "mg_3merk33gt1692dk2p2m1",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "name": "string",
  "permissions": [
    {
      "site_id": "site_3merk33gt21kym11een1",
      "gadget_id": "gad_3merk33gt1hnl6pvbu71",
      "action_id": "string",
      "schedule_id": "sch_3merk33gt21kyz1f3z1",
      "presence": "string"
    }
  ],
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

Edit member group

PATCH /member_groups/{member_group_id}
member_group_id
in path
string (member_group_id)

member_group ID

name
string

Group name

permissions
member_group_permission_rule

Permission rules granted to members of this group. A member can have multiple groups, and the permissions are the union of all the groups. Access is granted if at least one rule matches.

metadata
metadata

Key-value metadata

Request Example
{
  "name": "string",
  "permissions": [
    {
      "site_id": "site_3merk33gt21kym11een1",
      "gadget_id": "gad_3merk33gt1hnl6pvbu71",
      "action_id": "string",
      "schedule_id": "sch_3merk33gt21kyz1f3z1",
      "presence": "string"
    }
  ],
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}
An instance of member_group
Response Example (200 OK)
{
  "id": "mg_3merk33gt1692dk2p2m1",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "name": "string",
  "permissions": [
    {
      "site_id": "site_3merk33gt21kym11een1",
      "gadget_id": "gad_3merk33gt1hnl6pvbu71",
      "action_id": "string",
      "schedule_id": "sch_3merk33gt21kyz1f3z1",
      "presence": "string"
    }
  ],
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

Delete member group

DELETE /member_groups/{member_group_id}
member_group_id
in path
string (member_group_id)

member_group ID

An instance of member_group
Response Example (200 OK)
{
  "id": "mg_3merk33gt1692dk2p2m1",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "name": "string",
  "permissions": [
    {
      "site_id": "site_3merk33gt21kym11een1",
      "gadget_id": "gad_3merk33gt1hnl6pvbu71",
      "action_id": "string",
      "schedule_id": "sch_3merk33gt21kyz1f3z1",
      "presence": "string"
    }
  ],
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}

Events

Everything that happens in an Akiles organization is logged as an Event.

Events are structured in "subject - verb - object" form:

  • Subject: who did the action. It can be a member, an administrator, an API key, an OAuth application...
  • Verb: what action was done on the object. Create, edit, delete, use...
  • Object: what object the action was done on. A member, a gadget...

For example:

  • gadget action events (i.e. someone opening a door) are reported with the member in the subject, use as verb, and the gadget action as object.
  • Member editions are reported with the admin user who did the action in the subject, edit as the verb, and the member as the object.

Offline delayed events

Gadget action events originate from the Akiles device when the user uses PINs, NFC cards or phone, or BT from a phone to open. If the Akiles device is offline (i.e. has no connection to Akiles Cloud), it buffers events in local persistent memory and reports them later when it becomes online again. When this happens, the event appears with the created_at corresponding to the moment it originally happened, NOT the moment when it was reported.

event

id
string (event_id)

event ID

organization_id
string (organization_id)

organization ID

subject
event_subject
verb
string create, edit, delete, use
object
event_object
created_at
string (date-time)

Creation time for this object.

Example
{
  "id": "evt_3merk33gt21kym11een1",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "subject": {
    "member_id": "mem_3merk33gt7ml3tde71f3",
    "member_email_id": "me_3rkd7ya2pnjysjqbluj1",
    "member_magic_link_id": "mml_3rkd2f2mv925ptvpgblh",
    "member_pin_id": "mp_3rkd2fmgmmdh1dgkvluh",
    "member_card_id": "mc_3yd55tbrelfxhjjvkdqh",
    "member_sdk_token_id": "mst_3yd55tbrelfxhjjvkdqh"
  },
  "verb": "use",
  "object": {
    "type": "gadget_action",
    "device_id": "dev_3merk33gt3l525ryhcmh",
    "gadget_id": "gad_3merk33gt1hnl6pvbu71",
    "gadget_action_id": "open",
    "member_id": "mem_3merk33gt7ml3tde71f3",
    "member_email_id": "me_3rkd7ya2pnjysjqbluj1",
    "member_magic_link_id": "mml_3rkd2f2mv925ptvpgblh",
    "member_group_id": "mg_3merk33gt1692dk2p2m1",
    "member_group_association_id": "mga_3rkd81x2hc3qluv56pl1",
    "member_pin_id": "mp_3rkd2fmgmmdh1dgkvluh",
    "member_card_id": "mc_3yd55tbrelfxhjjvkdqh",
    "member_sdk_token_id": "mst_3yd55tbrelfxhjjvkdqh",
    "organization_id": "org_3merk33gt1v9ypgfzrp1",
    "site_id": "site_3merk33gt21kym11een1",
    "webhook_id": "wh_3merk33gt21kyz1f3z1",
    "hardware_id": "hw_3merk33gt2a7grgmljhh",
    "card_id": "crd_3ykdnnld7d57x83eqcm1"
  },
  "created_at": "2024-03-13T16:56:51.766836837Z"
}

event_subject

member_id
string (member_id)

member ID

member_email_id
string (member_email_id)

member_email ID

member_magic_link_id
string (member_magic_link_id)

member_magic_link ID

member_pin_id
string (member_pin_id)

member_pin ID

member_card_id
string (member_card_id)

member_card ID

member_sdk_token_id
string (member_sdk_token_id)

member_sdk_token ID

Example
{
  "member_id": "mem_3merk33gt7ml3tde71f3",
  "member_email_id": "me_3rkd7ya2pnjysjqbluj1",
  "member_magic_link_id": "mml_3rkd2f2mv925ptvpgblh",
  "member_pin_id": "mp_3rkd2fmgmmdh1dgkvluh",
  "member_card_id": "mc_3yd55tbrelfxhjjvkdqh",
  "member_sdk_token_id": "mst_3yd55tbrelfxhjjvkdqh"
}

event_object

type
string device, gadget, gadget_action, member, member_email, member_magic_link, member_group, member_group_association, member_pin, member_card, member_sdk_token, organization, site, webhook, hardware, card
device_id
string (device_id)

device ID

gadget_id
string (gadget_id)

gadget ID

gadget_action_id
string

gadget_action ID

member_id
string (member_id)

member ID

member_email_id
string (member_email_id)

member_email ID

member_magic_link_id
string (member_magic_link_id)

member_magic_link ID

member_group_id
string (member_group_id)

member_group ID

member_group_association_id
string (member_group_association_id)

member_group_association ID

member_pin_id
string (member_pin_id)

member_pin ID

member_card_id
string (member_card_id)

member_card ID

member_sdk_token_id
string (member_sdk_token_id)

member_sdk_token ID

organization_id
string (organization_id)

organization ID

site_id
string (site_id)

site ID

webhook_id
string (webhook_id)

webhook ID

hardware_id
string (hardware_id)

hardware ID

card_id
string (card_id)

card ID

Example
{
  "type": "gadget_action",
  "device_id": "dev_3merk33gt3l525ryhcmh",
  "gadget_id": "gad_3merk33gt1hnl6pvbu71",
  "gadget_action_id": "open",
  "member_id": "mem_3merk33gt7ml3tde71f3",
  "member_email_id": "me_3rkd7ya2pnjysjqbluj1",
  "member_magic_link_id": "mml_3rkd2f2mv925ptvpgblh",
  "member_group_id": "mg_3merk33gt1692dk2p2m1",
  "member_group_association_id": "mga_3rkd81x2hc3qluv56pl1",
  "member_pin_id": "mp_3rkd2fmgmmdh1dgkvluh",
  "member_card_id": "mc_3yd55tbrelfxhjjvkdqh",
  "member_sdk_token_id": "mst_3yd55tbrelfxhjjvkdqh",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "site_id": "site_3merk33gt21kym11een1",
  "webhook_id": "wh_3merk33gt21kyz1f3z1",
  "hardware_id": "hw_3merk33gt2a7grgmljhh",
  "card_id": "crd_3ykdnnld7d57x83eqcm1"
}

List events

GET /events
cursor
in query
string

Cursor value obtained from a previous request to the same endpoint. If not present, the first page of results is returned.

limit
in query
integer

Items per page.

sort
in query
string

Sort order. Field name, followed by :asc or :desc. Defaults to id:desc. In most models, IDs are assigned in increasing order in time, so sorting by id is equivalent to sorting by created_at. Depending on the model, sorting on some fields might not be supported.

data
event
has_next
boolean
cursor_next
string (cursor)
Response Example (200 OK)
{
  "data": [
    {
      "id": "evt_3merk33gt21kym11een1",
      "organization_id": "org_3merk33gt1v9ypgfzrp1",
      "subject": {
        "member_id": "mem_3merk33gt7ml3tde71f3",
        "member_email_id": "me_3rkd7ya2pnjysjqbluj1",
        "member_magic_link_id": "mml_3rkd2f2mv925ptvpgblh",
        "member_pin_id": "mp_3rkd2fmgmmdh1dgkvluh",
        "member_card_id": "mc_3yd55tbrelfxhjjvkdqh",
        "member_sdk_token_id": "mst_3yd55tbrelfxhjjvkdqh"
      },
      "verb": "use",
      "object": {
        "type": "gadget_action",
        "device_id": "dev_3merk33gt3l525ryhcmh",
        "gadget_id": "gad_3merk33gt1hnl6pvbu71",
        "gadget_action_id": "open",
        "member_id": "mem_3merk33gt7ml3tde71f3",
        "member_email_id": "me_3rkd7ya2pnjysjqbluj1",
        "member_magic_link_id": "mml_3rkd2f2mv925ptvpgblh",
        "member_group_id": "mg_3merk33gt1692dk2p2m1",
        "member_group_association_id": "mga_3rkd81x2hc3qluv56pl1",
        "member_pin_id": "mp_3rkd2fmgmmdh1dgkvluh",
        "member_card_id": "mc_3yd55tbrelfxhjjvkdqh",
        "member_sdk_token_id": "mst_3yd55tbrelfxhjjvkdqh",
        "organization_id": "org_3merk33gt1v9ypgfzrp1",
        "site_id": "site_3merk33gt21kym11een1",
        "webhook_id": "wh_3merk33gt21kyz1f3z1",
        "hardware_id": "hw_3merk33gt2a7grgmljhh",
        "card_id": "crd_3ykdnnld7d57x83eqcm1"
      },
      "created_at": "2024-03-13T16:56:51.766836837Z"
    }
  ],
  "has_next": true,
  "cursor_next": "i9vmCnOgONT2AjUMCn1K1N5cJg=="
}

Get event

GET /events/{event_id}
event_id
in path
string (event_id)

event ID

An instance of event
Response Example (200 OK)
{
  "id": "evt_3merk33gt21kym11een1",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "subject": {
    "member_id": "mem_3merk33gt7ml3tde71f3",
    "member_email_id": "me_3rkd7ya2pnjysjqbluj1",
    "member_magic_link_id": "mml_3rkd2f2mv925ptvpgblh",
    "member_pin_id": "mp_3rkd2fmgmmdh1dgkvluh",
    "member_card_id": "mc_3yd55tbrelfxhjjvkdqh",
    "member_sdk_token_id": "mst_3yd55tbrelfxhjjvkdqh"
  },
  "verb": "use",
  "object": {
    "type": "gadget_action",
    "device_id": "dev_3merk33gt3l525ryhcmh",
    "gadget_id": "gad_3merk33gt1hnl6pvbu71",
    "gadget_action_id": "open",
    "member_id": "mem_3merk33gt7ml3tde71f3",
    "member_email_id": "me_3rkd7ya2pnjysjqbluj1",
    "member_magic_link_id": "mml_3rkd2f2mv925ptvpgblh",
    "member_group_id": "mg_3merk33gt1692dk2p2m1",
    "member_group_association_id": "mga_3rkd81x2hc3qluv56pl1",
    "member_pin_id": "mp_3rkd2fmgmmdh1dgkvluh",
    "member_card_id": "mc_3yd55tbrelfxhjjvkdqh",
    "member_sdk_token_id": "mst_3yd55tbrelfxhjjvkdqh",
    "organization_id": "org_3merk33gt1v9ypgfzrp1",
    "site_id": "site_3merk33gt21kym11een1",
    "webhook_id": "wh_3merk33gt21kyz1f3z1",
    "hardware_id": "hw_3merk33gt2a7grgmljhh",
    "card_id": "crd_3ykdnnld7d57x83eqcm1"
  },
  "created_at": "2024-03-13T16:56:51.766836837Z"
}

Webhooks

Webhooks allow your server to receive real-time notifications about events happening in the Akiles organization.

Webhooks send a POST request to the indicated url when a matching event occurs. The request body is a JSON-encoded event, same as you would get in the response of the "get event" endpoint.

URL can be HTTP or HTTPS. We strongly recommend using HTTPS for production.

Filtering

Each webhook has a "filter" which specifies which events should be sent to it. A filter is a list of "filter rules". Each rule specifies:

  • object_type: The value of object.type to match. This must be specified, i.e. it's NOT possible to subscribe to all object types.
  • verb: The value of verb to match. If it's set to null it matches all verbs, i.e. it IS possible to subscribe to all events about one object type, all verbs.

The webhook filter matches if any of the rules in the array match.

Some examples:

  • "filter": []: Matches no events. Events match if at least one filter rule matches, which can't happen if there's no rules at all.
  • "filter": [{"verb": "use", "object_type": "gadget_action"}]: Matches gadget action events.
  • "filter": [{"object_type": "member"}]: Matches all events related to members (creation, edition, deletion...)
  • "filter": [{"verb": "edit", "object_type": "member"}]: Matches member edition events.
  • "filter": [{"verb": "edit", "object_type": "member"}, {"verb": "use", "object_type": "gadget_action"}]: Matches member editions, and gadget actions.

An event will be sent to this webhook if it matches at least one rule. If it matches multiple rules, it will only be sent once. If it matches multiple webhook objects, it will be sent once per webhook to its respective URL, even if the URL is the same.

Signature

If an attacker learns the URL of your webhook, they could send requests themselves to it, spoofing webhooks. To prevent this, Akiles signs the webhook requests so that your server can check they come from Akiles and not an attacker. It's strongly recommended to verify the signature.

The signature is the result of applying HMAC-SHA256 to the raw request body bytes, using the webhook secret as a key. It's sent in the X-Akiles-Sig-SHA256 HTTP header.

The secret is returned when you create the webhook. It's not possible to view the secret of an existing webhook, you have to delete and recreate it instead.

The following is an example of how to receive webhooks and validate the signature using nodejs:

import url from 'url'
import http from 'http'
import crypto from 'crypto'

// replace with the secret you get when creating the webhook.
const webhookSecret = 'f305d484fd10de285b00bb203659e863';

const app = http.createServer((request, response) => {
    let body = [];
    request.on('data', (chunk) => {
        body.push(chunk);
    }).on('end', () => {
        body = Buffer.concat(body).toString();

        const gotSig = Buffer.from(request.headers['x-akiles-sig-sha256'], 'hex');
        const expectedSig = crypto.createHmac("sha256", webhookSecret).update(body).digest();
        const ok = crypto.timingSafeEqual(gotSig, expectedSig);
        if (!ok) {
            response.writeHead(400);
            response.write('bad sig');
            response.end();
            return;
        }

        const event = JSON.parse(body.toString());
        console.log('Got event!');
        console.log(event);

        response.writeHead(200);
        response.write('cool');
        response.end();
    });
});

app.listen(8088);

Delivery

Webhook delivery is considered successful if your server returns a 2xx status code.

Unsuccessful deliveries are retried, with an exponential backoff starting at a few seconds and doubling every time, for up to 1 hour.

webhook_filter_rule

object_type
string gadget, gadget_action, member, member_group, organization, site

Object type of the event.

verb
string create, edit, delete, use

Action verb of the event. If null, matches all verbs.

Example
{
  "object_type": "gadget_action",
  "verb": "use"
}

webhook

id
string (webhook_id)

webhook ID

organization_id
string (organization_id)

organization ID

filter
webhook_filter_rule

Array of event filter rules. An event will be sent to this webhook if it matches at least one rule. If it matches multiple rules, it will only be sent once. If it matches multiple webhook objects, it will be sent once per webhook to its respective URL.

url
string

URL the events are sent to.

secret
string

Secret used to sign webhooks. Visible only in the response for the creation API call.

is_enabled
boolean

True if this webhook is enabled. When false, no webhooks are sent.

is_deleted
boolean

True if this object has been deleted.

created_at
string (date-time)

Creation time for this object.

Example
{
  "id": "wh_3merk33gt21kyz1f3z1",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "filter": [
    {
      "object_type": "gadget_action",
      "verb": "use"
    }
  ],
  "url": "https://example.com/hook",
  "secret": "f305d484fd10de285b00bb203659e863",
  "is_enabled": true,
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z"
}

List webhooks

GET /webhooks
cursor
in query
string

Cursor value obtained from a previous request to the same endpoint. If not present, the first page of results is returned.

limit
in query
integer

Items per page.

sort
in query
string

Sort order. Field name, followed by :asc or :desc. Defaults to id:desc. In most models, IDs are assigned in increasing order in time, so sorting by id is equivalent to sorting by created_at. Depending on the model, sorting on some fields might not be supported.

data
webhook
has_next
boolean
cursor_next
string (cursor)
Response Example (200 OK)
{
  "data": [
    {
      "id": "wh_3merk33gt21kyz1f3z1",
      "organization_id": "org_3merk33gt1v9ypgfzrp1",
      "filter": [
        {
          "object_type": "gadget_action",
          "verb": "use"
        }
      ],
      "url": "https://example.com/hook",
      "secret": "f305d484fd10de285b00bb203659e863",
      "is_enabled": true,
      "is_deleted": false,
      "created_at": "2024-03-13T16:56:51.766836837Z"
    }
  ],
  "has_next": true,
  "cursor_next": "i9vmCnOgONT2AjUMCn1K1N5cJg=="
}

Create webhook

POST /webhooks
filter
webhook_filter_rule

Array of event filter rules.

url
string

URL the events are sent to.

is_enabled
boolean

True if this webhook is enabled. When false, no webhooks are sent.

Request Example
{
  "filter": [
    {
      "object_type": "gadget_action"
    }
  ],
  "url": "https://example.com/hook",
  "is_enabled": true
}
An instance of webhook
Response Example (200 OK)
{
  "id": "wh_3merk33gt21kyz1f3z1",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "filter": [
    {
      "object_type": "gadget_action",
      "verb": "use"
    }
  ],
  "url": "https://example.com/hook",
  "secret": "f305d484fd10de285b00bb203659e863",
  "is_enabled": true,
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z"
}

Get webhook

GET /webhooks/{webhook_id}
webhook_id
in path
string (webhook_id)

webhook ID

An instance of webhook
Response Example (200 OK)
{
  "id": "wh_3merk33gt21kyz1f3z1",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "filter": [
    {
      "object_type": "gadget_action",
      "verb": "use"
    }
  ],
  "url": "https://example.com/hook",
  "secret": "f305d484fd10de285b00bb203659e863",
  "is_enabled": true,
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z"
}

Edit webhook

PATCH /webhooks/{webhook_id}
webhook_id
in path
string (webhook_id)

webhook ID

filter
webhook_filter_rule

Array of event filter rules.

url
string

URL the events are sent to.

is_enabled
boolean

True if this webhook is enabled. When false, no webhooks are sent.

Request Example
{
  "filter": [
    {
      "object_type": "gadget_action"
    }
  ],
  "url": "https://example.com/hook",
  "is_enabled": true
}
An instance of webhook
Response Example (200 OK)
{
  "id": "wh_3merk33gt21kyz1f3z1",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "filter": [
    {
      "object_type": "gadget_action",
      "verb": "use"
    }
  ],
  "url": "https://example.com/hook",
  "secret": "f305d484fd10de285b00bb203659e863",
  "is_enabled": true,
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z"
}

Delete webhook

DELETE /webhooks/{webhook_id}
webhook_id
in path
string (webhook_id)

webhook ID

An instance of webhook
Response Example (200 OK)
{
  "id": "wh_3merk33gt21kyz1f3z1",
  "organization_id": "org_3merk33gt1v9ypgfzrp1",
  "filter": [
    {
      "object_type": "gadget_action",
      "verb": "use"
    }
  ],
  "url": "https://example.com/hook",
  "secret": "f305d484fd10de285b00bb203659e863",
  "is_enabled": true,
  "is_deleted": false,
  "created_at": "2024-03-13T16:56:51.766836837Z"
}