Skip to main content

at.new link API

An at.new link opens the AT Protocol composer with a record prefilled from the URL. The URL is the entire interface, so it can be built directly in any language. Opening a link never creates anything: the person who opens it reviews the record and presses Create.

Parameters

/<nsid>Path form: open a template for that record type
collectionSame as the path form
rkeyRecord key. Omit it to mint a fresh TID on create
recordThe full record, percent-encoded JSON (small records)
#record=<payload>The full record in the URL fragment (large records)
textBecomes the record's text field
titleAdded before the text
urlAdded after the text, on its own line

Examples:

https://at.new/xyz.statusphere.status
https://at.new/?collection=app.bsky.feed.post&text=Hello%20world
https://at.new/?record=%7B%22%24type%22%3A%22xyz.statusphere.status%22%2C%22status%22%3A%22%F0%9F%8E%89%22%7D

Record payloads

The record JSON includes $type — that selects the collection. If the record lacks a valid $type, a collection parameter fills it in. Records up to a few hundred bytes fit in ?record= as percent-encoded JSON. Anything larger goes in the fragment, #record=, which is processed in the opener's browser and never sent to a server. The limits are 100 KB encoded and 1 MB decoded.

The fragment accepts three encodings: percent-encoded JSON, b64: followed by base64url of the UTF-8 JSON, or dfl: followed by base64url of raw-deflate-compressed JSON. Base64url is base64 with + as -, / as _, and padding removed. Use b64: unless your records are large.

function atNewLink(record) {
  const bytes = new TextEncoder().encode(JSON.stringify(record));
  let binary = "";
  for (let i = 0; i < bytes.length; i += 0x8000) {
    binary += String.fromCharCode(...bytes.subarray(i, i + 0x8000));
  }
  const b64 = btoa(binary)
    .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
  return `https://at.new/#record=b64:${b64}`;
}

For the compressed form: raw deflate has no zlib header. Node: zlib.deflateRawSync(buf).toString("base64url"). Python: zlib.compressobj(wbits=-15).

Template variables

String values in the record can contain placeholders, which makes a link a reusable template. Built-ins are filled in when the opener presses Create:

{{$now}}the current time, as an ISO 8601 UTC instant
{{$did}}the opener's signed-in DID
{{$handle}}the opener's signed-in handle
{{$lang}}the opener's primary language subtag, like "en"

Any placeholder without a $, like {{message}}, becomes a text input the opener must fill before creating. Placeholders work inside longer strings and inside arrays.

atNewLink({
  $type: "com.example.guestbook.entry",
  message: "{{message}}",
  author: "{{$did}}",
  createdAt: "{{$now}}",
});

Everyone who opens this link fills in their own message and creates the entry under their own DID, with a fresh timestamp.

More template variables coming soon.

Notes

Omit TID-style record keys so each opener gets a fresh key and repeat clicks never overwrite. Meaningful keys like self are kept.

A machine-readable version of these docs lives at /llms.txt. Give that URL to an AI agent and it can build these links.