API Development

JSON Web Token

API integration is managed by encrypted JSON Web Token (JWT). JWT is a JSON-based open standard (RFC 7519) for creating access tokens that assert some number of claims.

A restaurant owner (RO) logs into the Restaurant Portal and is provided a JWT. The JWT authorizes the RO to fetch data that it owns e.g. all deliveries for today.

The portal shows all available services for the current RO e.g. Reports, Orders and Dashboard services. Services provided from the Plugin Registry (PR) are unique for each brand. The PR returns a list of Plugin Objects (PO) which JavaScript bundle URL:

[
    {
        "code": "HELLO_WORLD",
        "route": "hello-world",
        "bundleUrl": "https://example.com/app.{bundle}.js"
    }
]
+------------+          +--------------------+
|            | -------> | Plugin Registry    |
|            |          +--------------------+
|            |  Reports Plugin URL  |  |  |
| Web        |<---------------------+  |  |
| Client     |  Orders Plugin URL      |  |
|            |<------------------------+  |
|            |  Dashboard Plugin URL      |
|            |<---------------------------+
+------------+
 |  |  | GET https://plug.rps.com/res/{id}/reports/revenue   +-------------+
 |  |  +---------------------------------------------------->| Reports API |
 |  |                                                        +-------------+
 |  |    GET https://oapi.domain.com/res/{id}/orders/today   +-------------+
 |  +------------------------------------------------------->| Orders API  |
 |                                                           +-------------+
 |       GET https://board.food.com/res/{id}/dashboard     +---------------+
 +-------------------------------------------------------->| Dashboard API |
                                                           +---------------+

JSON Web Key

A JSON Web Key (JWK) is a JSON data structure that represents a cryptographic key. The JWK permits fast local verification of the transmitted JWT for each Portal Service API.

Public Keys

Restaurant Portal operates in three different environments, each environment has a unique JWK:

EU

Environment Key Expiry (seconds)
Staging https://0phew9ltyk.execute-api.eu-west-1.amazonaws.com/stg/v2/jwk.json 3600
Production https://z2ib6nvxrj.execute-api.eu-west-1.amazonaws.com/prd/v2/jwk.json 3600

APAC

Environment Key Expiry (seconds)
Production https://i2i8h1fo03.execute-api.ap-southeast-1.amazonaws.com/prd/v2/jwk.json 3600

MENA

Environment Key Expiry (seconds)
Production https://z2ib6nvxrj.execute-api.eu-west-1.amazonaws.com/prd/v2/jwk.json 3600

LATAM

Environment Key Expiry (seconds)
Production https://dlbjm8jos6.execute-api.us-east-1.amazonaws.com/prd/v2/jwk.json 3600

RESTful paths

Recommendation: Brands developing a Portal Service API ideally should follow a RESTful pattern for paths e.g. /restaurants/{platformId}/resourceName.

Confirm ownership of resource

After successful verification of the JWT, when available, the platformId in the incoming request should be compared to the identifiers in the JWT authSchema property:

{
  "country": "KW",
  "user": {
    "locale": "en_GB",
    "name": "Restaurant Owner",
    "email": "[email protected]",
    "userId": "162",
    "operatorCode": "de-15320-lh2"
  },
  "version": "1",
  "authSchema": {
    "restaurants": [
        {
            "id": "3456",  // 9Cookies Go identifier
            "platforms": [ // Platform(s) the current restaurant is attached to
                {
                    "restaurantId": "100931123n",
                    "platformId": "LH_DE", // Global Entity Id
                    "platformKey": "LH_DE"
                },
                {
                    "restaurantId": "987hn423n",
                    "platformId": "PDE_DE", // Global Entity Id
                    "platformKey": "PDE_DE"
                }
            ]
        }
    ]
  },
  "iat": 1523881223,
  "exp": 1523883023,
  "iss": "portalAuth",
  "sub": "1"
}
// e.g. Path:   /restaurant/100931123n/reports
// e.g. Header: X-Platform-Id: 100931123n
const platformId = "987hn423n";
const payload = jwt.verify(token, jwks);
const authSchema = JSON.parse(payload['authSchema']);

