Connecting records, web pages and apps

This is a bit of a follow-up to Mapping web pages to canonical AT records

We’re continuing to work on making the Bluesky app more Atmospheric, building on features like the Germ, Streamplace, and Standard Site integrations. This leads to some common questions that have cropped up before, like: what’s the best way to link an atproto record and a web page, and how does it know which app someone is using? This is relevant to a few proposals and other conversations and I want to try to tie them together.

Here is where we’re leaning. None of it is final, and we’d like to hear where you disagree.

1. Give your app a profile record
There’s usually no record saying “this account uses this app,” so usage has to be inferred from unique DIDs under a namespace or per-app record conventions. That works for some apps and badly for others (standard.site is an interesting case here because it could mean one of several apps). At minimum a profile record could just provide an app’s canonical URL for that account, consumers get a second thing they need.

2. Adopt at-tags
The at-tags meta-tag proposal is stellar with broad community support that we’d also like to implement and see more broadly supported. The proposal is small and easy to review, we think it’s sound, and want to help push it towards a recommendation. If you publish pages that correspond to records, consider emitting them. If you run a publishing platform, we’d like to know whether standard.site’s verification flow could adopt them too, since a shared convention would drive more usage.

3. Closing the loop on at-tags
We’d also like to explore what closing the at-tags loop would look like. The current at-tags proposal only links from an HTTP URI to an AT URI, which feels like a potential trip hazard (this is acknowledged by the at-tags proposal). Bi-directional linking would help to establish trust and also give firehose consumers a pointer back to your web app. How should that work?

Overall

  • Does a profile-record convention sound right?

  • If you’ve shipped at-tags or something similar, where and how are you using them? Did they open any new questions as you were implementing them?

  • How would you want record owners to attest to URLs?

4 Likes

How would you want record owners to attest to URLs?

Just adding that one thing that’s come up in discussions on the network (and internally) is a way to associate a record with multiple URLs.

Example: a Standard.site document may be published using Leaflet, but also appear on the author’s personal site. Both are valid, but currently devs can only verify using the URLs baked into the records.

Curious if anyone has thoughts on that! Especially around how something like at-tags could point scrapers to that resource(s).

3 Likes

Glad to see at tags being adopted! As a prolific creator of “enter a URL and let me do things with it” tools, I’d love to leverage them (as well as using them on my ATproto-backed blog).

One thing I’d like to correct before this gets spread further is something I (IMO) fumbled when making the original proposal about mapping at-tags to the rel attribute:

rel="alternate" is about alternate representation of a specific document, not “alternative records the page is associated with”. If Bluesky app is adopting this, we may want to fix that part of the spec up before that becomes a point of confusion we carry forever. I’ll open a separate thread, but wanted to make sure people knew.

With this out of the way,

Not an established pattern AFAIK, but it’d be nice to have a standard Lexicon field for this. I’m gonna be using community.lexicon namespace just because it’s there.

Something like:

{
 // a record, any type...
 // ...record data....
  "community.lexicon.meta.refs": {
    canonical: [....],
    alternates: [ "https://...."]
  }
 // ....
 // ...more record data...
}

Idea would be:

  1. define the “refs” Lexicon as a separate lexicon
  2. insert it into the record in a specific namespaced field, without needing to define it in the original Lexicon

That way this information travels together with the record, but standardized and without collisions. Like meta tags, you can always expect to find it there and at that name, if provided.

We recently integrated standard.site on the Google Open Source Blog (opensource.googleblog.com, running on Blogger) and just added <meta name="at:canonical"> to our live template alongside <link rel="site.standard.document">. Here is the short version on your three points, with a little context below:

TL;DR

  1. App profile records:
    • Breaks if it assumes 1 account (DID) = 1 app = 1 domain.
    • Needs to support 1-to-many accounts (one author publishing across multiple apps/sites, or one organizational DID hosting multiple blog domains).
    • For publishing, site.standard.publication already handles domain routing; an optional generator / app field on the publication or document record (like Atom’s <generator>) seems cleaner than a per-account singleton.
  2. Adopting at-tags in standard.site:
    • Yes to at:canonical—we just rolled it out live on opensource.googleblog.com as a 1-line addition alongside <link rel="site.standard.document">, and it cleanly avoids the RFC 3986 <link href="at://..."> validator issues.
    • Yes to at:author—really useful for multi-author org blogs where the repo DID is the organization (at:me), not the individual writer.
    • Caution on at:alternate—pointing at:alternate at a parent publication or comment thread breaks HTML alternate semantics (and risks clients redirecting an article URL to the blog homepage or a comment thread). Explicit tags like at:publication and at:blog:comments feel safer.
  3. Closing the loop (bi-directional URL attestation):
    • standard.site already closes the loop for a single canonical URL (<meta name="at:canonical"> → site.standard.document → site.standard.publication ↔ .well-known).
    • The missing piece is multi-URL attestation—both for syndicated/cross-posted articles (@esb.lol’s point) and for shortlinks (goo.gle, bit.ly, etc.).

Details & crossover with shortlinks / third-party schedulers

