Smokesignal XRPC API Reference#
Base URL: https://smokesignal.events
All XRPC endpoints are available at /xrpc/{method-nsid}. Responses are JSON (application/json).
Table of Contents#
Public Query Endpoints#
community.lexicon.calendar.searchEvents#
Search for events by text query, organizer DID, and/or location. Requires OpenSearch to be configured on the server.
Method: GET
Path: /xrpc/community.lexicon.calendar.searchEvents
Authentication: None
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
query |
string | No | — | Free-text search query. Use "upcoming" to search for upcoming events. |
repository |
string | No | — | DID of the event organizer to filter by (did:plc:... or did:web:...). |
location |
string or string[] | No | — | Location CID(s) to filter by. Repeat the parameter for multiple values. |
limit |
integer | No | 10 |
Maximum number of results to return. |
At least one of query, repository, or location must be provided. If none are provided, an empty result set is returned.
Response Body:
{
"results": [
{
"$type": "community.lexicon.calendar.eventView",
"name": "Rust Meetup",
"description": "Monthly Rust meetup",
"startsAt": "2026-03-01T18:00:00Z",
"endsAt": "2026-03-01T20:00:00Z",
"countGoing": 42,
"countInterested": 15,
"countNotGoing": 3,
"url": "https://smokesignal.events/alice.bsky.social/3abc123"
}
]
}
Each result contains the event record fields (flattened), RSVP counts, and a web URL for the event.
curl Example:
# Search by text
curl 'https://smokesignal.events/xrpc/community.lexicon.calendar.searchEvents?query=rust+meetup&limit=5'
# Search by organizer DID
curl 'https://smokesignal.events/xrpc/community.lexicon.calendar.searchEvents?repository=did:plc:1234abcd'
# Search upcoming events
curl 'https://smokesignal.events/xrpc/community.lexicon.calendar.searchEvents?query=upcoming&limit=10'
# Search by multiple locations
curl 'https://smokesignal.events/xrpc/community.lexicon.calendar.searchEvents?location=bafyreiabc&location=bafyreixyz'
Example HTTP Request:
GET /xrpc/community.lexicon.calendar.searchEvents?query=rust+meetup&limit=5 HTTP/1.1
Host: smokesignal.events
Accept: application/json
Example HTTP Response:
HTTP/1.1 200 OK
Content-Type: application/json
{
"results": [
{
"$type": "community.lexicon.calendar.eventView",
"name": "Rust Meetup",
"description": "Monthly Rust meetup in Tallinn",
"startsAt": "2026-03-01T18:00:00Z",
"endsAt": "2026-03-01T20:00:00Z",
"mode": "physical",
"countGoing": 42,
"countInterested": 15,
"countNotGoing": 3,
"url": "https://smokesignal.events/alice.bsky.social/3abc123"
}
]
}
Error Responses:
| Status | Error | Description |
|---|---|---|
| 404 | invalid_repository |
repository parameter is not a valid did:plc or did:web value. |
| 503 | search_unavailable |
OpenSearch is not configured on the server. |
| 500 | search_error |
Failed to initialize or execute the search. |
community.lexicon.calendar.getEvent#
Retrieve a single event by its organizer DID and record key.
Method: GET
Path: /xrpc/community.lexicon.calendar.getEvent
Authentication: None
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
repository |
string | Yes | DID of the event organizer (did:plc:... or did:web:...). |
record_key |
string | Yes | Record key (rkey) of the event. |
Response Body:
{
"$type": "community.lexicon.calendar.eventView",
"name": "Rust Meetup",
"description": "Monthly Rust meetup",
"startsAt": "2026-03-01T18:00:00Z",
"endsAt": "2026-03-01T20:00:00Z",
"countGoing": 42,
"countInterested": 15,
"countNotGoing": 3,
"url": "https://smokesignal.events/alice.bsky.social/3abc123"
}
The response contains the event record fields (flattened at the top level), RSVP counts, and a web URL.
curl Example:
curl 'https://smokesignal.events/xrpc/community.lexicon.calendar.getEvent?repository=did:plc:1234abcd&record_key=3abc123'
Example HTTP Request:
GET /xrpc/community.lexicon.calendar.getEvent?repository=did:plc:1234abcd&record_key=3abc123 HTTP/1.1
Host: smokesignal.events
Accept: application/json
Example HTTP Response:
HTTP/1.1 200 OK
Content-Type: application/json
{
"$type": "community.lexicon.calendar.eventView",
"name": "Rust Meetup",
"description": "Monthly Rust meetup in Tallinn",
"startsAt": "2026-03-01T18:00:00Z",
"endsAt": "2026-03-01T20:00:00Z",
"mode": "physical",
"countGoing": 42,
"countInterested": 15,
"countNotGoing": 3,
"url": "https://smokesignal.events/alice.bsky.social/3abc123"
}
Error Responses:
| Status | Error | Description |
|---|---|---|
| 404 | not_found |
Invalid repository DID format or event record not found. |
community.lexicon.calendar.getRSVP#
Retrieve a specific RSVP record for a given event and identity.
Method: GET
Path: /xrpc/community.lexicon.calendar.getRSVP
Authentication: None
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
event |
string | Yes | AT-URI of the event (e.g., at://did:plc:1234abcd/community.lexicon.calendar.event/3abc123). |
identity |
string | Yes | DID or handle of the RSVP author. Handles are resolved to DIDs. |
Response Body:
{
"uri": "at://did:plc:5678efgh/community.lexicon.calendar.rsvp/3xyz789",
"cid": "bafyreiabc123",
"record": {
"$type": "community.lexicon.calendar.rsvp",
"event": "at://did:plc:1234abcd/community.lexicon.calendar.event/3abc123",
"status": "going",
"createdAt": "2026-02-10T12:00:00Z"
}
}
curl Example:
curl 'https://smokesignal.events/xrpc/community.lexicon.calendar.getRSVP?event=at://did:plc:1234abcd/community.lexicon.calendar.event/3abc123&identity=did:plc:5678efgh'
Example HTTP Request:
GET /xrpc/community.lexicon.calendar.getRSVP?event=at%3A%2F%2Fdid%3Aplc%3A1234abcd%2Fcommunity.lexicon.calendar.event%2F3abc123&identity=did%3Aplc%3A5678efgh HTTP/1.1
Host: smokesignal.events
Accept: application/json
Example HTTP Response:
HTTP/1.1 200 OK
Content-Type: application/json
{
"uri": "at://did:plc:5678efgh/community.lexicon.calendar.rsvp/3xyz789",
"cid": "bafyreiabc123",
"record": {
"$type": "community.lexicon.calendar.rsvp",
"event": "at://did:plc:1234abcd/community.lexicon.calendar.event/3abc123",
"status": "going",
"createdAt": "2026-02-10T12:00:00Z"
}
}
Error Responses:
| Status | Error | Description |
|---|---|---|
| 400 | InvalidRequest |
Invalid event AT-URI format or invalid identity DID. |
| 404 | NotFound |
RSVP not found for the given event and identity. |
| 500 | InternalServerError |
Database error while fetching the RSVP. |
community.lexicon.calendar.listRSVPs#
List all RSVPs for a given identity with cursor-based pagination.
Method: GET
Path: /xrpc/community.lexicon.calendar.listRSVPs
Authentication: None
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
identity |
string | Yes | — | DID or handle of the user to list RSVPs for. Handles are resolved to DIDs. |
limit |
integer | No | 50 |
Maximum number of results. Clamped to range [1, 100]. |
cursor |
string | No | — | RFC 3339 datetime string from a previous response for pagination. |
Response Body:
{
"rsvps": [
{
"uri": "at://did:plc:5678efgh/community.lexicon.calendar.rsvp/3xyz789",
"cid": "bafyreiabc123",
"record": {
"$type": "community.lexicon.calendar.rsvp",
"event": "at://did:plc:1234abcd/community.lexicon.calendar.event/3abc123",
"status": "going",
"createdAt": "2026-02-10T12:00:00Z"
},
"eventUri": "at://did:plc:1234abcd/community.lexicon.calendar.event/3abc123",
"status": "going"
}
],
"cursor": "2026-02-10T12:00:00Z"
}
The cursor field is only present when there are more results available. Pass it as the cursor query parameter to fetch the next page.
curl Example:
# First page
curl 'https://smokesignal.events/xrpc/community.lexicon.calendar.listRSVPs?identity=did:plc:5678efgh&limit=25'
# Next page using cursor
curl 'https://smokesignal.events/xrpc/community.lexicon.calendar.listRSVPs?identity=did:plc:5678efgh&limit=25&cursor=2026-02-10T12:00:00Z'
Example HTTP Request:
GET /xrpc/community.lexicon.calendar.listRSVPs?identity=did:plc:5678efgh&limit=25 HTTP/1.1
Host: smokesignal.events
Accept: application/json
Example HTTP Response:
HTTP/1.1 200 OK
Content-Type: application/json
{
"rsvps": [
{
"uri": "at://did:plc:5678efgh/community.lexicon.calendar.rsvp/3xyz789",
"cid": "bafyreiabc123",
"record": {
"$type": "community.lexicon.calendar.rsvp",
"event": "at://did:plc:1234abcd/community.lexicon.calendar.event/3abc123",
"status": "going",
"createdAt": "2026-02-10T12:00:00Z"
},
"eventUri": "at://did:plc:1234abcd/community.lexicon.calendar.event/3abc123",
"status": "going"
},
{
"uri": "at://did:plc:5678efgh/community.lexicon.calendar.rsvp/3def456",
"cid": "bafyreidef456",
"record": {
"$type": "community.lexicon.calendar.rsvp",
"event": "at://did:plc:9999zzzz/community.lexicon.calendar.event/3ghi789",
"status": "interested",
"createdAt": "2026-02-08T09:30:00Z"
},
"eventUri": "at://did:plc:9999zzzz/community.lexicon.calendar.event/3ghi789",
"status": "interested"
}
],
"cursor": "2026-02-08T09:30:00Z"
}
Error Responses:
| Status | Error | Description |
|---|---|---|
| 400 | InvalidRequest |
Invalid identity DID format or invalid cursor format (must be RFC 3339). |
| 500 | InternalServerError |
Database error while fetching RSVPs. |
Authenticated Procedure Endpoints#
These endpoints require an AT Protocol JWT Bearer token. See Authentication for details.
events.smokesignal.rsvp.linkAttestation#
Link an external attestation record (e.g., from a ticket purchase on ti.to or Eventbrite) to an event. This creates an acceptance ticket that the target user can later confirm to complete their RSVP.
Method: POST
Path: /xrpc/events.smokesignal.rsvp.linkAttestation
Authentication: Required (AT Protocol JWT Bearer token)
Authorization: The authenticated caller must be both the event organizer and the attestation record owner.
Request Body (application/json):
| Field | Type | Required | Description |
|---|---|---|---|
did |
string | Yes | DID of the target user the attestation is for. |
event |
string | Yes | AT-URI of the event. |
aturi |
string | Yes | AT-URI of the remote attestation record. |
Response Body (success):
{}
curl Example:
curl -X POST 'https://smokesignal.events/xrpc/events.smokesignal.rsvp.linkAttestation' \
-H 'Authorization: Bearer <at-proto-jwt>' \
-H 'Content-Type: application/json' \
-d '{
"did": "did:plc:5678efgh",
"event": "at://did:plc:1234abcd/community.lexicon.calendar.event/3abc123",
"aturi": "at://did:plc:1234abcd/com.example.attestation/3att456"
}'
Example HTTP Request:
POST /xrpc/events.smokesignal.rsvp.linkAttestation HTTP/1.1
Host: smokesignal.events
Authorization: Bearer eyJhbGciOi...
Content-Type: application/json
{
"did": "did:plc:5678efgh",
"event": "at://did:plc:1234abcd/community.lexicon.calendar.event/3abc123",
"aturi": "at://did:plc:1234abcd/com.example.attestation/3att456"
}
Example HTTP Response:
HTTP/1.1 200 OK
Content-Type: application/json
{}
Error Responses:
| Status | Error | Description |
|---|---|---|
| 401 | AuthRequired |
Missing, invalid, or expired authentication token, or no issuer in token. |
| 400 | InvalidRequest |
Invalid target DID format, invalid event AT-URI, or invalid attestation AT-URI. |
| 403 | Forbidden |
Caller is not the event organizer or not the attestation record owner. |
| 404 | NotFound |
Event not found. |
| 500 | InternalServerError |
Failed to store the attestation link. |
events.smokesignal.event.configure#
Configure Smokesignal-specific settings for an event, such as email confirmation requirements and external ticketing redirects.
Method: POST
Path: /xrpc/events.smokesignal.event.configure
Authentication: Required (AT Protocol JWT Bearer token)
Authorization: The authenticated caller must be the event organizer (owner of the event's AT-URI).
Request Body (application/json):
| Field | Type | Required | Description |
|---|---|---|---|
event |
string | Yes | AT-URI of the event to configure. Must belong to the authenticated user. |
requireConfirmedEmail |
boolean | No | When true, RSVPs require a confirmed email address. |
disableDirectRsvp |
boolean | No | When true, the RSVP button redirects to an external ticketing URL. |
rsvpRedirectUrl |
string | No | HTTPS URL to redirect users to for external ticketing (max 2048 characters). Must use the https scheme. Pass an empty string to clear. |
At least one of requireConfirmedEmail, disableDirectRsvp, or rsvpRedirectUrl must be provided.
Response Body (success):
{}
curl Example:
# Enable email confirmation requirement
curl -X POST 'https://smokesignal.events/xrpc/events.smokesignal.event.configure' \
-H 'Authorization: Bearer <at-proto-jwt>' \
-H 'Content-Type: application/json' \
-d '{
"event": "at://did:plc:1234abcd/community.lexicon.calendar.event/3abc123",
"requireConfirmedEmail": true
}'
# Set up external ticketing redirect
curl -X POST 'https://smokesignal.events/xrpc/events.smokesignal.event.configure' \
-H 'Authorization: Bearer <at-proto-jwt>' \
-H 'Content-Type: application/json' \
-d '{
"event": "at://did:plc:1234abcd/community.lexicon.calendar.event/3abc123",
"disableDirectRsvp": true,
"rsvpRedirectUrl": "https://ti.to/myorg/my-event"
}'
Example HTTP Request:
POST /xrpc/events.smokesignal.event.configure HTTP/1.1
Host: smokesignal.events
Authorization: Bearer eyJhbGciOi...
Content-Type: application/json
{
"event": "at://did:plc:1234abcd/community.lexicon.calendar.event/3abc123",
"requireConfirmedEmail": true,
"disableDirectRsvp": true,
"rsvpRedirectUrl": "https://ti.to/myorg/my-event"
}
Example HTTP Response:
HTTP/1.1 200 OK
Content-Type: application/json
{}
Error Responses:
| Status | Error | Description |
|---|---|---|
| 401 | AuthRequired |
Missing, invalid, or expired authentication token, or no issuer in token. |
| 400 | InvalidRequest |
Event AT-URI doesn't belong to the authenticated user, invalid AT-URI format, or no settings provided. |
| 400 | InvalidRedirectUrl |
Redirect URL is not valid or does not use HTTPS. |
| 404 | EventNotFound |
The specified event does not exist. |
| 500 | InternalServerError |
Failed to update event settings. |
Authentication#
Authenticated endpoints require an AT Protocol JWT passed as a Bearer token in the Authorization header:
Authorization: Bearer <at-proto-jwt>
The JWT must:
- Be a valid AT Protocol access token
- Contain an
iss(issuer) claim with the caller's DID - Not be expired
Tokens are validated by the server using AT Protocol's JOSE/JWT verification with P-256 ECDSA keys.
Error Responses#
All error responses follow a consistent JSON structure:
{
"error": "ErrorCode",
"message": "Human-readable description of the error"
}
Some endpoints use error_description instead of message:
{
"error": "error_code",
"error_description": "Human-readable description of the error"
}
Common HTTP Status Codes#
| Status | Meaning |
|---|---|
| 200 | Success |
| 400 | Bad request (invalid parameters or input) |
| 401 | Authentication required or token invalid |
| 403 | Forbidden (insufficient permissions) |
| 404 | Resource not found |
| 500 | Internal server error |
| 503 | Service unavailable (e.g., search not configured) |