Using the BlueSky API to change your Handle to your Domain Name

posted on 13 Dec 2023 | Software

Personally in the post-Muskening twitter world, I'm pulling for Mastodon, but Bluesky is a somewhat interesting platform too. One feature of Bluesky that stands out is that you can set your name to be based on a domain-name you own. I own Pxtl.ca, so I thought I'd take a crack at leveraging that feature.

What follows is a guide on how to do this in Powershell, which is a programming language you already have installed if you use Windows.

Preamble

What you need

I'm going to be doing this in Powershell 5.1, which is what's built into Windows. If you're not on Windows or you're a pro-star, you can install Powershell 7 on any operating system.

Second, you need your own domain. Those cost money annually, but that's how you can have your own snazzy site like "Pxtl.ca". Personally I use Google Domains, but sadly this is a sunsetting product so going forwards I'm going to need a new service to run my domain-name subscription.

Intro to Powershell

First up, we open up our Powershell console. You can type ⊞ Win + R to launch the run menu to access it, and then type "powershell" to use it. If this is your first time running powershell you may need to do some fiddling to enable it properly. This will give you a console to run powershell commands.

Now, if you're following along and copying and pasting Powershell code from this article into your Powershell window, a warning: Copying and pasting code from a stranger into your console and running it is very dangerous because a computer does what it's told and this stranger may have told your computer to do something very bad. In this situation, I am basically a stranger with candy.

So, don't ever copy/paste code from an article without fully understanding what it does. So, I'll be explaining everything I do.

Logging In to the API

We're going to be sending HTTP commands (like your web browser does, but through the Powershell console) using a powershell command called Invoke-RestMethod. "Rest" is just a programmer-jargon for "HTTP that uses data structures instead of websites".

So first, we have to create a session in our console that we can use to do stuff. Bluesky/Atproto (their programmer-interface protocol) makes you first "login" before you can do stuff.

So we run the following:

$sessionResponse = Invoke-RestMethod -Method POST -Uri https://bsky.social/xrpc/com.atproto.server.createSession `
    -body (@{identifier = 'email@example.com'; password ='PASSWORDGOESHERE'} | ConvertTo-Json) `
    -ContentType 'application/json'

Where "email@example.com" should be replaced with your own email address associated with your Bluesky account (we don't use your username, just the email) and PASSWORDGOESHERE is, obviously, your Bluesky password. If your password contains a ' char, you'll have to escape it.

So, what this does: We're using Invoke-RestMethod to send a POST action (like submitting a form on a website) to the url https://bsky.social/xrpc/com.atproto.server.createSession. We know that the target is trustworthy with your Bluesky password because it's in the bsky.social domain -- that is, we're sending your password to Bluesky, not to some stranger.

The little backtick at the end of the line is just the simplest way to do multi-line commands in Powershell. The backtick-line-continuation is considered bad form by Powershell people but Powershell is a bad language so imho turnabout is fair play.

The -body parameter is the body of data we're sending to Bluesky. @{identifier = 'email@example.com'; password ='PASSWORDGOESHERE'} is a Powershell object we're building with the login and password, and then we're piping it into | ConvertTo-Json to turn it into the JSON text format so it can be sent over the wire to Bluesky.

Finally we let Bluesky know we're sending it as JSON with the parameter -ContentType 'application/json'.

And we store the result of this command into $sessionResponse.

Inspecting the Result

You can inspect $sessionResponse by just running $sessionResponse in your Powershell console. You'll see an object with several members like did and handle and email and accessJwt. The important bits of data we need going forwards are did, which is your true user-account-ID in Bluesky, and accessJwt, which is the temporary super-long secret code we use going forwards to do other commands.

Now, the accessJwt code is short-lived (a few minutes) before the Bluesky server decides it doesn't like it anymore, so going forwards you might have to re-run the createSession command above to get a new one if commands start failing and complaining about security.

Creating the Domain Record

Okay, so we've proven to Bluesky that we are who we say we are, but how do we prove to Bluesky that we own the domain? I mean, I could say I'm google.com or whatever if they don't check. But they check.

So, let's say you own mydomain.com. You'll have to go into your domain registrar's website and create a new TXT record on mydomain.com that proves to Bluesky you own it. So get the did value from $sessionResponse by typing $sessionResponse.did. For example, my did value is did:plc:otu6mg5xkk47y3ghqpq2w3oo (this isn't a secret it's fine that you know that).

Create a new TXT record on mydomain.com called _atproto, and give it a value of did=YOURDIDGOESHERE. So for me, I created _atproto as a TXT record with the value did=did:plc:otu6mg5xkk47y3ghqpq2w3oo. Yes it's weirdly double-barreled with the did=did part. This is fine.

If you want your name to be some subdomain of your domain, you can even make _atproto.somesubdomain. I tried this out by making _atproto.martin so I could be "@martin.pxtl.ca". This is great if you're running an organization and you've got many users on that domain - you can create one TXT record per-user, each with their own subdomain, so like if my son got his own Bluesky account (it would have a different did) I could give him _atproto.gooseguy so he'd be "@gooseguy.pxtl.ca".

Testing the Domain Record

Once the domain record has been created, you can test that it exists with the Powershell command Resolve-DnsName

So, to test it, mash together the TXT record's host name and your domain name, and call it as follows:

Resolve-DnsName _atproto.mydomain.ca

So for example for my "@martin.pxtl.ca" name, it would be,

Resolve-DnsName _atproto.martin.pxtl.ca

If you get a result talking about the domain and servers and authorities? It worked. If you get ugly red error messages, it didn't. That might mean you screwed up, or it just hasn't propagated yet (DNS/domain updates can be slow, give it a few more minutes).

Updating your Username

Finally, now that we can log into Bluesky and we've created proof for Bluesky that we own the domain, we can tell Bluesky to change our name to match our domain.

Invoke-RestMethod `
    -Method POST `
    -Uri https://bsky.social/xrpc/com.atproto.identity.updateHandle `
    -Headers @{Authorization = "Bearer $($sessionResponse.accessJwt)"} `
    -Body (@{handle ='mydomain.ca'} | ConvertTo-Json) `
    -ContentType 'application/json'

