Point in time documentation of the Keybase Chat API

26 June 2017

A couple of months back I did a brief writeup of Keybase and what it's good for.  I mentioned briefly that it implements a 1-to-n text chat feature, where n>=1.  Yes, this means that you can use Keybase Chat to talk to yourself, which is handy for prototyping and debugging code.  What does not seem to be very well known is that the Keybase command line utility has a JSON API, the documentation of which you can scan through by issuing the command `keybase chat help api` from a command window.  I'm considering incorporating Keybase into my exocortex so I spent some time one afternoon playing around with the API, seeing what I could make it do, and writing up what I had to do to make it work.  As far as I know there is no official API documentation anywhere; at least, Argus and I didn't find any.  So, under the cut are my notes in the hope that it helps other people work with the Keybase API.

The API may drift a bit, so here are the software versions I used during testing:

Client:  1.0.22-20170512224715+f5fba02ec
Service: 1.0.22-20170512224715+f5fba02ec

keybase chat send drwho "some message here"

keybase chat send drwho,leandra@leandra.virtadpt.net "some message here"

API takes JSON input, emits JSON output.

The Keybase server listens on UNIX socket /run/user/[ UID ]/keybase/keybased.sock by default or /home/[ username ]/.config/keybase/keybased.sock if you start Keybase such that it's an isolated service (and not systemwide (listening on /keybase)).  I have a hypothesis that it would be possible to write code that just opens this socket and pumps JSON commands into it, and reads JSON responses from it.

echo {"foo": "bar", "baz": "quux"} | keybase chat api -m

keybase chat api -m '{"method": "list"}' -p

Common error message.  It may take more than one attempt to get a response other than this:

{
    "error": {
        "code": 0,
        "message": "API network error: Get https://api.keybase.io/_/api/1.0/merkle/path.json?last=1181451\u0026poll=10\u0026sig_hints_low=3\u0026uid=c8c70ce896547dfaa65ef1082b908000: dial tcp: lookup api.keybase.io: no such host (error 1601)"
    }
}

keybase chat help api

Put keybase utility into API mode: keybase chat api

  • -p - Neatly format JSON when printing it.  Useful for debugging.
  • -m - Tell keybase API to expect JSON input.