// Look up restaurant by platformId.
let restaurant;
authSchema.restaurants.find(r => {
  return restaurant = r.platforms.find(p => p.platformId == platformId);
});

if (restaurant) {
    // The requester is permitted to access to the requested resource.
}

Available platforms with platformKeys

platformId maps directly to Global Entity IDs as described by Data Warehouse and Data Fridge teams.

Platform platformKey platformId (Global Entity Id)
Mjam MJM_AT MJM_AT
Pizza-Online.fi PO_FI PO_FI
OnlinePizza.se FO_OP (OP_SE*) OP_SE
YoGiYo YO_KR YO_KR
DameJidlo.cz DJ_CZ DJ_CZ
PizzaPortal PPP_PL PPP_PL
Otlob HF_EG HF_EG
NetPincer.hu NP_HU NP_HU

* Due to the migration of OnlinePizza to the Foodora stack, you also need to support the legacy platformKey OP_SE for a limited time.

Talabat

Country platformKey platformId (Global Entity ID)
United Arab Emirates TB_AE TB_AE
Bahrain TB_BH TB_BH
Jordan TB_JO TB_JO
Kuwait TB_KW TB_KW
Oman TB_OM TB_OM
Qatar TB_QA TB_QA
Saudi Arabia TB_SA TB_SA

foodonclick

Country platformKey platformId (Global Entity ID)
United Arab Emirates FOC_AE FOC_AE
Jordan FOC_JO FOC_JO
Lebanon FOC_LB FOC_LB
Oman FOC_OM FOC_OM
Qatar FOC_QA FOC_QA
Saudi Arabia FOC_SA FOC_SA

PedidosYa

Country platformKey platformId (Global Entity ID)
Argentina PY_AR PY_AR
Bolivia PY_BO PY_BO
Chile PY_CL PY_CL
Ecuador PY_EC PY_EC
Mexico PY_MX PY_MX
Panama AP_PA AP_PA
Puerto Rico PY_PR PY_PR
Paraguay PY_PY PY_PY
Uruguay PY_UY PY_UY
Venezuela PY_VE PY_VE

ClickDelivery

Country platformKey platformId (Global Entity ID)
Colombia CD_CO CD_CO
Ecuador CD_EC CD_EC
Greece CD_GR CD_GR
Peru CD_PE CD_PE

Domicilios

Country platformKey platformId (Global Entity ID)
Colombia CD_CO CD_CO
Ecuador CD_EC CD_EC
Greece CD_GR CD_GR
Peru CD_PE CD_PE

Foodora

Country platformKey platformId (Global Entity ID)
Austria FO_AT FO_AT
Canada FO_CA FO_CA
Finland FO_FI FO_FI
Germany FO_DE FO_DE
Norway FO_NO FO_NO
Sweden FO_SE FO_SE

Foodpanda

Country platformKey platformId (Global Entity ID)
Bangladesh FP_BD FP_BD
Bulgaria FP_BG FP_BG
Hong Kong FP_HK FP_HK
Malaysia FP_MY FP_MY
Pakistan FP_PK FP_PK
Philippines FP_PH FP_PH
Romania FP_RO FP_RO
Singapore FP_SG FP_SG
Taiwan FP_TW FP_TW
Thailand FP_TH FP_TH

Default behaviour

Plugin Service APIs that do not require explicit platformId in requests can assume the first platform to be the "active" platform.

CORS

All endpoints must return 200 for OPTIONS requests with following headers

Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Content-Type,Authorization
Access-Control-Allow-Methods: OPTIONS,GET
Access-Control-Allow-Credentials: false

Additionally, all non OPTIONS methods must also include

Access-Control-Allow-Origin: *

Reports

The portal supports a number of reporting views, as long as the format below is respected. Integrating new reporting endpoints developed by brands is not currently automated and requires some brief collaboration by the Portal team.