I posted a related question earlier today (Standard Site, cardyb and shortlinks) that overlaps quite a bit with #2 and #3:

  • Shortlinks + at:canonical in cardyb: Almost every org and foundation uses shortlinks (goo.gle, mzl.la, bit.ly) for campaign tracking. Right now, cardyb follows the 301/302 redirect to grab OpenGraph <meta> tags from the landing page, but drops standard.site verification because the shortlink hostname doesn’t match the destination domain. If cardyb reads <meta name="at:canonical"> on the resolved page (and checks .well-known against the resolved <link rel="canonical"> origin), or if “closing the loop” lets a site.standard.document record attest to its official shortlink alongside its canonical URL, shortlinks wouldn’t break record linking.
  • Write-time (cardyb) vs. index-time linking: Many teams post via third-party schedulers (Sprinklr, Buffer) that construct app.bsky.embed.external directly via the API and never call cardyb to attach associatedRefs. Even with at-tags in HTML <head>, those posts won’t connect back to the AT record unless AppViews/indexers also resolve links (or match attested URLs on records) asynchronously after the post hits the firehose.

rel="alternate" is about alternate representation of a specific document, not “alternative records the page is associated with”

This is a good shout. I think @fabricatedtechnobabble.com is saying something similar. Perhaps something more like at:associated is a better name for this?

Like meta tags, you can always expect to find it there and at that name, if provided.

Yeah you got it! That’s one of the things we’re looking for and we think the ecosystem needs. A field such as you suggest makes some sense to me. I especially like the “alternate” term here being used to note that an alternate resource for this atproto records lives at https://… which seems to fit the spirit of that semantic more closely.


@fabricatedtechnobabble.com thanks for your input! Exciting to hear that you’re integrating, looking forward to checking that out.

Have just a couple thoughts, hopefully I’m understanding your points correctly :folded_hands:

Breaks if it assumes 1 account (DID) = 1 app = 1 domain

I think Jim’s first item is more about establishing a pattern for an app to denote a user of their app. This is independent of Standard.site, and would be app specific: pub.leaflet.actor.profile rather than something like site.standard.actor.profile.

There are probably more examples of this out there, but to note one instance, the community has drafted this lexicon for apps to declare their metadata, as well as accountIndicators to tell give integrators some indication of who’s using their app.

Jim’s point about “consumers get a second thing they need” is suggesting that a profile on (for example) Leaflet could potentially specify the URL pattern(s) they “own” on Leaflet, and help integrators know if they can safely associate records with the URLs they found them on (via at-tags).

Yes to at:author—really useful for multi-author org blogs

I could be wrong here, but one thing to note is that at the moment, there’s no way for an author who is not the repo DID to bi-directionally verify that they authored/co-authored a record. Example: I could publish an article and output metadata specifying you as a co-author.

From trusted sources such as Google, we may be be able to fudge it. But we hope that some of these convos around bidirectionality and associated ref records may help find a pattern for record co-authorship as well.

standard.site already closes the loop

Yep! And they really paved the way here, making it really easy for us to integrate Standard.site quickly. But for arbitrary records on atproto, we’re looking to help establish a pattern to do the same sort of URL attestation.

If we find an article by an author on both good.com and evil.com, we need a way to know if the author has expressed trust in one or both of these URLs.

Shortlinks + at:canonical in cardyb

This is a really good point that hasn’t come up in a while, but I’ll add it to the meeting agenda this week. I believe we avoided this because of how we write these external embeds, as you note in the next point.

Basically it’s this, iirc: at the time of posting, we resolve metadata from cardyb and write it directly to the record. A bit.ly may indeed resolve to good.com at time of writing. But the owner of the shortlink could change the target to evil.com later on, and the external embed in the post would still appear like good.com

As you note, there are things we could explore here. Let us know if you have other thoughts!

Really glad to see this conversation! This is exactly the kind of thing we’ve needed in Bridgy Fed for a while now: a way to say “this record is also this other object, on this other network.”

For identities, this is doable right now, with alsoKnownAs in DID docs. For web sites and fediverse accounts bridged into the Atmosphere, we put their home page URL and ActivityPub actor id, respectively, into their bridged DiD doc’s alsoKnownAs.

We don’t yet have the equivalent for individual posts, though. Right now, we put the original post’s URI in the bridgyOriginalUrl custom field, but that’s obviously not ideal.

The web and the fediverse already have good, structured ways to do this. You all have already discussed the web side, rel=alternate and rel=canonical links, and the fediverse reuses those with its FEP-fffd proxy links, both of which we use in Bridgy Fed. As an example, here’s the proxy link in the bridged ActivityPub AS2 object for this Bluesky post:

"url": [
  ...,
  {
    "href": "at://did:plc:xgvzy7ni6ig6ievcbls5jaxe/app.bsky.feed.post/3mxhjydoiwk2i",
    "rel": "canonical",
    "type": "Link"
  }
]

(There’s also an optional mediaType property, which we don’t use, for the MIME type.)

I could see us doing the exact same thing in ATProto, in a sidecar record. I’ve had sketching this out on my todo list for a long time, ideally as part of lexicon.community. Maybe this is the kick in the butt I need to start that.

1 Like