Developer docs
Add Atmosphere “Open in…” links, smart client recommendations, and AT-URI resolution to your own app — with two MIT-licensed packages and a public API.
Overview
The same waypoint catalog, recommendations, and link logic that power aturi.to are published as two standalone, MIT-licensed npm packages so you can drop them into any Atmosphere (AT Protocol) app:
@aturi.to/waypoints— a zero-dependency, framework-agnostic core: the client catalog, per-client “Open in…” link builders, recommendations, and URL ⇄ AT-URI resolution. Works in the browser, Node 18+, and edge runtimes.@aturi.to/waypoints-react— a headless-first React picker UI plus client icons, built on the core. Ships zero CSS by default and is fully themeable.
Both are dual-licensed MIT (the aturi.to app itself is GPL-3.0) to remove the adoption barrier. Prefer not to install anything? The hosted Resolve API does the same work over HTTP.
@aturi.to/waypoints
The zero-dependency core. Turn an AT URI into per-client links, recommend the best client for a record type, and reverse-resolve a pasted URL back into an AT URI.
npm install @aturi.to/waypointsResolve an AT URI or a pasted URL
import { resolveAtUri, resolveUrl } from '@aturi.to/waypoints';
// AT URI -> waypoints
const result = resolveAtUri('at://did:plc:abc/app.bsky.feed.post/3k7');
result?.waypoints; // [{ id: 'anisota', name: 'Anisota', category, url }, ...]
result?.recommended; // { ids: ['bluesky', 'anisota', ...], label: 'Recommended for Posts' }
// Pasted page URL -> waypoints (offline pattern match)
const fromUrl = await resolveUrl('https://bsky.app/profile/alice.bsky.social/post/3k7');What’s included
- High-level resolvers:
resolveAtUri,resolveUrl,buildWaypointsForParsed, andresolveViaApi(a typed client for the hosted endpoint). - Catalog & recommendations:
getWaypointDataForType,getCategorizedWaypointsData,getRecommendedWaypointsData, and the rawWAYPOINT_DESTINATIONS_DATAcatalog. - Parsing:
parseURI,parseAtUri,matchSupportedUrl,resolveHandle.
A handful of destinations (pdsls, atp.tools, Margin, Grain, Popfeed) only produce useful URLs when a DID is known — they’re filtered out unless a DID is available, so pass one in or supply a resolveHandle to resolveUrl. Full reference in the package README.
@aturi.to/waypoints-react
A drop-in React “Open in…” picker. Headless-first: it ships zero CSS and emits stable, namespaced styling hooks, so you can use your own design system, opt into the polished theme, or drop down to a hook and render everything yourself. It re-exports the entire core, so a single install gives you the components and the resolvers.
npm install @aturi.to/waypoints-react
# peers (you almost certainly already have react/react-dom):
npm install react react-dom lucide-react1. Drop-in picker
Renders clean semantic markup with no CSS attached. Every element carries a data-aturi-wp attribute and an aturi-wp-* class; map your own via classNames, pass unstyled to drop the built-ins, or replace rows with the renderWaypoint prop.
import { WaypointPicker } from '@aturi.to/waypoints-react';
<WaypointPicker
type="post"
handle="alice.bsky.social"
collection="app.bsky.feed.post"
rkey="3k7qw..."
/>;2. The useWaypoints hook
For full control, the hook returns render-ready data plus copy / open helpers — no markup at all.
import { useWaypoints } from '@aturi.to/waypoints-react';
function MyPicker() {
const { recommended, categories, waypoints, copy, open } = useWaypoints({
type: 'post',
handle: 'alice.bsky.social',
collection: 'app.bsky.feed.post',
rkey: '3k7qw...',
});
return (
<ul>
{waypoints.map((w) => (
<li key={w.id}>
{w.icon}
<button onClick={() => open(w.url)}>{w.name}</button>
<button onClick={() => copy(w.url)}>Copy</button>
</li>
))}
</ul>
);
}3. The polished theme (opt-in)
Want the Aturi look without writing CSS? Import the stylesheet once. It targets the namespaced classes and is fully themeable via --aturi-wp-* CSS custom properties (with light/dark defaults).
import '@aturi.to/waypoints-react/styles.css';
import { WaypointPicker } from '@aturi.to/waypoints-react';Server vs. client: the package is a client component (it carries "use client"), so it works out of the box in the Next.js App Router. For framework-agnostic helpers inside a Server Component, import them from @aturi.to/waypoints directly.
Resolve API
Don’t want to install anything? Hit the hosted endpoint from a share sheet, an Apple Shortcut, or any client — no login, no API keys. It returns the resolved waypoints and recommendations for a page URL or an AT URI.
GET https://aturi.to/api/resolve?url=<encoded-page-url>
GET https://aturi.to/api/resolve?atUri=at://...The core package’s resolveViaApi() is a typed client for this endpoint. It’s the right choice from a browser, where fetching arbitrary pages is blocked by CORS.
Build an aturi.to link
Universal links need no SDK at all — just rewrite an AT URI into an aturi.to/profile/… URL and the recipient picks their client on a friendly landing page.
function toAturiLink(atUri: string): string {
const uri = atUri.replace('at://', '');
return `https://aturi.to/profile/${uri}`;
}License
@aturi.to/waypoints and @aturi.to/waypoints-react are MIT © dame.art. The aturi.to web app and browser extension are licensed GPL-3.0 — the packages are intentionally dual-licensed MIT so other Atmosphere developers can adopt them freely.
Building an Atmosphere client and want it in the catalog? See the supported waypoints or open a PR.