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.
"example.com"
VirtualHost = {"rest"} modules_enabled
"rest.example.net" "rest"
Component = "dmVyeSBzZWNyZXQgdG9rZW4K"
component_secret = {"http_oauth2"} modules_enabled
mod_http_oauth2 can be used to grant bearer tokens which are accepted by mod_rest.
The API endpoint becomes available at the path /rest
, so the full URL will be something like https://your-prosody.example: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>
</body>'
or a JSON payload:
curl https://prosody.example:5281/rest \
--oauth2-bearer dmVyeSBzZWNyZXQgdG9rZW4K \
-H 'Content-Type: application/json' \
--data-binary '{
"body" : "Hello!",
"kind" : "message",
"to" : "user@example.org",
"type" : "chat"
}'
The Content-Type
header is important!
A POST containing an <iq>
stanza automatically wait for the reply, long-polling style.
curl https://prosody.example:5281/rest \
--oauth2-bearer dmVyeSBzZWNyZXQgdG9rZW4K \
-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.
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
To enable JSON payloads set
= "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"
}
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
join
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
join
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