The Anchor AppView provides a RESTful API for accessing location-based social check-ins from the AT Protocol network.
https://dropanchor.app/api
Note: All API endpoints are now under the /api/
namespace to separate them
from frontend routes.
No authentication required. All endpoints are public.
All responses are JSON with proper CORS headers enabled.
{ "checkins": [...], "cursor": "2025-07-04T10:00:00Z" }
Each check-in in the response includes full profile information:
{ "id": "3lbmo5gsjgv2f", "uri": "at://did:plc:wxex3wx5k4ctciupsv5m5stb/app.dropanchor.checkin/3lbmo5gsjgv2f", "author": { "did": "did:plc:wxex3wx5k4ctciupsv5m5stb", "handle": "tijs.social", "displayName": "Tijs", "avatar": "https://cdn.bsky.app/img/avatar/..." }, "text": "Great coffee here!", "createdAt": "2025-07-04T10:00:00Z", "coordinates": { "latitude": 37.7749, "longitude": -122.4194 }, "address": { "name": "Blue Bottle Coffee", "street": "1 Ferry Building", "locality": "San Francisco", "region": "CA", "country": "US", "postalCode": "94111" } }
{ "error": "Description of the error" }
- 1000 requests per hour per IP
- Rate limit headers included in all responses
Get recent check-ins from all users with pagination support.
Endpoint: GET /api/global
Parameters:
limit
(optional): Number of check-ins to return (default: 50, max: 100)cursor
(optional): ISO timestamp for pagination
Example Request:
curl "https://dropanchor.app/api/global?limit=10&cursor=2025-07-04T10:00:00Z"
Example Response:
{ "checkins": [ { "id": "record123", "uri": "at://did:plc:example123/app.dropanchor.checkin/record123", "author": { "did": "did:plc:example123", "handle": "alice.bsky.social" }, "text": "Amazing coffee and cozy atmosphere!", "createdAt": "2025-07-04T10:15:00Z", "coordinates": { "latitude": 52.3676, "longitude": 4.9041 }, "address": { "name": "Cafe Central", "street": "Damrak 123", "locality": "Amsterdam", "region": "North Holland", "country": "Netherlands", "postalCode": "1012 LP" } } ], "cursor": "2025-07-04T10:15:00Z" }
Get check-ins within a specified radius of coordinates using spatial queries.
Endpoint: GET /api/nearby
Required Parameters:
lat
: Latitude (decimal degrees)lng
: Longitude (decimal degrees)
Optional Parameters:
radius
: Search radius in kilometers (default: 5, max: 50)limit
: Number of results (default: 50, max: 100)
Example Request:
curl "https://dropanchor.app/api/nearby?lat=52.3676&lng=4.9041&radius=10&limit=20"
Example Response:
{ "checkins": [ { "id": "record123", "uri": "at://did:plc:example123/app.dropanchor.checkin/record123", "author": { "did": "did:plc:example123", "handle": "alice.bsky.social" }, "text": "Great spot for lunch!", "createdAt": "2025-07-04T10:15:00Z", "coordinates": { "latitude": 52.3700, "longitude": 4.9000 }, "address": { "name": "Restaurant Brown", "street": "Herengracht 456", "locality": "Amsterdam", "region": "North Holland", "country": "Netherlands", "postalCode": "1017 CA" }, "distance": 2.34 } ], "center": { "latitude": 52.3676, "longitude": 4.9041 }, "radius": 10 }
Notes:
- Results are sorted by distance (closest first)
- Distance is calculated using the Haversine formula
distance
field is included in nearby results (in kilometers)
Get all check-ins from a specific user.
Endpoint: GET /api/user
Required Parameters:
did
: User's decentralized identifier
Optional Parameters:
limit
: Number of results (default: 50, max: 100)cursor
: ISO timestamp for pagination
Example Request:
curl "https://dropanchor.app/api/user?did=did:plc:example123&limit=10"
Example Response:
{ "checkins": [ { "id": "record123", "uri": "at://did:plc:example123/app.dropanchor.checkin/record123", "author": { "did": "did:plc:example123", "handle": "alice.bsky.social" }, "text": "Love this place!", "createdAt": "2025-07-04T10:15:00Z", "coordinates": { "latitude": 52.3676, "longitude": 4.9041 }, "address": { "name": "Cafe Central", "street": "Damrak 123", "locality": "Amsterdam", "region": "North Holland", "country": "Netherlands", "postalCode": "1012 LP" } } ], "user": { "did": "did:plc:example123" } }
Get check-ins from users that the specified user follows on Bluesky.
Endpoint: GET /api/following
Required Parameters:
user
: User's DID to get following feed for
Optional Parameters:
limit
: Number of results (default: 50, max: 100)cursor
: ISO timestamp for pagination
Example Request:
curl "https://dropanchor.app/api/following?user=did:plc:example123&limit=10"
Example Response:
{ "checkins": [ { "id": "record456", "uri": "at://did:plc:friend789/app.dropanchor.checkin/record456", "author": { "did": "did:plc:friend789", "handle": "bob.bsky.social" }, "text": "Best pizza in town!", "createdAt": "2025-07-04T10:30:00Z", "coordinates": { "latitude": 52.3600, "longitude": 4.8800 }, "address": { "name": "Pizza Palace", "street": "Prinsengracht 789", "locality": "Amsterdam", "region": "North Holland", "country": "Netherlands", "postalCode": "1016 HT" } } ], "user": { "did": "did:plc:example123" }, "cursor": "2025-07-04T10:30:00Z" }
Note: Social graph data is synced daily from Bluesky. If no follows are found, returns an empty array.
Search for nearby places using OpenStreetMap data via the Overpass API. This endpoint enables location-based place discovery for check-ins.
Endpoint: GET /api/places/nearby
Required Parameters:
lat
: Latitude coordinate (-90 to 90)lng
: Longitude coordinate (-180 to 180)
Optional Parameters:
radius
: Search radius in meters (default: 300, max: 2000)categories
: Comma-separated list of OSM categories (e.g., "amenity=cafe,amenity=restaurant")
Example Request:
curl "https://dropanchor.app/api/places/nearby?lat=37.7749&lng=-122.4194&radius=500&categories=amenity%3Dcafe,amenity%3Drestaurant"
Example Response:
{ "places": [ { "id": "node:2895323815", "elementType": "node", "elementId": 2895323815, "name": "Blue Bottle Coffee", "latitude": 37.7749, "longitude": -122.4194, "address": { "$type": "community.lexicon.location.address", "name": "Blue Bottle Coffee", "street": "315 Linden St", "locality": "San Francisco", "region": "CA", "country": "US", "postalCode": "94102" }, "category": "cafe", "categoryGroup": "food_and_drink", "icon": "☕", "distanceMeters": 45.8, "formattedDistance": "46m", "tags": { "amenity": "cafe", "name": "Blue Bottle Coffee", "addr:street": "Linden St", "addr:housenumber": "315", "addr:city": "San Francisco", "addr:state": "CA", "addr:country": "US", "addr:postcode": "94102" } } ], "totalCount": 23, "searchRadius": 500, "categories": ["amenity=cafe", "amenity=restaurant"], "searchCoordinate": { "latitude": 37.7749, "longitude": -122.4194 } }
Place Categories:
Common category filters include:
- Food & Drink:
amenity=restaurant
,amenity=cafe
,amenity=bar
,shop=supermarket
- Entertainment:
amenity=cinema
,tourism=attraction
,leisure=park
- Sports:
leisure=fitness_centre
,leisure=climbing
,leisure=swimming_pool
- Shopping:
shop=clothes
,shop=electronics
,shop=books
- Accommodation:
tourism=hotel
,tourism=hostel
- Transportation:
amenity=bus_station
,amenity=fuel
Notes:
- Results include complete address information extracted from OpenStreetMap data
- Places are sorted by distance (closest first)
- Address data follows the AT Protocol
community.lexicon.location.address
format - Results are cached for 5 minutes per location to improve performance
- Uses Overpass API for real-time OpenStreetMap data
Get AppView health metrics and processing statistics.
Endpoint: GET /api/stats
Parameters: None
Example Request:
curl "https://dropanchor.app/api/stats"
Example Response:
{ "totalCheckins": 1247, "totalUsers": 89, "recentActivity": 23, "lastProcessingRun": "2025-07-04T10:00:00Z", "timestamp": "2025-07-04T10:45:00Z" }
Fields:
totalCheckins
: Total number of check-ins in the databasetotalUsers
: Number of unique users who have checked inrecentActivity
: Check-ins in the last 24 hourslastProcessingRun
: Timestamp of last successful data ingestiontimestamp
: Current server timestamp
interface Checkin {
id: string; // Unique record identifier
uri: string; // Full AT Protocol URI
author: {
did: string; // User's decentralized identifier
handle: string; // User's Bluesky handle
};
text: string; // Check-in message/review
createdAt: string; // ISO timestamp
coordinates?: { // Optional location coordinates
latitude: number;
longitude: number;
};
address?: { // Optional resolved venue information
name?: string; // Venue name
street?: string; // Street address
locality?: string; // City
region?: string; // State/Province
country?: string; // Country
postalCode?: string; // Postal/ZIP code
};
distance?: number; // Distance in km (only in nearby results)
}
Status Code | Description |
---|---|
200 | Success |
400 | Bad Request - Missing or invalid parameters |
404 | Not Found - Invalid endpoint |
429 | Too Many Requests - Rate limit exceeded |
500 | Internal Server Error |
{ "error": "lat and lng parameters required" }
{ "error": "Invalid latitude or longitude" }
{ "error": "Rate limit exceeded. Try again later." }
class AnchorAPI {
private baseURL = "https://dropanchor.app/api";
async getGlobalFeed(limit = 50, cursor?: string) {
const params = new URLSearchParams({
limit: limit.toString(),
...(cursor && { cursor }),
});
const response = await fetch(`${this.baseURL}/global?${params}`);
return response.json();
}
async getNearbyCheckins(lat: number, lng: number, radius = 5, limit = 50) {
const params = new URLSearchParams({
lat: lat.toString(),
lng: lng.toString(),
radius: radius.toString(),
limit: limit.toString(),
});
const response = await fetch(`${this.baseURL}/nearby?${params}`);
return response.json();
}
async getUserCheckins(did: string, limit = 50, cursor?: string) {
const params = new URLSearchParams({
did,
limit: limit.toString(),
...(cursor && { cursor }),
});
const response = await fetch(`${this.baseURL}/user?${params}`);
return response.json();
}
async getFollowingFeed(userDid: string, limit = 50, cursor?: string) {
const params = new URLSearchParams({
user: userDid,
limit: limit.toString(),
...(cursor && { cursor }),
});
const response = await fetch(`${this.baseURL}/following?${params}`);
return response.json();
}
async getNearbyPlaces(
lat: number,
lng: number,
radius = 300,
categories?: string[],
) {
const params = new URLSearchParams({
lat: lat.toString(),
lng: lng.toString(),
radius: radius.toString(),
...(categories && { categories: categories.join(",") }),
});
const response = await fetch(`${this.baseURL}/places/nearby?${params}`);
return response.json();
}
async getStats() {
const response = await fetch(`${this.baseURL}/stats`);
return response.json();
}
}
import Foundation class AnchorAPI { private let baseURL = "https://dropanchor.app/api" func getGlobalFeed(limit: Int = 50, cursor: String? = nil) async throws -> GlobalFeedResponse { var components = URLComponents(string: "\(baseURL)/global")! components.queryItems = [ URLQueryItem(name: "limit", value: String(limit)) ] if let cursor = cursor { components.queryItems?.append(URLQueryItem(name: "cursor", value: cursor)) } let (data, _) = try await URLSession.shared.data(from: components.url!) return try JSONDecoder().decode(GlobalFeedResponse.self, from: data) } func getNearbyCheckins(lat: Double, lng: Double, radius: Double = 5, limit: Int = 50) async throws -> NearbyResponse { var components = URLComponents(string: "\(baseURL)/nearby")! components.queryItems = [ URLQueryItem(name: "lat", value: String(lat)), URLQueryItem(name: "lng", value: String(lng)), URLQueryItem(name: "radius", value: String(radius)), URLQueryItem(name: "limit", value: String(limit)) ] let (data, _) = try await URLSession.shared.data(from: components.url!) return try JSONDecoder().decode(NearbyResponse.self, from: data) } }
- Use pagination with cursors for large datasets
- Cache responses when appropriate
- Use reasonable limits (avoid requesting more than needed)
- Keep radius reasonable (< 50km) for good performance
- Consider user's location accuracy when setting radius
- Sort by distance when displaying nearby results
- Check if social graph data is available before using /following
- Handle empty following lists gracefully
- Consider falling back to /global when following feed is empty
- Always check for error responses
- Implement retry logic for temporary failures
- Handle rate limits appropriately
For API support and feature requests, please visit:
- GitHub: Anchor AppView Issues
- AT Protocol Documentation: atproto.com
- Initial API release
- Global, nearby, user, and following feeds
- Statistics endpoint
- Spatial queries with Haversine distance
- AT Protocol integration