List of methods

  • list
    • list by topic
  • read
  • send
  • delete
  • edit
  • attach
  • download
  • read (doesn't mark message as read)
  • mark (as read, up to a specific message)
  • send to public channel
  • setstatus

List messages in my inbox:

{
    "method": "list"
}

{
    "result": {
        "conversations": [{
                "id": "00007727e270dd46c8b537250e33c67bfeaf4e71c76f885c09e2c94040cc5035",
                "channel": {
                    "name": "drwho,foo,bar,baz",
                    // Defaults to false, true means the unencrypted public broadcast.
                    "public": false,
                    "topic_type": "chat"
                },
                // true means you've seen the message already.
                "unread": false,
                // UNIX time_t format.
                "active_at": 1497918896,
                // time_t + 0-999 ms
                "active_at_ms": 1497918896662
            },
            ....
        ],
        "offline": false
    }
}

Read a conversation:

{
    "method": "read",
    "params": {
        "options": {
            "channel": {
                "name": "you,them"
            }
        }
    }
}
{
    "result": {
        "messages": [{
                "msg": {
                    // Value of "id" increases monotonically.  Use this to sort messages in the queue.
                    "id": 4,
                    "channel": {
                        // Corresponds to the name of the channel you requested.
                        "name": "you,them",
                        "public": false,
                        "topic_type": "chat"
                    },
                    "sender": {
                        "uid": "c8c70ce896547dfaa65ef1082b908000",
                        "username": "drwho",
                        "device_id": "2dc577a45556306e02a21cd176f37b18",
                        "device_name": "Windbringer"
                    },
                    "sent_at": 1497918896,
                    "sent_at_ms": 1497918896658,
                    "content": {
                        "type": "text",
                        "text": {
                            "body": "Yes, it did."
                        }
                    },
                    "prev": [{
                        "id": 3,
                        "hash": "W+OVqQDHGUqYqWNziRrZOnKBibC4AOCGb5c9WPkPb3Y="
                    }],
                    "unread": false
                }
            }
            ...
        ],
        "ratelimits": [{
            "tank": "chat",
            "capacity": 900,
            "reset": 568,
            "gas": 895
        }]
    }
}

Send a message:

{
    "method": "send",
    "params": {
        "options": {
            "channel": {
                "name": "drwho"
            },
            "message": {
                "body": "Hello, world!"
            }
        }
    }
}
{
    "result": {
        "message": "message sent",
        "ratelimits": [{
            "tank": "chat",
            "capacity": 900,
            "reset": 181,
            "gas": 897
        }]
    }
}

Delete a message:

{
    "method": "delete",
    "params": {
        "options": {
            "channel": {
                "name": "drwho"
            },
            "message_id": [ integer message ID that you know exists ]
        }
    }
}
{
    "result": {
        "message": "message deleted",
        "ratelimits": [{
            "tank": "chat",
            "capacity": 900,
            "reset": 808,
            "gas": 898
        }]
    }
}

Edit a message:

{
    "method": "edit",
    "params": {
        "options": {
            "channel": {
                "name": "foo,bar,baz"
            },
            "message_id": [ integer message ID that you know exists ],
            "message": {
                "body": "I have just edited this message."
            }
        }
    }
}
{
    "result": {
        "message": "message edited",
        "ratelimits": [{
            "tank": "chat",
            "capacity": 900,
            "reset": 609,
            "gas": 896
        }]
    }
}

Peek at a conversation, which does not mark the messages as read:

{
    "method": "read",
    "params": {
        "options": {
            "channel": {
                "name": "drwho"
            },
            "peek": true
        }
    }
}
{
    "result": {
        "messages": [{
            "msg": {
                "id": 7,
                "channel": {
                    "name": "drwho",
                    "public": false,
                    "topic_type": "chat"
                },
                "sender": {
                    "uid": "c8c70ce896547dfaa65ef1082b908000",
                    "username": "drwho",
                    "device_id": "2dc577a45556306e02a21cd176f37b18",
                    "device_name": "Windbringer"
                },
                "sent_at": 1497923894,
                "sent_at_ms": 1497923894655,
                "content": {
                    "type": "edit",
                    "edit": {
                        "messageID": 4,
                        "body": "I have just edited this message."
                    }
                },
                "prev": [{
                    "id": 6,
                    "hash": "VwGvKcy8CSJu+MSLyEhK8c2O6W1gE2FsyRjHAHa/QUI="
                }],
                "unread": false
            }
        },
        ...
        {
]
                "unread": false
            }
        }]
    }
}

Just peek at unread messages:

{
    "method": "read",
    "params": {
        "options": {
            "channel": {
                "name": "drwho"
            },
            "unread_only": true,
            "peek": true
        }
    }
}

This is what a null result looks like - the length of $.result.messages JSONpath should be zero.

{
    "result": {
        "messages": []
    }
}

For a particular conversation, mark all messages as read up to the one you specify:

{
    "method": "mark",
    "params": {
        "options": {
            "channel": {
                "name": "drwho"
            },
            "message_id": 5
        }
    }
}
{
    "result": {
        "ratelimits": [{
            "tank": "chat",
            "capacity": 900,
            "reset": 850,
            "gas": 897
        }]
    }
}

List inboxes by topic:

{
    "method": "list",
    "params": {
        "options": {
            "topic_type": "DEV"
        }
    }
}

This is a null result - the JSONpath $.result.conversations will be null:

{
    "result": {
        "conversations": null,
        "offline": false,
        "ratelimits": [{
            "tank": "chat",
            "capacity": 900,
            "reset": 697,
            "gas": 896
        }]
    }
}

I haven't figured out yet how to get a positive result, because I'm not in any topical chats to my knowledge.  An API call to list the topics the user is subscribed to would be helpful.

Broadcast a message to a public channel:

{
    "method": "send",
    "params": {
        "options": {
            "channel": {
                "name": "drwho",
                "public": true
            },
            "message": {
                "body": "Testing the Keybase API - sending a public broadcast."
            }
        }
    }
}
{
    "result": {
        "message": "message sent",
        "ratelimits": [{
            "tank": "chat",
            "capacity": 900,
            "reset": 436,
            "gas": 888
        }]
    }
}