So again, we're doing an HTTP POST to bsky.social, but this time we're calling updateHandle instead of createSession. Since we've already got the session, we need to reference a token (that short-lived secret I mentioned before) to let them know "yes, I'm still the person you were talking to earlier". So we use the parameter -Headers @{Authorization = "Bearer $($sessionResponse.accessJwt)"} which adds the accessJwt token to this request as an Authorization header entry.

Again, we're doing a JSON body, but this time the body is just a single entry in the object handle. That should match the @ handle you're trying to get, the same one you created a TXT entry for - like, since I did both _atproto.martin.pxtl.ca and _atproto.pxtl.ca, I can do either handle='pxtl.ca' or handle=martin.pxtl.ca.

This action won't send any result if it works. It will only give you feedback if it fails.

So, check your Bluesky account. There will be errors for a minute or so, but your user handle should be updated to match your domain.

TL;DR

In short, in Powershell:

Run

$sessionResponse = Invoke-RestMethod -Method POST -Uri https://bsky.social/xrpc/com.atproto.server.createSession `
    -body (@{identifier = 'email@example.com'; password ='PASSWORDGOESHERE'} | ConvertTo-Json) `
    -ContentType 'application/json'

with your bluesky email and password.

Get $sessionResponse.did and use that when you create a new TXT record on yourdomain.com called _atproto, and give it a value of did=YOURDIDGOESHERE. If you want to use a subdomain of yourdomain.com because you feel like it or you've got friends who want to use the same domain, you can make _atproto.mysubmodmain to get that.

Now, let Bluesky know about it by calling

Invoke-RestMethod `
    -Method POST `
    -Uri https://bsky.social/xrpc/com.atproto.identity.updateHandle `
    -Headers @{Authorization = "Bearer $($sessionResponse.accessJwt)"} `
    -Body (@{handle ='yourdomain.com'} | ConvertTo-Json) `
    -ContentType 'application/json'

or whatever your domain (or subdomain or whatever) is.

Questions? Scroll up to the detailed part. Don't @ me.

comments powered by Disqus