Responses must support the following schema:

{
    "data":[{
        "data":{ ... },
        "compare": { ... }
    }]
}

The top level data object contains two properties: data and compare. The later is an optional property reserved for comparisons, and only supported via CSV exports.

The keys in the former data property are decided by the developer. For a new report to be included in the portal, the developer must share the English names of each property. If the property represents a unit, that too must be described. Currently supported units

Supported units

All units are browser locale specific

Unit Description Input Render e.g.
DATE Full date 2017-10-25 25/10/2017
DATE_MONTH Month 2017-10-01 October
DATE_YEAR Year 2017-01-01 2017
CURRENCY Restaurant currency, symbol only displayed in table header 100 100

Table report

Performance report

GET /v1/restaurants/{id}/reports/orders/day?from=2017-08-01&to=2017-08-01
{
    "data":[{
        "data":{
            "date":"2017-08-01",
            "orderCount":62,
            "revenue":1031.65
        }
    },
    {
        "data": {
            "date": "2017-08-02",
            "orderCount":53,
            "revenue":877.8
        }
    }]
}
Rendering
Date Orders Sales (€)
2017-08-01 62 1031.65
2017-08-02 53 877.8

Performance report

List report

GET /v1/restaurants/6482/reports/dishes?from=2017-08-01&to=2017-10-31
{
    "data":[{
        "data":{
            "name":"Cheeseburger",
            "orderCount":351,
            "revenue":1158.3
        }
    },
    {
        "data":{
            "name":"Pommes Frites",
            "orderCount":221,
            "revenue":140.6
        }
    }]
}
Rendering
Name Order Amount/Revenue
Cheeseburger 351
1158.3
Pommes Frites 221
140.6

List report

Comparison report

GET
/v1/restaurants/{id}/reports/orders/day?from=2017-08-01&to=2017-08-01&compare[from]=2017-07-31&compare[to]=2017-07-31
{
    "data": [
        {
            "data": {
                "date": "2017-08-01",
                "orderCount": 62,
                "revenue": 1031.65
            },
            "compare": {
                "date": "2017-07-31",
                "orderCount": 56,
                "revenue": 823.9
            }
        }
    ]
}

Rendering

Rendered as CSV file:

day,orders,sales,day_comparison,orders_comparison,sales_comparison
"2017-08-01","62","1031.65","2017-07-31","56","823.9"

Responses

Successful

{
    "data": {

    }
}

Error

400

{
    "state": "ERROR",
    "code": "OUT_OF_RANGE",
    "message": "You can only select 31 days"
}
{
    "state": "ERROR",
    "code": "VALIDATION_ERROR",
    "message": "Error",
    "errors": [{
        "field": "id",
        "code": "FIELD_CANNOT_BE_EMPTY",
        "message": "id cannot be empty",
    }]
}
{
    "state": "ERROR",
    "code": "VALIDATION_ERROR",
    "message": "Error",
    "errors": [{
        "field": "from",
        "code": "FIELD_CANNOT_BE_EMPTY",
        "message": "from cannot be empty",
    }, {
        "field": "to",
        "code": "FIELD_CANNOT_BE_EMPTY",
        "message": "to cannot be empty",
    }]
}

401

No token passed
{
    "state": "ERROR",
    "code": "TOKEN_INVALID",
    "message": "Authorization failed, invalid authorization header (did you forget Bearer?)"
}
Unsupported token
{
    "state": "ERROR",
    "code": "TOKEN_INVALID",
    "message": "Authorization failed, provided token cannot be verified",
}
Token expired
{
    "state": "ERROR",
    "code": "TOKEN_EXPIRED",
    "message": "Authorization failed, token expired",
}

403

{
    "state": "ERROR",
    "code": "FORBIDDEN",
    "message": "Forbidden"
}

500

{
    "state": "ERROR",
    "code": "INTERNAL_ERROR",
    "message": "Unknown error"
}

results matching ""

    No results matching ""