REST Server Examples
This page provides some examples on how to use the REST server in KDB-X.
Check out examples for:
Customers
This section demonstrates a sample REST server with various styles of REST endpoints, including static, dynamic, and API-like patterns. See how to register endpoints and handle different types of RESTful requests in KDB-X.
- Static
-
resource names are predefined at endpoint registration time, for example
{/customers} - Dynamic
-
the resource name (or part of it) is supplied at call-time, for example
/db/{tbl}/{col} - API-like
-
same as static but uses verbs instead of names, for example
/getCustomers
Expand this box to see the example
.com_kx_rest:use`kx.rest;
.rest:.com_kx_rest; / Alias namespace for convenience
//
// @desc App initialization.
//
init:{
.rest.init[enlist[`autoBind]!enlist[1b]]; / Initialize
initStatic[]; / Sample static endpoints
initDynamic[]; / Sample dynamic endpoints
initApi[]; / Sample API-like endpoints
.rest.register[`get;"/help"; / Returns information about registered endpoints
"Retrieves information about registered REST endpoints";
{.rest.t};
()!()];
.rest.register[`get;"/hc"; / health-check
"health-check endpoint";
{"ok"};
()!()];
.rest.register[`get;"/_ping";
"for testing";
{"pong"};
()!()]; }
//
// Sample parameter subset to support paging.
//
pagingParams:.rest.reg.data[`i;-6h;0b;0;"Offset of first row"],
.rest.reg.data[`cnt;-6h;0b;10;"Number of rows to return"];
//
// Wraps `#` to limit to data size.
//
take:{[n;d]min[(n;count d)]#d}
//
// Initialization examples.
//
//
// Static endpoints, and various styles of versioning.
//
initStatic:{
.rest.register[`get;"/customers";
"Returns all customers";
{take[x[`arg;`cnt]]select from customers where i>=x[`arg;`i]};
pagingParams
];
.rest.register[`get;"/customers.2";
"Returns all customers (version 2)";
{take[x[`arg;`cnt]]update newCol:1 from select from customers where i>=x[`arg;`i]};
pagingParams
];
.rest.register[`get;"/v3/customers";
"Returns all customers (version 3)";
{take[x[`arg;`cnt]]update newCol:1, newCol2:10 from select from customers where i>=x[`arg;`i]};
pagingParams
];
.rest.register[`get;"/customers/{id}";
"Returns one or more customers by their IDs";
{select from customers where id in x[`arg;`id]};
.rest.reg.data[`id;6h;1b;0Ni;"One or more customer IDs"]
]; }
initDynamic:{
.rest.register[`get;"/db";
"Retrieves list of table names";
{tables[]};
()!()
];
.rest.register[`get;"/db/{table}";
"Retrieves a table";
.tbl.getData;
.rest.reg.data[`table;-11h;1b;`;"Table name"],
pagingParams
];
.rest.register[`get;"/db/{table}/meta";
"Retrieves metadata of a table";
{0!meta x[`arg;`table]};
.rest.reg.data[`table;-11h;1b;`;"Table name"]
];
.rest.register[`get;"/db/{table}/{col}";
"Retrieves a column subset of a table";
.tbl.getData;
.rest.reg.data[`table;-11h;1b;`;"Table name"],
.rest.reg.data[`col;11h;1b;0#`;"Result columns"],
pagingParams
]; }
initApi:{
.rest.register[`get;"/getCustomers";
"Returns all customers";
{([] id:til 10; nm:10?`5)};
::
]; }
//
// Automatic data retrieval
//
//
// @desc Generic data retrieval handler. Look for {tbl}, {id}, and {rel} arguments
//
.tbl.getData:{
tn:x[`arg;`table];
i_:$[`i in key x[`arg];x[`arg;`i];0];
cnt:$[`cnt in key x[`arg];x[`arg;`cnt];0W];
if[not tn in tables[]; 'table]; / Check whether table exists
w:$[""~0N!flt:x[`data];();enlist parse flt]; / If request is a POST, then data is expected to be the where clause
c:$[`col in key x`arg;{x!x}x[`arg;`col];()]; / Columns to retrieve
take[cnt]select from ?[tn;w;0b;c] where i>=i_ }
init[]
//
// Test data
//
n:100
customers:([] id:1+til n; name:n?`7)
sn:`aapl`goog`msft`nke`ftse
symbols:1!([] sym:`aapl`goog`msft`nke`ftse; src:count[sn]?`8);
trades:([] ts:$[12h;.z.d]+$[`minute;1]*til n;
sym:n?(0!symbols)[`sym];
price:n?10.0;
size:n?1000)
queryclient
This section provides the full definition of an example client that interacts with the REST server. It relies on the kurl module. It shows how to connect, create resources, submit queries, and check job status using q code and HTTP requests.
Expand this box to see the example
.kurl:use`kx.kurl
if[not `server in key .Q.opt .z.x; '"Provide -server http://host:port"]
server:first @[;`server] .Q.opt .z.x
// Wait forever until health check returns true.
while[200 <> first @[.kurl.sync;(server,"/v1/hc";`GET;::);{(-1;"")}];
system "sleep 1"]
// Create a new project folder
body: .j.j `name`dir!("myProject";"projFolder1")
headers:("http-method";"Content-Type")!("POST";"application/json")
resp:.kurl.sync (server,"/v1/projects";`POST;`body`headers!(body;headers))
if[200 <> first resp; 'last resp]
project:.j.k last resp
-1 "Created ", project `name;
projectID:string project `id
// Create an empty database folder
body: .j.j enlist[`name]!enlist "db1"
resp:.kurl.sync (server,"/v1/projects/",projectID,"/databases";
`POST;
`body`headers!(body;headers))
if[200 <> first resp; 'last resp]
database:.j.k last resp
databaseID:database `id
// Create a random partitioned table using JSON
// Note that this is just for demonstrations sake
// instead of calling this API, you may mount a db externally on the server
n:1000
t:([]
date:n?2021.01.01 2021.01.02 2021.01.03;
x: n?100f;
y: n?0b )
resp:.kurl.sync
(0N!server,"/v1/projects/",projectID,"/databases/",databaseID,"/tables";
`POST;
`body`headers!(.j.j `name`table!(`t;t);headers))
if[200 <> first resp; 'last resp]
// Submit a query job
body:.j.j`query`databaseID!("select from t"; databaseID)
resp:.kurl.sync (server,"/v1/projects/",projectID,"/jobs";
`POST;
`body`headers!(body;headers))
if[200 <> first resp; 'last resp]
job:.j.k last resp
jobID:string job `id
// Check on the job
resp:.kurl.sync (server,"/v1/projects/",projectID,"/jobs/",jobID;`GET;::)
if[200 <> first resp; 'last resp]
queryserver
This section demonstrates a REST server capable of running asynchronous query jobs. It explains the server's resource organization, usage patterns, and how to register endpoints for managing projects, databases, tables, and jobs.
Jobs are run in worker processes that execute arbitrary qSQL and trigger a callback when done. A REST Client is expected to poll for async job results.
Resources for the server are organized as a list of projects containing lists of databases.
Databases are organized as a list of tables.
Usage patterns:
GET /v1/hc Simply health check
GET /v1/projects List all projects
GET /v1/projects/{projectID} List one project's attributes
GET /v1/projects/{projectID}/databases List all databases for projectID
GET /v1/projects/{projectID}/databases/{databaseID} List one database's arrtibutes
GET /v1/projects/{projectID}/databases/{databaseID}/tables List all tables for projectID + databaseID
GET /v1/projects/{projectID}/databases/{databaseID}/tables/{tableID} List table arributes
POST /v1/projects Create a new project
POST /v1/projects/{projectID} Update a project (rename/delete)
POST /v1/projects/{projectID}/databases Create a database
POST /v1/projects/{projectID}/databases/{databaseID} Update a database
POST /v1/projects/{projectID}/databases/{databaseID}/tables Create a table
POST /v1/projects/{projectID}/databases/{databaseID}/tables/{tableID} Update a table
GET /v1/projects/{projectID}/jobs/ List all running queries
POST /v1/projects/{projectID}/jobs/ Run a new query
GET /v1/projects/{projectID}/jobs/{jobID} Check on the status of a job
GET /v1/projects/{projectID}/jobs/{jobID}/results Get the JSONified query results
Expand this box to see the example
.com_kx_rest:use`kx.rest; // load the module
.rest:.com_kx_rest / Make an alias for convenience
.rest.init[enlist[`autoBind]!enlist[1b]];
// Querystring params
\d .demo
param.projectID:.rest.reg.data[`projectID;-7h;1b;0;"Project ID"]
param.databaseID:.rest.reg.data[`databaseID;10h;1b;"";"Database ID"]
param.tableID:.rest.reg.data[`tableID;10h;1b;"";"Table ID"]
param.jobID:.rest.reg.data[`jobID;-7h;1b;0;"Job ID"]
// Body params
.rest.reg.object[`project;
.rest.reg.data[`name;-11h;0b;`;"Project name"],
.rest.reg.data[`dir;10h;0b;"";"Project directory"]]
.rest.reg.object[`database;
.rest.reg.data[`name;-11h;0b;`;"Database name"]]
.rest.reg.object[`table;
.rest.reg.data[`name;-11h;0b;`;"Table name"],
.rest.reg.data[`table;98h;0b;([] x:());"Table content"]]
.rest.reg.object[`job;
param.databaseID,
.rest.reg.data[`query;10h;0b;"";"q query"]]
param.project:.rest.reg.body[`project;0b;::;"Project information"]
param.database:.rest.reg.body[`database;0b;::;"Database information"]
param.table:.rest.reg.body[`table;0b;::;"Table information"]
param.job:.rest.reg.body[`job;0b;::;"Job information"]
.rest.register[`get;"/v1/hc";"simple healthcheck";{"ok"};()!()];
// GETTER API
.rest.register[`get;
"/v1/projects";
"List all projects";
{.demo.projects};
::]
.rest.register[`get;
"/v1/projects/{projectID}";
"List one projects attributes";
{first select from .demo.projects where id = x[`arg;`projectID]};
param.projectID]
.rest.register[`get;
"/v1/projects/{projectID}/databases";
"List all databases for a project";
{.demo.findDB};
param.projectID]
.rest.register[`get;
"/v1/projects/{projectID}/databases/{databaseID}";
"List a databases attributes";
{
db:.demo.findDB x;
enlist[`name]!enlist db `h
};
param.projectID,param.databaseID]
.rest.register[`get;
"/v1/projects/{projectID}/databases/{databaseID}/tables";
"List all tables in a database";
{
db:.demo.findDB x;
key db `h
};
param.projectID,param.databaseID]
.rest.register[`get;
"/v1/projects/{projectID}/databases/{databaseID}/tables/{tableID}";
"List table attributes";
{'`notImplemented};
param.projectID,param.databaseID,param.tableID]
// CREATE APIs
.rest.register[`post;
"/v1/projects";
"Create a new project";
{
system "mkdir -p ",di:projRoot,x[`data;`dir];
.demo.projects,:
`name`id`dir`created!(x[`data;`name];count .demo.projects;di; .z.p);
:last .demo.projects
};
param.project]
.rest.register[`post;
"/v1/projects/{projectID}";
"Update a project";
{'`notImplemented};
param.projectID]
.rest.register[`post;
"/v1/projects/{projectID}/databases";
"Add a database to a project";
{
proj:.demo.findProject x;
dbh:hsym `$db:proj[`dir],"/",string n: x[`data;`name];
if[() ~ key dbh; system "mkdir -p ",db];
:enlist[`id]!enlist n
};
param.projectID,param.database]
.rest.register[`post;
"/v1/projects/{projectID}/databases/{databaseID}";
"Rename or delete a database";
{"not implemented"};
param.projectID,param.databaseID]
.rest.register[`post;
"/v1/projects/{projectID}/databases/{databaseID}/tables";
"Add a table to a database";
{ .demo.writePar x };
param.projectID,param.databaseID,param.table]
.rest.register[`post;
"/v1/projects/{projectID}/databases/{databaseID}/tables/{tableID}";
"Add or overwrite dates in a table";
{`notImplemented};
param.projectID,param.databaseID,param.tableID]
// JOB API
.rest.register[`get;"/v1/projects/{projectID}/jobs";
"List all jobs for a project";
{select from .demo.jobs where projectID = x[`arg; `projectID];};
param.projectID]
.rest.register[`post;
"/v1/projects/{projectID}/jobs";
"Submit a query job";
{
proj:findProject x;
avail:first .demo.workers except exec worker from .demo.jobs;
db:proj[`dir],"/",x[`data;`databaseID];
neg[avail] (`.demo.runQuery; db; x[`data;`query]);
.demo.jobs,:`id`worker`projectID`status!(count .demo.jobs;
avail;proj `id;`active);
:last .demo.jobs
};
param.projectID,param.job]
.rest.register[`get;
"/v1/projects/{projectID}/jobs/{jobID}";
"List details of a job";
{findJob x};
param.projectID,param.jobID]
.rest.register[`get;
"/v1/projects/{projectID}/jobs/{jobID}/results";
"Get results for a finished job";
{
job:select from findJob[x] where status=`done;
if[1 <> count job;'"Job not finished"];
job[`worker] ".demo.results"
};
param.projectID,param.jobID]
projRoot:system["cd "],"/exampleProjects/"
projects:([] name:(); id:"j"$(); dir:(); created:"p"$())
findProject:{
proj:select from .demo.projects where id = x[`arg;`projectID];
if[0 = count proj; '"No such project ", x[`arg, `projectID]];
:first proj; }
findDB:{
proj:findProject x;
dbh:hsym `$db:proj[`dir],"/",n: x[`arg;`databaseID];
if[() ~ key dbh;'"Database does not exist: ", n];
:`name`h!(n;dbh) }
findJob:{
select from .demo.jobs where projectID = x[`arg;`projectID],
id = x[`arg;`jobID] }
writePar:{
db:findDB x;
name:x[`data;`name];
t:x[`data;`table];
t[`date]:"D"$t `date;
a:cols[t] except `date;
kt:?[t;();`date;a!a];
(`$string key[kt]) {[root;name;date;d]
(` sv root,date,name,`) set flip d
}[db `h;name]' value kt;
:name }
done:{ .demo.jobs:update status:`done from .demo.jobs where worker = .z.w;}
maxWait:00:00:05
i:0
n:10
workers:()
jobs:([] id:"j"$(); worker:"I"$(); projectID:"j"$(); status:`$())
\d .
.z.po:{.demo.i+:1;}
.z.ts:{[start;now]
if [now > start + .demo.maxWait;
-2 "Took longer than ",string[.demo.maxWait],
" to start ",string[.demo.n]," workers";
-2 "Exiting...";
exit 1];
// Clear timer and uninstall .z.po
if[.demo.n = count .z.W;
system "t 0";
.z.po:{};
.demo.workers:key .z.W]
}[.z.p;]
do[10; system "q queryworker.q -server ",string system "p"]
\t 1000
queryworker
This section provides the full definition of the example REST server’s worker processes. These workers execute query jobs submitted to the server and return results asynchronously.
This process should be started automatically by the REST server example
Copy this into your present working directory when running the REST server.
\d .demo
h:-1i
lastresult:()
.z.pc:{if[x ~ h; exit 0];}
if[not `server in key args:.Q.opt .z.x;
-2 "Server port required with -server <port>";
exit 1]
h:hopen `$":localhost:", first args `server
runQuery:{[dbpath;query]
system "l ",dbpath;
lastResult::value query;
.z.w (`.demo.done; 1b); }
\d .
OpenAPI example
This section contains an OpenAPI 3.0 specification for the REST server shown in the example on how to expose a RESTful interface. You can use this specification to import the API into tools that support OpenAPI, making it easier to explore and test the endpoints.
To use this in examples, copy the content below to a local JSON file, and import it into the relevant portal.
Replace the servers.url example with your server URL.
You can probably edit this after the fact in the portal’s UI.
Expand this box to see the example
{
"openapi": "3.0.1",
"info": {
"title": "kdbx-rest-test-api",
"description": "",
"version": "1.0"
},
"servers": [
{
"url": "https://example.azure-api.net"
}
],
"paths": {
"/customers": {
"get": {
"summary": "customers",
"description": "Returns all customers",
"operationId": "customers",
"parameters": [
{
"name": "i",
"in": "query",
"description": "Offset of first row",
"schema": {
"type": ""
}
},
{
"name": "cnt",
"in": "query",
"description": "Number of rows to return",
"schema": {
"type": ""
}
}
],
"responses": {
"200": {
"description": null
}
}
}
},
"/customers.2": {
"get": {
"summary": "customersdotv2",
"description": "Returns all customers (v2)",
"operationId": "customersdotv2",
"parameters": [
{
"name": "i",
"in": "query",
"description": "Offset of first row",
"schema": {
"type": ""
}
},
{
"name": "cnt",
"in": "query",
"description": "Number of rows to return",
"schema": {
"type": ""
}
}
],
"responses": {
"200": {
"description": null
}
}
}
},
"/v3/customers": {
"get": {
"summary": "customersv3",
"description": "Returns all customers v3",
"operationId": "customersv3",
"parameters": [
{
"name": "i",
"in": "query",
"description": "Offset of first row",
"schema": {
"type": ""
}
},
{
"name": "cnt",
"in": "query",
"description": "Number of rows to return",
"schema": {
"type": ""
}
}
],
"responses": {
"200": {
"description": null
}
}
}
},
"/customers/{id}": {
"get": {
"summary": "customerbyid",
"operationId": "customerbyid",
"parameters": [
{
"name": "id",
"in": "path",
"description": "Customer ID",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": null
}
}
}
},
"/db": {
"get": {
"summary": "db",
"description": "Retrieve list of table names",
"operationId": "db",
"responses": {
"200": {
"description": null
}
}
}
},
"/db/{table}": {
"get": {
"summary": "tablebyid",
"operationId": "tablebyid",
"parameters": [
{
"name": "table",
"in": "path",
"description": "Table name",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "i",
"in": "query",
"description": "Offset of first row",
"schema": {
"type": ""
}
},
{
"name": "cnt",
"in": "query",
"description": "Number of rows to return",
"schema": {
"type": ""
}
}
],
"responses": {
"200": {
"description": null
}
}
}
},
"/db/{table}/meta": {
"get": {
"summary": "tablemeta",
"description": "Get table meta",
"operationId": "tablemeta",
"parameters": [
{
"name": "table",
"in": "path",
"description": "Table name",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": null
}
}
}
},
"/db/{table}/{col}": {
"get": {
"summary": "column",
"description": "Get a subset of a column",
"operationId": "column",
"parameters": [
{
"name": "table",
"in": "path",
"description": "Table name",
"required": true,
"schema": {
"type": ""
}
},
{
"name": "col",
"in": "path",
"description": "Column name",
"required": true,
"schema": {
"type": ""
}
},
{
"name": "i",
"in": "query",
"description": "Offset of first row",
"schema": {
"type": ""
}
},
{
"name": "cnt",
"in": "query",
"description": "Number of rows to return",
"schema": {
"type": ""
}
}
],
"responses": {
"200": {
"description": null
}
}
}
},
"/help": {
"get": {
"summary": "help",
"description": "List all endpoints",
"operationId": "help",
"responses": {
"200": {
"description": null
}
}
}
},
"/getCustomers": {
"get": {
"summary": "getCustomers",
"description": "API like example endpoint that returns all customers",
"operationId": "getcustomers",
"responses": {
"200": {
"description": null
}
}
}
}
}
}
Expose a RESTful interface
This section provides an example of exposing a RESTful API to a KDB-X-based system.
We load the REST server library and create a simple API and async query server.
Example customers API
Create customers.q from the source in the customers example.
Run q in the current directory and load the REST server module followed by customers.q:
q -p 8080
.com_kx_rest:use`kx.rest;
\l customers.q
In another terminal run:
curl http://localhost:8080/customers
[{"id":1,"name":"milglie"},{"id":2,"name":"igfbage"},{"id":3,"name":"kaodhbe"},{"id":4,"name":"bafclbi"},{"id":5,"name":"kfhogjn"},{"id":6,"name":"jecpaen"},{"id":7,"name":"kfmohpi"},{"id":8,"name":"lkklcoi"},{"id":9,"name":"kfifpag"},{"id":10,"name":"fglgofj"}]
curl http://localhost:8080/db/customers/meta
[{"c":"id","t":"j","f":"","a":""},{"c":"name","t":"s","f":"","a":""}]
curl http://localhost:8080/getCustomers
[{"id":0,"nm":"mpnan"},{"id":1,"nm":"nogel"},{"id":2,"nm":"holpj"},{"id":3,"nm":"kkfpn"},{"id":4,"nm":"pegin"},{"id":5,"nm":"jcgnm"},{"id":6,"nm":"dlhep"},{"id":7,"nm":"cmejl"},{"id":8,"nm":"bfmjf"},{"id":9,"nm":"lcicg"}]
For a full overview of the server and the concepts introduced, see the customer example server.
Protecting a backend API
We can provision a protected public gateway to allow access to the private HTTP backend.
With the customers API example, you should have your server exposed on port 8080.
The guides below will look for an HTTP backend endpoint, this is the host and port of the Customers example.
To follow along, run the HTTP customers example on a VM instance and expose port 8080. We will then use a gateway to secure access to the REST server running on 8080.
In practice, you will want 8080 exposed to the virtual network the gateway is on, but not externally to the wider Web.
Custom HTTP headers
Your HTTP backend and Gateway should also consider requiring custom headers of your own design to quickly ignore foreign requests. This is not covered by the guides below.
Amazon API Gateway
Amazon API Gateway can be used to secure a REST API backend.
Start up a VM instance and run the customers example on port 8080 and expose port 8080 on the VM’s network.
Now use Amazon API Gateway to secure access to the customers API backend by importing an API configuration:
A sample OpenAPI 2.0 configuration for the customers API is included from the appendix.
After importing the API configuration, create a stage and name it kxce-stage.
The kxce-stage should define a single stage variable httpUrl, and the value should be the URL of the customers backend.
Set this variable to the URL without the protocol prefix
For example, set httpUrl to myserver.com, not http://myserver.com.
After deploying the stage, your API will be accessible over HTTPS using the Stage URL; however it will not require authentication.
To require authentication to use an API, see See Amazon API Gateway Authentication.
To use IAM for authentication, attach an IAM Authenticator under Develop > Routes.
Azure API Management
Azure Active Directory is now known as Microsoft Entra ID
Azure API Management service instances can be used to create a gateway to allow access to one or more REST endpoints.
See Azure API Management Key Concepts
To protect your API backend, we recommend using Microsoft Entra ID as the OAuth2 provider when using the API Management Service.
If you do not already have a Microsoft Entra tenant, and an Azure API Management service instance running, create new ones. Note that this might take a while. The name of your Azure API Management service instance becomes part of the gateway URL.
Start up a VM instance and run the customers example on port 8080 and expose port 8080 on the VM’s network.
We use Azure API Management to secure access to the customers API backend.
Once the Azure API Management Instance is up, import the sample OpenAPI JSON from the appendix.
After importing the API, click on the API’s Design tab, and enter the Customer examples HTTP backend endpoint URL for all operations.
With this alone, you should now be able to access your REST server from the API Management gateway URL, which at the time of writing is shown in the API Management Overview.
The APIs are still unauthenticated at this point, however the Gateway should be functional.
Once you have a Microsoft Entra tenant running, go to App Registrations and follow the instructions for making an API and a client app.
Be sure to follow the last step: without it, OAuth2 is confusingly enabled, yet not required.
Note
At the time of writing, that last step mentions editing a policy in raw XML. You may also edit the policy visually in the current UI, under Inbound Processing, in the Design tab, adding a validate-jwt type policy.
After you have the backend protected as described in the Azure walkthrough, you might want to replace the step where you treat the Azure API Portal as the app, instead using something like kurl as the client app.
Google Cloud API Gateway
Google API Gateway can be used to secure a REST API backend.
Start up a VM instance and run the customers example on port 8080 and expose port 8080 on the VM’s network.
We will now use Google API Gateway to secure access to the customers API backend by importing a configuration.
See Setting up Google API Gateway
Installing Google API Gateway in the Console
If you have not done so before, opening Google API Gateway in the Cloud Console will have you “install” and accept a user agreement.
Once you have Google API Gateway opened in the Google Console, you can import an API config.
When asked to import a configuration, you can use the sample OpenAPI 2.0 configuration for the customers API. Make sure to replace the x-google-backend address with the address of your VM running the REST server demo.
When asked to create a Gateway, you name it kxce-gateway, and choose a region.
Once Google finishes uploading your API, make a request to
https://GATEWAY_URL/customers
to use the customers API, where GATEWAY_URL is the URL listed in the Google Console’s Gateway tab.
For example, the generated gateway URL would look like:
https://kxce-gateway-5uoiifib.ue.gateway.dev/customers
Your API is now accessible over HTTPS using the Gateway; however it is not yet secured.
To secure the API, read about the options Google provides.
-
For simplicity, and to complete this demo, follow the steps for securing with an API Key
-
For further authentication options, instead of using API keys, see Authenticating with a Service Account
Next steps
- Refer to the REST API reference page for details on the APIs available.