This is yet another RESTful API for sending and receiving stanzas via Prosody. It can be used to build bots and components implemented as HTTP services. It is the spiritual successor to mod_post_msg and absorbs use cases from mod_http_rest and mod_component_http and other such modules floating around the Internet.
You make a choice: install via VirtualHosts or as a Component. User authentication can be used when installed via VirtualHost, and OAuth2 can be used for either.
This enables rest on the VirtualHost domain, enabling user authentication to secure the endpoint. Make sure that the modules_enabled section is immediately below the VirtualHost entry so that it’s not under any Component sections. EG:
"chat.example.com"
VirtualHost = {"rest"} modules_enabled
If you install this as a component, you won’t be able to use user authentication above, and must use OAuth2 authentication outlined below.
"chat.example.com" "rest"
Component = "dmVyeSBzZWNyZXQgdG9rZW4K"
component_secret = {"http_oauth2"} modules_enabled
To enable user authentication, edit the “admins = { }” section in prosody.cfg.lua, EG:
= { "admin@chat.example.com" } admins
To set up the admin user account:
.example.com prosodyctl adduser admin@chat
and lastly, drop the “@host” from the username in your http queries, EG:
curl \://chat.example.com:5281/rest/version/chat.example.com \
https-k \
--user admin \
-H 'Accept: application/json'
mod_http_oauth2 can be used to
grant bearer tokens which are accepted by mod_rest. Tokens can be passed
to curl
like
--oauth2-bearer dmVyeSBzZWNyZXQgdG9rZW4K
instead of using
--user
.
The API endpoint becomes available at the path /rest
, so
the full URL will be something like
https://conference.chat.example.com:5281/rest
.
To try it, simply curl
an XML stanza payload:
curl https://prosody.example:5281/rest \
--user username \
-H 'Content-Type: application/xmpp+xml' \
--data-binary '<message type="chat" to="user@example.org">
<body>Hello!</body>
</message>'
or a JSON payload:
curl https://prosody.example:5281/rest \
--user username \
-H 'Content-Type: application/json' \
--data-binary '{
"body" : "Hello!",
"kind" : "message",
"to" : "user@example.org",
"type" : "chat"
}'
The Content-Type
header is important!
New alternative format with the parameters kind
,
type
, and to
embedded in the path:
curl https://prosody.example:5281/rest/message/chat/john@example.com \
--user username \
-H 'Content-Type: text/plain' \
--data-binary 'Hello John!'
A POST containing an <iq>
stanza automatically
wait for the reply, long-polling style.
curl https://prosody.example:5281/rest \
--user username \
-H 'Content-Type: application/xmpp+xml' \
--data-binary '<iq type="get" to="example.net">
<ping xmlns="urn:xmpp:ping"/>
</iq>'
Replies to other kinds of stanzas that are generated by the same Prosody instance MAY be returned in the HTTP response. Replies from other entities (connected clients or remote servers) will not be returned, but can be forwarded via the callback API described in the next section.
A subset of IQ stanzas can be sent as simple GET requests
curl https://prosody.example:5281/rest/version/example.com \
--user username \
-H 'Accept: application/json'
The supported queries are
archive
disco
extdisco
items
lastactivity
oob
payload
ping
stats
version
TL;DR: Set this webhook callback URL, get XML POST
-ed
there.
"rest.example.net" "rest"
Component = "http://my-api.example:9999/stanzas" rest_callback_url
The callback URL supports a few variables from the stanza being sent,
namely {kind}
(e.g. message, presence, iq or meta) and ones
corresponding to stanza attributes: {type}
,
{to}
and {from}
.
The preferred format can be indicated via the Accept header in response to an OPTIONS probe that mod_rest does on startup, or by configuring:
= "application/json" rest_callback_content_type
Example callback looks like:
POST /stanzas HTTP/1.1
Content-Type: application/xmpp+xml
Content-Length: 102
message to="bot@rest.example.net" from="user@example.com" type="chat">
<body>Hello</body>
<message> </
or as JSON:
POST /stanzas HTTP/1.1
Content-Type: application/json
Content-Length: 133
{
"body" : "Hello",
"from" : "user@example.com",
"kind" : "message",
"to" : "bot@rest.example.net",
"type" : "chat"
}
The set of stanzas routed to the callback is determined by these two settings:
rest_callback_stanzas
{ "message", "presence", "iq" }
rest_callback_events
{ "bare", "full", "host" }
,
while on a VirtualHost the default is { "host" }
.
Events correspond to which form of address was used in the
to
attribute of the stanza.
localpart@hostpart
localpart@hostpart/resourcepart
hostpart
The following example would handle only stanzas like
<message to="anything@hello.example"/>
"hello.example" "rest"
Component = "http://hello.internal.example:9003/api"
rest_callback_url = { "message" }
rest_callback_stanzas = { "bare" } rest_callback_events
To accept the stanza without returning a reply, respond with HTTP
status code 202
or 204
.
HTTP status codes in the 4xx
and 5xx
range
are mapped to an appropriate stanza error.
For full control over the response, set the Content-Type
header to application/xmpp+xml
and return an XMPP stanza as
an XML snippet.
HTTP/1.1 200 Ok
Content-Type: application/xmpp+xml
message type="chat">
<body>Yes, this is bot</body>
<message> </
{
"body" : "Hello!",
"kind" : "message",
"type" : "chat"
}
Further JSON object keys as follows:
kind
"message"
type
"chat"
for 1-to-1 messages and
"groupchat"
for group chat messages. Others include
"normal"
, "headline"
and "error"
.
body
subject
html
oob_url
kind
"presence"
type
"unavailable"
for offline.
show
away
, dnd
etc.
status
Only one type of payload can be included in an iq
.
kind
"iq"
type
"get"
or "set"
for queries,
"response"
or "error"
for replies.
ping
disco
items
message type="" id="" to="" from="" xml:lang="">
<
...message> </
An XML declaration (<?xml?>
) MUST
NOT be included.
The payload MUST contain one (1) message
,
presence
or iq
stanza.
The stanzas MUST NOT have an xmlns
attribute, and the
default/empty namespace is treated as jabber:client
.
Simple echo bot that responds to messages as XML:
from flask import Flask, Response, request
import xml.etree.ElementTree as ET
= Flask("echobot")
app
@app.before_request
def parse():
= ET.fromstring(request.data)
request.stanza
@app.route("/", methods=["POST"])
def hello():
if request.stanza.tag == "message":
return Response(
"<message><body>Yes this is bot</body></message>",
="application/xmpp+xml",
content_type
)
return Response(status=501)
if __name__ == "__main__":
app.run()
And a JSON variant:
from flask import Flask, Response, request, jsonify
= Flask("echobot")
app
@app.route("/", methods=["POST"])
def hello():
print(request.data)
if request.is_json:
= request.get_json()
data if data["kind"] == "message":
return jsonify({"body": "hello"})
return Response(status=501)
if __name__ == "__main__":
app.run()
Remember to set
rest_callback_content_type = "application/json"
for this to
work.
This section describes the JSON mapping. It can’t represent any possible stanza, for full flexibility use the XML mode.
kind
"message"
,
"presence"
or "iq"
.
type
kind
, see RFC 6121.
E.g."chat"
for message stanzas etc.
to
from
id
body
subject
type:groupchat
message.
show
"away"
,
"dnd"
. No value means a normal online status. See RFC 6121 for the full
list.
status
state
"active"
(default) and
"composing"
(typing).
html
<body>
element.
oob_url
muc
ping
version
name
, version
fields, and optionally
an os
field, to describe the software.
disco
Boolean true
in a kind:iq
type:get
for a service discovery query.
Responses have a map containing an array of available features in the
features
key and an array of “identities” in the
identities
key. Each identity has a category
and type
field as well as an optional name
field. See XEP-0030 for
further details.
items
true
in a kind:iq
type:get
for a service discovery items list query. The
response contain an array of items like
{"jid":"xmpp.address.here","name":"Description of item"}
.
extensions
FORM_DATA
fields as the keys pointing at maps with the rest
of the data.
Used to execute arbitrary commands on supporting entities.
command
String representing the command node
or Map with the
following possible fields:
node
action
"execute"
. Multi-step
commands may involve "next"
, "prev"
,
"complete"
or "cancel"
.
actions
true
) with available actions to
proceed with in multi-step commands.
status
"executing"
.
sessionid
note
"type"
and "text"
fields that carry
simple result information.
form
data
Discovering commands:
{
"items" : {
"node" : "http://jabber.org/protocol/commands"
},
"id" : "8iN9hwdAAcfTBchm",
"kind" : "iq",
"to" : "example.com",
"type" : "get"
}
Response:
{
"from" : "example.com",
"id" : "8iN9hwdAAcfTBchm",
"items" : [
{
"jid" : "example.com",
"name" : "Get uptime",
"node" : "uptime"
}
],
"kind" : "iq",
"type" : "result"
}
Execute the command:
{
"command" : {
"node" : "uptime"
},
"id" : "Jv-87nRaP6Mnrp8l",
"kind" : "iq",
"to" : "example.com",
"type" : "set"
}
Executed:
{
"command" : {
"node" : "uptime",
"note" : {
"text" : "This server has been running for 0 days, 20 hours and 54 minutes (since Fri Feb 7 18:05:30 2020)",
"type" : "info"
},
"sessionid" : "6380880a-93e9-4f13-8ee2-171927a40e67",
"status" : "completed"
},
"from" : "example.com",
"id" : "Jv-87nRaP6Mnrp8l",
"kind" : "iq",
"type" : "result"
}
Requires Prosody trunk / 0.12
With the plugin installer in Prosody 0.12 you can use:
sudo prosodyctl install --server=https://modules.prosody.im/rocks/ mod_rest
For earlier versions see the documentation for installing 3rd party modules