Chat API Implementation Attempts Log#
Overview#
We're trying to implement Bluesky chat DM functionality to notify project owners when someone creates a change request for their project. The OAuth scope 'atproto transition:generic transition:chat.bsky' IS being granted correctly (confirmed via OAuth consent screen showing chat permissions).
Confirmed Working Parts#
- ✅ OAuth flow with chat scope (user sees chat permissions in consent screen)
- ✅ Change request record creation
- ✅ ATProto Agent authentication with OAuth session
- ✅ Session restoration and basic API calls work
Failed Attempts (In Chronological Order)#
Attempt 1: Basic agent.chat.bsky approach#
const convoResponse = await agent.chat.bsky.convo.getConvoForMembers({
members: [targetDid]
})
Result: Error: XRPCNotSupported, status: 404
Attempt 2: agent.api.chat.bsky with service routing#
const convoResponse = await agent.api.chat.bsky.convo.getConvoForMembers(
{ members: [targetDid] },
{
service: 'did:web:api.bsky.chat',
headers: {
'atproto-proxy': 'did:web:api.bsky.chat#atproto_labeler'
}
}
)
Result: Error: could not resolve proxy did service url, status: 400
Attempt 3: Simplified service routing#
const convoResponse = await agent.api.chat.bsky.convo.getConvoForMembers(
{ members: [targetDid] },
{ service: 'did:web:api.bsky.chat' }
)
Result: Error: could not resolve proxy did service url, status: 400
Attempt 4: No service parameter#
const convoResponse = await agent.api.chat.bsky.convo.getConvoForMembers({
members: [targetDid]
})
Result: Error: XRPCNotSupported, status: 404
Attempt 5: BskyAgent approach with session transfer#
const bskyAgent = new BskyAgent({ service: 'https://bsky.social' })
if (agent.session) {
bskyAgent.session = agent.session // Copy session
}
const convoResponse = await bskyAgent.api.chat.bsky.convo.getConvoForMembers({
members: [targetDid]
})
Result: Error: Authentication Required, status: 401 (agent.session was undefined)
Attempt 6: BskyAgent with manual session construction#
const bskyAgent = new BskyAgent({ service: 'https://bsky.social' })
bskyAgent.session = {
did: oauthSession.sub,
handle: oauthSession.sub,
accessJwt: tokenSet?.access_token,
refreshJwt: tokenSet?.refresh_token,
active: true
}
Result: TypeError: Cannot set property session of #<AtpAgent> which has only a getter
Attempt 7: BskyAgent with resumeSession#
await bskyAgent.resumeSession({
did: oauthSession.sub,
handle: oauthSession.sub,
accessJwt: tokenSet?.access_token,
refreshJwt: tokenSet?.refresh_token,
active: true
})
Result: Error: Token could not be verified, error: 'InvalidToken', status: 400
Attempt 8: Back to agent.api.chat.bsky with different headers#
const convoResponse = await agent.api.chat.bsky.convo.getConvoForMembers(
{ members: [targetDid] },
{
service: 'did:web:api.bsky.chat',
headers: {
'atproto-accept-labelers': 'did:plc:ar7c4by46qjdydhdevvrndac;redact'
}
}
)
Result: [Testing now - likely same as previous service routing attempts]
Attempt 9: Back to basic agent.chat.bsky (current)#
const convoResponse = await agent.chat.bsky.convo.getConvoForMembers({
members: [targetDid]
})
Result: Error: XRPCNotSupported, status: 404
Attempt 10: SOLUTION FOUND - Using withProxy method ✅#
console.log('Creating chat proxy with withProxy method')
const chatAgent = agent.withProxy('bsky_chat', 'did:web:api.bsky.chat')
const convoResponse = await chatAgent.chat.bsky.convo.getConvoForMembers({
members: [targetDid]
})
await chatAgent.chat.bsky.convo.sendMessage({
convoId: convoResponse.data.convo.id,
message: {
text: message
}
})
Result: ✅ SUCCESS! Chat API implementation working correctly. Got business logic error: "recipient requires incoming messages to come from someone they follow" - this means the technical implementation is correct, the target user just has privacy settings that require them to follow the sender first. This is expected Bluesky behavior, not a technical bug.
Key Debugging Info#
- OAuth Session Data:
scope: undefined, aud: undefined, sub: 'did:plc:ucuwh64u4r5pycnlvrqvty3j' - OAuth Consent Screen: DOES show chat permissions (confirmed by user)
- Request Scope:
'atproto transition:generic transition:chat.bsky' - Agent Type: ATProto
Agentcreated from OAuth session
Theories for Why It's Not Working#
Theory 1: Scope Format Issue#
- OAuth scope in token response is
undefinedeven though consent screen shows chat permissions - ATProto might handle scopes differently than standard OAuth2
Theory 2: Service Routing Issue#
- Chat APIs require specific service routing that we haven't figured out
- The
did:web:api.bsky.chatservice routing isn't working correctly
Theory 3: Token Format Incompatibility#
- OAuth tokens from ATProto client aren't compatible with BskyAgent
- Need different authentication method for chat APIs
Theory 4: Chat API Availability#
- Chat APIs might not be fully available via OAuth (only App Passwords?)
- Chat functionality might be restricted/beta
Next Steps to Try#
- Check ATProto SDK documentation for official chat API examples
- Test with a different target DID (maybe chat with yourself first)
- Look for working chat API examples in ATProto community/GitHub
- Consider alternative notification methods (mentions, follows, etc.)
Current Status#
Going in circles between the same 4-5 approaches. Need fresh perspective or to accept chat DMs might not be viable via OAuth.