interplanetary impact index

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 Agent created from OAuth session

Theories for Why It's Not Working#

Theory 1: Scope Format Issue#

  • OAuth scope in token response is undefined even 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.chat service 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#

  1. Check ATProto SDK documentation for official chat API examples
  2. Test with a different target DID (maybe chat with yourself first)
  3. Look for working chat API examples in ATProto community/GitHub
  4. 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.