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:
VirtualHost "chat.example.com"
modules_enabled = {"rest"}If you install this as a component, the HTTP Basic credentials are the components base JID along with its secret.
Component "chat.example.com" "rest"
component_secret = "dmVyeSBzZWNyZXQgdG9rZW4K"To enable user authentication, edit the “admins = { }” section in prosody.cfg.lua, EG:
admins = { "admin@chat.example.com" }To set up the admin user account:
prosodyctl adduser admin@chat.example.comand lastly, drop the “@host” from the username in your http queries, EG:
curl -sf \
-H 'Accept: application/json' \
--user admin \
https://chat.example.com:5281/rest/version/chat.example.commod_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
archivediscoextdiscoitemslastactivityoobpayloadpingstatsversionTL;DR: Set this webhook callback URL, get XML POST-ed
there.
Component "rest.example.net" "rest"
rest_callback_url = "http://my-api.example:9999/stanzas"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:
rest_callback_content_type = "application/json"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"/>
Component "hello.example" "rest"
rest_callback_url = "http://hello.internal.example:9003/api"
rest_callback_stanzas = { "message" }
rest_callback_events = { "bare" }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".
bodysubjecthtmloob_urlkind"presence"
type"unavailable" for offline.
showaway, dnd etc.
statusOnly one type of payload can be included in an iq.
kind"iq"
type"get" or "set" for queries,
"response" or "error" for replies.
pingdiscoitems<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
app = Flask("echobot")
@app.before_request
def parse():
request.stanza = ET.fromstring(request.data)
@app.route("/", methods=["POST"])
def hello():
if request.stanza.tag == "message":
return Response(
"<message><body>Yes this is bot</body></message>",
content_type="application/xmpp+xml",
)
return Response(status=501)
if __name__ == "__main__":
app.run()And a JSON variant:
from flask import Flask, Response, request, jsonify
app = Flask("echobot")
@app.route("/", methods=["POST"])
def hello():
print(request.data)
if request.is_json:
data = request.get_json()
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".
typekind, see RFC 6121.
E.g."chat" for message stanzas etc.
tofromidbodysubjecttype:groupchat message.
show"away",
"dnd". No value means a normal online status. See RFC 6121 for the full
list.
statusstate"active" (default) and
"composing" (typing).
html<body> element.
oob_urlmucpingversionname, version fields, and optionally
an os field, to describe the software.
discoBoolean 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.
itemstrue 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"}.
extensionsFORM_DATA fields as the keys pointing at maps with the rest
of the data.
Used to execute arbitrary commands on supporting entities.
commandString representing the command node or Map with the
following possible fields:
nodeaction"execute". Multi-step
commands may involve "next", "prev",
"complete" or "cancel".
actionstrue) with available actions to
proceed with in multi-step commands.
status"executing".
sessionidnote"type" and "text" fields that carry
simple result information.
formdataDiscovering 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