Compare commits

..

383 Commits

Author SHA1 Message Date
lamp 0770688814 Merge tag '@atproto/pds@0.4.221' into modify-pds
@atproto/pds@0.4.221
2026-05-06 03:09:39 -04:00
github-actions[bot] 5af5deff55 Version packages (#4881)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-29 15:23:54 +01:00
Matthieu Sieben 087515e6a4 Improve oauth client sign-in options (#4896) 2026-04-29 15:19:25 +01:00
devin ivy 877e6293b9 pds: use entryway mock server for tests (#4757)
* pds: setup entryway mock server for tests, remove stale dep.

* changeset

* tidy test

* pds: tidy mock entryway
2026-04-24 16:35:18 -04:00
Matthieu Sieben 5d3e248c26 Fix /.well-known/change-password page (#4880)
* Fix /account/reset-password route

* changeset

* tidy

* improve error reporting

* messages

* name all providers
2026-04-23 17:58:22 +02:00
Emelia Smith 6467208918 Add documentation for requestLock throwing errors (#4879) 2026-04-22 17:55:30 +02:00
github-actions[bot] 17c394d9b1 Version packages (#4878)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-22 10:19:29 -03:00
rafael 03445eef45 update group chat lexicons (#4876) 2026-04-22 10:14:29 -03:00
github-actions[bot] 98d1d019c1 Version packages (#4867)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-21 11:43:55 -03:00
rafael 3cb156907a update chat lexicons (#4875) 2026-04-21 11:39:02 -03:00
Matthieu Sieben 84eb5ed95d Fix reset password flow from account page (#4873) 2026-04-20 17:56:58 +02:00
rafael dacb0e8005 group chat declaration hydration (#4868)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 10:43:20 -03:00
DS Boyce 750cfe9020 Codegen to add new descriptions (#4866)
* Codegen to add new descriptions

* Add changeset
2026-04-16 14:14:31 -07:00
github-actions[bot] e1d5e63314 Version packages (#4864)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-16 02:10:13 +01:00
David Buchanan c531144d24 OAuth Hooks Enhancements (#4857)
* onSignInFailed oauth hook

* plumb Account through to onSignInFailed hook

* plumb client ids through to oauth hooks

* pass sub to InvalidCredentialsError, not full Account

* defensively downgrade InvalidCredentialsError to InvalidRequestError after delivering onSignInFailed hook

* changeset

* support InvalidCredentialsError in PDS oauth store, test for enumeration attacks

* changeset

* fix test

* slight simplification
2026-04-16 02:01:12 +01:00
github-actions[bot] f93115f2e7 Version packages (#4860)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-15 15:47:06 -03:00
rafael 1d575edac9 group chat lexicons (#4854) 2026-04-15 15:35:18 -03:00
github-actions[bot] ff9f84e11a Version packages (#4851)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-15 10:06:49 +02:00
Matthieu Sieben 0cfb16b2bf Disable COEP headers when hCaptcha is enabled (#4855) 2026-04-15 08:58:39 +02:00
Matthieu Sieben 26d793af95 Improve AtUriString type an utilities (#4806)
* Improve `AtUriString` type an utilities

* review comments

* tidy

* tidy

* tidy

* tidy

* Apply suggestion from @matthieusieben

* Apply suggestion from @matthieusieben

* review comments
2026-04-14 18:57:10 +02:00
Matthieu Sieben 55d06de80a Fix normalizeDatetime inconsistencies (#4760)
* Fix `normalizeDatetime` inconsistencies

* tdy

* tidy

* tests

* tidy

* tidy

* comment
2026-04-14 18:56:26 +02:00
Matthieu Sieben 61e75af39e Improve error reporting (#4816)
* Improve error reporting from fetchHandler() in NodeJS

* Use request logger instead of global one when performing service proxying
2026-04-14 17:05:13 +02:00
Matthieu Sieben 952354c1dd Improve performances of lexParseJsonBytes function (#4836)
* Improve performances of `lexParseJsonBytes` function

* tidy

* tidy

* Add test for repeated keys; fix import order in bench file

Agent-Logs-Url: https://github.com/bluesky-social/atproto/sessions/3cb30e99-4d11-4396-82a3-79c2eb5ca6cf

Co-authored-by: matthieusieben <813661+matthieusieben@users.noreply.github.com>

* Fix `lexParseJsonBytes` strict mode to match `lexParse` behavior (#4843)

* Initial plan

* Fix lexParseJsonBytes strict mode to match lexParse behavior

- Fix parseNull/parseTrue/parseFalse to throw SyntaxError with 'Unexpected token' (matching JSON.parse)
- Fix parseNumber to compute full value before validating, throw TypeError('Invalid non-integer number: ${value}') in strict mode (matching lexParse)
- Fix parseObject to throw TypeError (not SyntaxError) for invalid $bytes, $link, and blob objects
- Use parseTypedBlobRef instead of isBlobRef for blob validation
- Add $type empty string check and improve $type error messages
- Update incorrect test: 1e10 is a safe integer and should not throw in strict mode
- Add comprehensive strict vs non-strict mode parity tests for lexParseJsonBytes vs lexParse

Agent-Logs-Url: https://github.com/bluesky-social/atproto/sessions/c72e3862-277f-4f61-b4eb-b5a334e42647

Co-authored-by: matthieusieben <813661+matthieusieben@users.noreply.github.com>

* Improve code comment in parseNumber

Agent-Logs-Url: https://github.com/bluesky-social/atproto/sessions/c72e3862-277f-4f61-b4eb-b5a334e42647

Co-authored-by: matthieusieben <813661+matthieusieben@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: matthieusieben <813661+matthieusieben@users.noreply.github.com>

* tidy

* tidy

* tests

* tidy

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: matthieusieben <813661+matthieusieben@users.noreply.github.com>
2026-04-14 17:04:14 +02:00
Matthieu Sieben b3ce11ae2e OAuth provider UI unification (#4820)
* refactor

* tidy

* tidy
2026-04-14 16:46:18 +02:00
Jim Calabro b704c52327 Data Plane Caller Interceptor (#4852)
* Data Plane Caller Interceptor

* Changeset

* add a test

---------

Co-authored-by: Eric Bailey <git@esb.lol>
2026-04-13 17:58:38 -04:00
rafael 7c61f196c3 improve ctx typing (#4849) 2026-04-13 11:09:05 -03:00
github-actions[bot] 2a9221d244 Version packages (#4842)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-09 15:21:17 -05:00
Eric Bailey 55c39860c8 Make sure to include did and isMe checks on decideStatus default decision (#4841) 2026-04-09 15:15:57 -05:00
github-actions[bot] 83b7246439 Version packages (#4833)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-09 10:24:41 -05:00
Eric Bailey d8801e2a17 Increase max image upload size to 2mb from 1mb (#4823)
* Increase max image upload size to 2mb from 1mb

* SI

* Apply suggestion from @surfdude29

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>

---------

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>
2026-04-08 16:47:40 -05:00
Eric Bailey aa1763df0f [APP-1775] Hydrate labels for actor statuses (#4555)
* Add labels to statusView, hydrate and return them

* Add test

* Add moderateStatus API SDK method

* Changeset

* Format
2026-04-08 16:06:27 -05:00
Matthieu Sieben b6b231f9c0 Fix typing of delete options to omit body instead of params. (#4839) 2026-04-08 16:22:21 +02:00
Matthieu Sieben f6f100c337 Avoid alteration of record data (#4835) 2026-04-08 16:11:08 +02:00
devin ivy 7000746941 bsky: do not apply blocks viewed by trusted mod service (#4837)
* bsky: skip blocks against mod viewer

* bsky: test mod service blocks fix

* changeset
2026-04-07 18:03:52 -04:00
Matthieu Sieben c62651dd69 Fix support for legacy blobs in lex SDK (#4828)
* Allow validation of legacy blobs (in 'validate' mode)

* tidy

* tidy

* tidy

* changeset

* tidy

* review comments

* update skills

* tidy

* Add `TypedBlobRef`

* fix style

* review comments

* lint
2026-04-07 20:11:46 +02:00
Matthieu Sieben 284e6fbca9 Revert #4824 (#4834)
Revert "Fix legacy profile validation by using safeParse instead of ifMatches validation (#4824)"

This reverts commit 9def38a5f6.
2026-04-07 12:01:29 +02:00
Eric Bailey 9def38a5f6 Fix legacy profile validation by using safeParse instead of ifMatches validation (#4824) 2026-04-07 10:24:51 +02:00
github-actions[bot] 158daf65fc Version packages (#4831)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-06 19:27:01 -05:00
Eric Bailey 42232c7f36 Register new suggested user endpoints with the router (#4830) 2026-04-06 18:56:30 -05:00
DS Boyce 3b9af1bdb6 Workaround missing dependency when installing npm@latest (#4821) 2026-04-02 14:48:14 -07:00
github-actions[bot] b6a9625e36 Version packages (#4811)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-02 13:24:00 -07:00
Matthieu Sieben 3711454321 Rewrite of aturi tests (#4815) 2026-03-28 17:13:57 -07:00
DS Boyce 2a5e2c267f Create new endpoints for suggested users (#4809)
* Create new endpoint for suggested users for Explore

* Update comment

* Add remaining endpoints

* Remove categories from discover endpoint

* Update descriptions
2026-03-27 10:46:10 -07:00
DS Boyce 34f9ae1f68 Show suggestions to logged out users (#4810)
* Show suggestions to logged out users

* Add changeset
2026-03-26 16:13:06 -07:00
github-actions[bot] 2203d825e8 Version packages (#4776)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-03-26 09:17:15 -03:00
Matthieu Sieben 4f11fc341a Add support for indexed query params syntax (#4804)
* Add support for indexed query params syntax

* Review comments

* avoid altering lexicons

* remove `getProfile`

* optimize regexp

* fix regexp
2026-03-25 21:07:03 +01:00
Matthieu Sieben c224e1fae6 add missing paramsParseLoose (#4803) 2026-03-24 15:56:18 +01:00
Matthieu Sieben 6116d56d36 Allow [] query params syntax with app.bsky.feed.getPostThread end… (#4802)
Allow `[]` query params syntax with `app.bsky.feed.getPostThread` endpoint
2026-03-24 15:49:37 +01:00
Matthieu Sieben ac6bd18f1d Disable strict mode parsing of records (#4796) 2026-03-24 13:38:11 +01:00
Matthieu Sieben c5c6c7dac3 Allow any encoding to match payload with no encoding specified in schema (#4799)
* Allow any encoding to match paylaod that don't expect any particular encoding

* Explicitly cancel request body when it does not get consumed

* tidy
2026-03-24 13:36:57 +01:00
Matthieu Sieben fac213dfdc Fix error stating that the lex build folder could not be cleared (#4800) 2026-03-24 13:30:48 +01:00
Matthieu Sieben 1479c87c6b Allow use of foo[]=bar syntax in query params (#4793)
* Allow use of `foo[]=bar` syntax in query params

* tidy

* fix delete while iterating

* xrpc-server: add paramsParseLoose route option for temp backwards compat

* bsky: add loose param parsing to app.bsky.feed.getPosts temporarily

* changeset update

* Add endpoint name in "Invalid params" error message

* Parse `[]` in `getProfile`

* tidy

---------

Co-authored-by: Devin Ivy <devinivy@gmail.com>
2026-03-24 13:01:49 +01:00
Matthieu Sieben 960a02b8d5 Skip read-after-write for invalid records (#4798) 2026-03-24 13:00:35 +01:00
Matthieu Sieben 36e3019711 Disable AppView response validation (#4797) 2026-03-24 11:50:30 +01:00
Matthieu Sieben a99dd58b5f Allow payload from XRPC endpoints with no output in their schema (#4788)
* Allow XRPC endpoints with no `output` specified in their schema to output anything.

* tidy

* fix tests
2026-03-24 11:33:39 +01:00
Matthieu Sieben 0dbea15da4 Make AT-uri validation faster (#4792) 2026-03-24 11:33:06 +01:00
Eric Bailey 3aa18f84ef Add some missing codegen files (#4791) 2026-03-23 13:09:50 -05:00
Eric Bailey ae22362c59 Add A/A test to thread endpoint (#4790)
* Add A/A test to thread endpoint

* Changeset

* Format
2026-03-23 12:41:21 -05:00
Eric Bailey 37b2f492bb Ensure scoped checkGates merges outer user context (#4780) 2026-03-23 12:10:21 -05:00
Matthieu Sieben d0c136cba2 Move PDS code over to @atproto/lex (#4408) 2026-03-23 18:10:16 +01:00
Matthieu Sieben c4df84cd78 Optimize counting of graphemes when validating strings (#4787) 2026-03-23 18:06:58 +01:00
Matthieu Sieben e5e5bcf85f Show invalid values in issue description (#4784)
* Show invalid values in issue description (when stringifiable in reasonably small string)

* tidy

* tidy
2026-03-23 13:52:09 +01:00
Matthieu Sieben 527f5d4c5d Various lex SDK improvements (#4779)
* Various lex SDK improvements

* tidy

* Improve performances of error instantiation

* Improve clarity of params validation errors (and add "cause" for improved debugging)

* update error handling

* tidy

* tidy

* review comments

* tidy
2026-03-23 11:47:23 +01:00
bnewbold 57ca2109f3 lexicons: add 'bot' wellKnown label value (#4777)
* lexicons: add 'bot' wellKnown label value

* lexicons: update other global labels
2026-03-19 15:42:27 -07:00
DS Boyce b86dbfcbe3 Remove feature gate for new user onboarding (#4775)
* Remove feature gate for new user onboarding

* Add changeset

* Remove feature gate from enum

* Remove unused type

* Attempt to fix tests
2026-03-19 13:55:01 -07:00
Eric Bailey db795be786 Feature Gates Refactor (#4756)
* Refactor feature gates client to be less handler-specific

* Improve passing around of feature gates

* Ensure merging of contexts results in a correct output

* Sync with main

* Docs

* Changeset

* Remove image gate

* Comments

* Format

* Update new usage
2026-03-19 13:58:07 -05:00
github-actions[bot] 8445e8a9aa Version packages (#4770)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-03-19 15:53:07 +01:00
Matthieu Sieben 737c046e95 Fix infinitely deep declaration (#4774) 2026-03-19 15:45:38 +01:00
Matthieu Sieben df8328c3c2 Fix xrpc options (#4773)
* Fix xrpc options

* tidy

* changeset

* tidy

* tidy

* fix
2026-03-19 15:25:45 +01:00
Matthieu Sieben b2baf8d660 Fix Client.call options for XRPC queries (#4772) 2026-03-19 14:10:34 +01:00
Matthieu Sieben 6a88461c5a Allow processing of invalid lex data (#4761)
* Allow processing of responses with invalid lex data

* Apply suggestions from code review

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* add tests

* tidy

* Add non-strict validation mode to lex SDK (#4766)

* non-strict validation mode

* apply strict option to client responses

* update readme

* Apply suggestions from code review

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Allow `-1` BlobRef size in non-strict mode

* review comments

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* tidy

* tidy

* tidy

* tidy

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-19 13:50:11 +01:00
Eric Bailey 3b41b81e27 Remove deprecated handling from getSuggestedFollowsByActor (#4747)
* Remove deprecated handling from getSuggestedFollowsByActor

* Remove very old test that is not testing existing functionality

* Import sort

* Softer off-ramp if suggestionsAgent
2026-03-18 18:36:20 -05:00
Eric Bailey 4ecde4879f Add feature flag to enable social proof on getSuggestedFollowsByActor (#4767)
* Add feature flag to enable social proof on the getSuggestedFollowsByActor endpoint

* Changeset
2026-03-18 18:14:31 -05:00
github-actions[bot] 13b2aad9b6 Version packages (#4720)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-03-18 16:47:57 -05:00
Eric Bailey efb2b58fb2 [APP-1926] Add bsync delete operations support (#4745)
* Add bsync handlers for deletion request

* Add test

* Changeset

* Abtract util, add comments
2026-03-18 15:39:36 -05:00
Matthieu Sieben cbbc03b6e4 Revert "non-strict validation mode"
This reverts commit 3d60e1af7c.
2026-03-18 15:13:36 +01:00
Matthieu Sieben 3d60e1af7c non-strict validation mode 2026-03-18 15:12:48 +01:00
rafael c89225f972 create chat declaration for dev-env users (#4763) 2026-03-17 18:24:57 -03:00
rafael bc69b03f53 improve checking for zero dates and test (#4762) 2026-03-17 14:23:51 -03:00
rafael ff42a3afc3 Avoid Go zero dates (year 0001) in bsky views (#4753)
* try to avoid returning zero dates (year 0001) in bsky views

* address Matthieu's review

* Move `parseDate` uses from view to hydration
2026-03-17 12:15:21 -03:00
bnewbold 266b5631d2 add 'app.bsky.actor.getPreferences' to authViewAll (#4749)
The motivation for this is to for read-only apps to display lists and
feeds which have been pinned/followed by the user. Presumably the prefs
could also be used by read-only apps to apply labeling configuration.
2026-03-16 22:17:34 -07:00
devin ivy 139b2941d6 bsky: remove feature gate for including format in image URLs (#4755)
* bsky: remove feature gate on urls containing image format

* changeset

* bsky: fix test

* tidy
2026-03-16 16:46:20 -04:00
Matthieu Sieben 67eb0c19ac Improve efficiency of AtUri did getter and typing of hostname property 2026-03-16 11:06:55 +01:00
Matthieu Sieben 112b159ec2 Export more string format type assertion utilities from @atproto/lex 2026-03-16 08:45:33 +01:00
Eric Bailey eaee3d4305 Serialize pagerank (#4746)
* Serialize pagerank

* Changeset
2026-03-13 10:46:43 -05:00
Foysal Ahamed 9f9f71a6a3 Add purge event for age assurance data for compliance (#4709)
*  Add purge event for age assurance data for compliance

* 🧹 Cleanup

* 📝 Add changeset

* :rotating_lights: Fix lint issue
2026-03-12 16:02:38 +01:00
Matthieu Sieben 5f73f8ed72 Improve reading of car files (#4740)
* await only if needed

* tidy

* changeset
2026-03-11 14:18:08 +01:00
Matthieu Sieben 192685fca7 Improve reading of car files (#4729)
* Improve reading of car files

* tidy

* fix

* tidy

* tidy

* tidy

* tidy

* code simplification

* tidy
2026-03-11 13:41:13 +01:00
Matthieu Sieben 3dc3791543 Add Standard Schema compatibility (#4734)
* Add [Standard Schema](https://standardschema.dev/) compatibility

* tidy

* tidy

* rename `StandardSchemaValidator` to `StandardSchemaAdapter`

* `LexValidationError` class now implements `ResultFailure`

* review comments

* add readme

* fix tests

* Add tests for StandardSchemaAdapter (`schema['~standard']`) (#4735)

* Initial plan

* Add StandardSchemaAdapter tests

Co-authored-by: matthieusieben <813661+matthieusieben@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: matthieusieben <813661+matthieusieben@users.noreply.github.com>

* changeset

* return `message` in `toJSON`

* review comments

* tidy

---------

Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: matthieusieben <813661+matthieusieben@users.noreply.github.com>
2026-03-10 16:39:39 +01:00
rafael c9004b4d98 disable tracking image rollout (#4726) 2026-03-05 20:38:14 -03:00
devin ivy 7ed57043c1 bsky: switch default image format to jpeg temporarily (#4723)
Co-authored-by: rafaeleyng <rafaeleyng@gmail.com>
2026-03-05 15:29:42 -03:00
Laurence Gonsalves b3f80b457c Fix typo in chat.bsky.authFullChatClient lexicon (#4719)
Change "chat.bsky.convo.exportAccountData" to
"chat.bsky.actor.exportAccountData" in an lxm of
chat.bsky.authFullChatClient#main's permission-set, as the former does
not exist, but seems to be a typo of the latter.
2026-03-05 09:37:05 -08:00
rafael 7444295e79 use feature gate to remove image format from URL (#4721)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 14:05:21 -03:00
Matthieu Sieben 5a2f8847ef Improve lex-client error reporting (#4714)
* Improve lex-client error reporting

* tidy

* Enhance XrpcFetchError handling and improve error messages

* fix test

* add tests

* improve test coverage

* fix lint
2026-03-05 12:14:44 +01:00
devin ivy 383e157021 bsky: remove format from img urls by default (#4712)
* bsky: remove format from img urls, w/ config to control rollout. default webp.

* tidy

* changeset

* tidy
2026-03-04 22:41:44 -05:00
github-actions[bot] 39ea120dae Version packages (#4718)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-03-04 17:54:55 -06:00
Eric Bailey 76ab6eaa7b Remove account status fallback to stale value (#4717) 2026-03-04 17:50:56 -06:00
github-actions[bot] 82e708095e Version packages (#4715)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-03-04 15:32:00 -06:00
Matthieu Sieben 0e5df95e3a Make sure to always trigger refreshSession when resuming sessions (#4713)
Make sure to always trigger `refreshSession` when `resumeSession()` is called
2026-03-04 15:13:50 -06:00
DS Boyce 66341400d4 Deprecate recId in favor of recIdStr (#4683)
* Deprecate recId in favor of recIdStr

* Add changeset

* Revert "Add changeset"

This reverts commit 3241aec7202c1bcb2a8e28f7064def5024dcae73.

* Recreate changeset as patch version
2026-03-04 12:37:28 -08:00
github-actions[bot] 88326d2833 Version packages (#4703)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-03-03 15:45:46 +01:00
Matthieu Sieben f7c26103a6 Increase type strictness in @atproto/syntax (#4689)
* Increase type strictness in `@atproto/syntax`

* review comments

* wip

* make `rkey` and `collection` AtUri getters actually safe

* tidy

* tidy

* tidy

* changeset

* tidy

* tidy

* fix ci
2026-03-03 15:10:29 +01:00
Matthieu Sieben 1c473ab555 Update @atprto/lexicon-resolver to use Lex SDK (#4697)
Use Lex SDK as codegen for `@atprto/lexicon-resolver`
2026-03-03 13:57:33 +01:00
Matthieu Sieben d5f4224f73 Update PDS's internal pipethough error handling (#4698)
* Update PDS's internal pipethough error handling

* tidy
2026-03-03 13:57:15 +01:00
Matthieu Sieben 138f0a0b37 Fix @atproto/sync test flakyness (#4705) 2026-03-03 13:57:02 +01:00
Matthieu Sieben e8969b6b3d Add Lex SDK support to @atproto/xrpc-server (#4691)
* Lex SDK error handling improvements

* Add support for method defined through `@atproto/lex` in addition to `@atproto/lexicon` "codegen"

* review comments

* tidy
2026-03-03 11:28:09 +01:00
DS Boyce 137065b333 Add feed to sendInteractions input (#4704) 2026-03-02 11:27:09 -08:00
Matthieu Sieben 52834aba18 Lex SDK error handling improvements (#4688)
* Lex SDK error handling improvements

* Allow `WWWAuthenticate` to have multiple challenges for the same scheme

* tidy

* add tests

* tests

* review comments

* tidy

* tidy

* tidy

* tidy
2026-03-02 15:37:00 +01:00
rafaeleyng 1b83d841cf Fix README typo 2026-02-23 10:31:09 -03:00
rafael d69c81e335 Fix invalid handle showing verification (#4634) 2026-02-23 10:22:14 -03:00
github-actions[bot] cb5f9bfc0c Version packages (#4674)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-23 09:37:01 +01:00
Lotharie 450f085663 docs: Updating app password based session example (#4631)
* docs: Updating app password based session example

Updating atproto app password based session example in README

* refactor: prettier lint + changeset
2026-02-23 09:30:05 +01:00
Matthieu Sieben 38852f0ddf QoL improvements for lex SDK (#4672)
* Allow calling `xrpc` and `xrpcSafe` functions with a service URL

* tidy

* tidy

* fix test

* Improve app password based session management

* lex lex jsdoc

* Add easy login for app password based bots

* Shorten readme intro
2026-02-20 20:26:43 +01:00
Matthieu Sieben c4b04899c8 Attempt at fixing flaky BSKY test (#4673)
Attempt at fixin CI
2026-02-20 20:26:27 +01:00
DS Boyce 1521b2ebba Use workspace dictionary in vscode settings (#4650) 2026-02-20 10:35:06 -08:00
github-actions[bot] 8603be629a Version packages (#4668)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-20 10:50:49 +01:00
Matthieu Sieben 7229c03a2a Add changeset check workflow (#4670)
* Add changeset check workflow

* fix node version

* Fake missing changeset

* tidy

* fix test
2026-02-20 10:47:49 +01:00
Matthieu Sieben dc9644bbeb Fix lint warning in @atproto/identity (#4669) 2026-02-20 10:35:45 +01:00
Matthieu Sieben 66b72950e8 Fix lex-cbor warnin: import with (#4667)
Use import `with` instead of deprecated import `assert` (fixing build warning)
2026-02-20 09:30:43 +01:00
github-actions[bot] caad88223f Version packages (#4653)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-20 09:05:35 +01:00
Eric Bailey ce87b88e33 Ensure service entrypoints are copied into docker containers (#4666) 2026-02-19 17:33:31 -06:00
Eric Bailey 7cf7d9062d [APP-1796] Add identifier fallbacks (#4664)
* Add identifier fallbacks

* Cleanup

* No await
2026-02-19 16:30:05 -06:00
Matthieu Sieben a23d13268c OAuth client improvements (#4642)
* Delete any pre-existing OAuth session when a new one is created (for a given `sub`)

* OAuth client improvements

* tidy

* tidy

* tdy

* remove event-target-polyfill dep

* Update .changeset/neat-humans-fix.md

Co-authored-by: devin ivy <devinivy@gmail.com>

* tidy

---------

Co-authored-by: devin ivy <devinivy@gmail.com>
2026-02-19 15:21:00 +01:00
Matthieu Sieben ea5df64db9 Various Lex SDK improvements (#4660)
* Various Lex SDK improvements

* changeset

* Improve typeing of `string()` schemas

* fix tests

* improve backwards compatibility
2026-02-19 13:52:24 +01:00
Matthieu Sieben 39dea03c41 Lex SDK fixes and improvements (#4654)
* minor tests fix

* Properly support "enum" and "const" schemas in params

* tidy

* Add support for `knownValues` in string schemas

* tidy

* tests

* tidy

* tidy

* tidy

* fix types

* fix tests

* add suggested tests

* merge conflicts
2026-02-19 13:17:54 +01:00
Matthieu Sieben cd9deb6f21 Fix conflicting identifier for generated type utilities (#4659)
Add a `$` in front of the method type utilities to prevent name conflicts with local definitions
2026-02-19 13:04:40 +01:00
Matthieu Sieben 0e8d4bb62b Skip puppeteer download in build and verify steps (#4652)
* Skip puppeteer download in build and verify steps

* tidy

* tidy

* fix deps

* install puppeteer as pretest step

* cleanup
2026-02-18 17:43:38 +01:00
Matthieu Sieben 009c4afd36 Add test cases for knownValues strings (#4639) 2026-02-18 13:05:59 +01:00
rafael 39cf199df5 move feature gates to analytics folder (#4640)
* move feature gates to analytics folder

* implement scoped feature gate evals

* Add separate config for metrics trackingHost

* Add source param

* Pare down metadata sent to event proxy

* Just build metrics within feature gate client

* Refactor a bit to streamline usage

* Further pare down API to be super clear

* Add readme

* Cleanup

* Use new env vars

* Stop metrics if we stop feature-gates

* Add metrics client test

* Adjust imports for lint

* Try catch for safety, revert logger change

* Structure log metadata to match proxy needs

* Nah use stableId

* Revert "Nah use stableId"

This reverts commit 5c6d39a2420f32efa43955b85d0cf19f20bf2c16.

* Align on deviceId

---------

Co-authored-by: Eric Bailey <git@esb.lol>
2026-02-17 16:59:29 -06:00
github-actions[bot] 2335075a20 Version packages (#4648)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-16 21:53:46 -06:00
Eric Bailey 978a99efad [ALG-58] Use existing getOnboardingSuggestedUsersSkeleton method name (#4647)
* Use existing method name

* Fix import

* Wait no, still wrong

* Changeset
2026-02-16 21:48:32 -06:00
Eric Bailey 7e3eba638f Add action (#4646)
Add claude action
2026-02-16 18:39:53 -06:00
DS Boyce 48aee6c92c Add recIdStr to suggested follows by actor (#4644)
* Add recIdStr to suggested follows by actor

* Add recIdStr to get suggestions skeleton
2026-02-16 13:37:25 -08:00
devin ivy ae220d593b PDS: compare lexicon methods case-insensitively (#4643)
* pds: lxm checks case-insensitive

* tidy
2026-02-16 14:00:50 -05:00
github-actions[bot] 6ae3cf6131 Version packages (#4637)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-13 15:11:24 +01:00
dan 619068fb81 Expose LexDefBuilder and allow passing moduleSpecifier (#4610)
* Expose LexDefBuilder and allow passing moduleSpecifier

* Changeset
2026-02-13 15:07:10 +01:00
Geoff Cramer 95e377504a Create index for fetching mod events by creator (#4630)
* db: create index for moderation_event.createdBy

* tests: add queryEvents by creator test case

* refactor: add additional columns to createdBy index to match runtime query

* refactor: remove id since including both createdAt and id provide little incremental efficiency

* refactor: adding id back since it could matter in some cases
2026-02-11 14:52:46 -05:00
github-actions[bot] 379cae6986 Version packages (#4625)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-10 19:39:18 -03:00
rafael 60f84ebe47 increase draft char limit (#4633) 2026-02-10 19:35:11 -03:00
rafael 50dfbec512 Add separate onboarding suggested users endpoint (#4632) 2026-02-10 19:18:59 -03:00
bnewbold f8c84ebd3d update germ lexicon (#4594)
* update germ lexicon (from published)

* make codegen

* add changeset

* Add `none` to germ declaration showButtonTo field (#4627)

add `none` to germ declaration showButtonTo field

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
2026-02-09 12:57:37 -06:00
Matthieu Sieben b619ae87a8 Avoid using invalid scope in oauth example app (#4586)
* Avoid using invalid scope in oauth example app

* tidy
2026-02-09 15:57:14 +01:00
Matthieu Sieben 8711f6e1b8 Force getBlob to trigger a browser download (#4616) 2026-02-09 15:30:57 +01:00
github-actions[bot] 23a13d7dde Version packages (#4621)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-06 19:09:51 +01:00
Matthieu Sieben fdbbff8543 Fix validation of oauth /authorize params (#4620) 2026-02-06 19:00:34 +01:00
github-actions[bot] 27cdeb4e86 Version packages (#4604)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-06 15:12:15 +01:00
Matthieu Sieben a2e4e95847 Use fixed version in oauth-provider UI packages (#4619)
* Use fixed version in oauth-provider UI packages

* pnpm-lock
2026-02-06 15:02:12 +01:00
Matthieu Sieben ed61c62f31 Add docs in lex SDK packages (#4601)
* Fix `exports` field in package.json

* tidy

* AT Protocol
2026-02-06 14:40:54 +01:00
Emelia Smith 9fdfb8a3ad Prevent verification of handle.invalid (#4608) 2026-02-06 14:21:29 +01:00
Matthieu Sieben 78fee144ff Throw more detailed error upon CSRF login issue (#4606)
* Throw more detailed error upon CSRF login issue

* Add cookie support detection mechanism

* lint

* tidy

* Update packages/oauth/oauth-provider-ui/cookie-error-page.html

Co-authored-by: devin ivy <devinivy@gmail.com>

* review comments

* tidy

---------

Co-authored-by: devin ivy <devinivy@gmail.com>
2026-02-06 14:20:41 +01:00
Matthieu Sieben 00e6dbdcea Fix method.resolveNoCheck is not a function (#4609)
* Fix `method.resolveNoCheck is not a function` error when using speciallt forged did method

* test
2026-02-06 14:03:31 +01:00
Samuel Newman 19ecf5f76a Fix avatars not being circular in OAuth flow (#4607)
* fix oauth avis not being round

* align "Another account" icon

* changeset
2026-02-04 10:06:40 -08:00
Matthieu Sieben 7b9a98a763 Improve type inference of XRPC methods (#4603)
* Improve type inference of XRPC methods

* tidy

* tidy

* tidy

* tidy
2026-02-03 14:29:22 +01:00
github-actions[bot] c1a10e1992 Version packages (#4592)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-30 17:22:08 -03:00
Eric Bailey 4f5c400127 Update platform to deviceName on draft, add maxLength (#4591) 2026-01-30 17:14:56 -03:00
github-actions[bot] 915f9065d3 Version packages (#4584)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-30 14:20:33 -03:00
Eric Bailey 25cea46aaa Add deviceId and platform to draft stash object (#4590)
Co-authored-by: rafaeleyng <rafaeleyng@gmail.com>
2026-01-30 14:13:32 -03:00
Matthieu Sieben 369bb02b9f @atproto/lex-client error typing improvements (#4589) 2026-01-30 14:01:17 +01:00
kindgracekind 3ba21f9a36 Remove duplicate scope from app.bsky.authFullApp (#4588) 2026-01-29 15:38:18 -08:00
Matthieu Sieben 49b38069ed changeset 2026-01-28 16:43:16 +01:00
Matthieu Sieben 8a725a9d69 Change workspace version selector from workspace:* to workspace:^ 2026-01-28 16:42:44 +01:00
github-actions[bot] 143a5f2251 Version packages (#4578)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-28 16:31:59 +01:00
Samuel Newman 2830daeaa6 Add presentation to video embed as a hint to the client about how to display the video (#4581)
* feat: add video presentation hinting

* codegen

* hydrate presentation

* Add changeset
2026-01-27 05:07:13 -08:00
Matthieu Sieben d54d7077eb DID document validation (#4580)
* DID document validation

* relax `import/no-deprecated` lint rule
2026-01-27 08:25:13 +01:00
devin ivy 78e8ec25df lexicon-resolver: don't require at:// handle for lexicon resoltion (#4577) 2026-01-23 15:30:12 -05:00
github-actions[bot] 595dd20323 Version packages (#4561)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-23 13:29:23 -06:00
Eduardo Cuducos e6e43f3ad3 Adds GrowthBook feature flag for suggested users from Discover (#4526)
* Adds GrowthBook feature flag for suggested users from Discover

* Moves GrowthBook URL to envvar

* Reverts search feature

* Some minor fixes

* Adds GrowthBook feature flag for suggested users from Discover

* Moves GrowthBook URL to envvar

* Some minor fixes

* Keep search filtering exploration around but disable until migration

* Properly initialize GrowthBook client

* Cleans up

* Migrate flags

* Tweak context creation and types

* Add refetching and more error handling

* Rename config to be a little more clear

* Remove check validation, not needed

* Yeah ok we can do this

* Changeset

---------

Co-authored-by: Eric Bailey <git@esb.lol>
2026-01-23 12:56:10 -06:00
rafael 5905eb94a6 Drafts implementation (#4549) 2026-01-22 18:23:56 -03:00
Matthieu Sieben ce356cde55 Fix typing mistake in suggestion endpoints (#4576)
* Use requester DID to accurately perform personalized suggestions

* tidy

* revert removal of `viewer` in `getTrendingTopics`

* tidy
2026-01-22 15:26:20 +01:00
bnewbold 2c4eb7fe20 permission-set for deleting public content (#4453)
bsky permission set for deleting public content
2026-01-21 09:40:34 -08:00
Matthieu Sieben ece2ee8533 changeset 2026-01-21 17:36:36 +01:00
Matthieu Sieben ecf59214d5 Update performances of cbor encoding/decoding (#4572)
* Update performances of cbog encoding/decoding

* pnpm-lock

* tidy

* ci
2026-01-21 17:26:24 +01:00
Matthieu Sieben 99963d002a lex SDK improvements (#4571)
* Improve code coverage of `@atproto/lex-data` tests

* Improve typing of `@atproto/syntax` type assertion utilities

* Improve performance of `@atproto/lex-schema ` string format checking

* Remove `assertX` string format assertion utilities

* tidy

* tidy

* Rename `isLanguageString` to `validateLanguage`

* add string format utils

* tidy

* Refactor uri validation to use `@atproto/syntax`

* More language validation to @atproto/syntax
2026-01-21 16:15:47 +01:00
bnewbold 9bdd35881a Experiment: wire-through germ chat declaration in bsky appview backend (#4415)
* lexicons: add germ chat declaration record, and ref from profile view

* codegen germ record for bsky pkg

* make lex-client build happy by including germ ref

* add germ chat declaration methods to dataplane .proto

* dataplane codegen

* start wiring up germ chat records

* germ lex in api package

* api codegen

* initial attempt at germ record test

* update germ lexicon

* make codegen

* updates for germ declaration NSID rename

* implement new germ hydration

* more progress on germ integration

* fix germ profile test

The main issue here was forgetting the '.associated.' bit in the data
path.

* remove unnecessary germ imports/refs

* hydrate germ metadata in basic profile view (as per schema)

* type fixes for germ appview test

* prettier

* synchronize bsky.proto with dataplane; run buf:gen

* add changeset for Germ declaration records
2026-01-20 17:15:58 -08:00
bnewbold aa22728888 rename lexicon file to match NSID (authManageFeedDeclaration) (#4568)
rename lexicon file to match NSID
2026-01-20 14:58:19 +01:00
Eduardo Cuducos cbd5837f01 Re-adds recId to suggested users (as string) (#4565) 2026-01-20 08:55:35 -05:00
Matthieu Sieben aaedafc6ba Replace tap's event validation from "zod" to "@atproto/lex" (#4532)
* Replace `tap`'s event validation from "zod" to "@atproto/lex"

* Expose `record` data as parsed atproto data (including CIDs and Uint8Arrays)

* Minor change to validation of integers in lex data

* tidy

* tidy

* test using vitest

* add missing `tap` from root `tsconfig.json`
2026-01-20 14:11:24 +01:00
Matthieu Sieben b3d1819f49 tidy 2026-01-20 13:21:47 +01:00
Matthieu Sieben 7310b9704d Fix behavior of "default" and assignment to LexValue (#4562)
* Fix inability to assign (object containing) open union results to `LexMap` type

* properly support and type default values

* strong cohesion

* changeset

* tidy

* Add validation error as cause when handling invalid records

* tidy

* Update `cborg` dependency, fixing encoding of strings with invalid surrogate pairs, and ignoring `undefined` object properties

* tidy

* Simplify encoding logic of `number`

* Improvements around usage of `noUndefinedVals`

* Memoize array schemas (without options)

* Memoize empty params schemas

* tidy

* tidy
2026-01-20 13:20:56 +01:00
Matthieu Sieben fa4ef5e815 Fix oauth response when using prompt=select_account with no sessions available (#4569)
* Fix oauth response when using `prompt=select_account` and no session are available

Fixes #4566

* allow the account to be pre-selected when `prompt=login`
2026-01-20 09:32:09 +01:00
wmjae e9dc15bd79 update copyright year to 2026 (#4542) 2026-01-19 17:05:43 -08:00
Eduardo Cuducos 538d39e19d Fixes semver bump (#4563)
* Fixes semver bump

* Update .changeset/frank-bats-hope.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-19 14:26:54 -05:00
David Buchanan 3fbec803ed Changeset for #4560 (#4564)
changeset for #4560
2026-01-19 17:16:45 +00:00
David Buchanan 6c7160ff7b PDS: Stop probing image dimensions on blob upload (#4560)
* pds: stop probing image dimensions

* remove width and height from BlobMetadata

* remove image dimension checks from tests

* comment out width and height in Blob schema
2026-01-19 17:02:23 +00:00
Eduardo Cuducos d8e53636c8 Drops recId from user suggestions (#4547) 2026-01-19 10:40:13 -05:00
devin ivy f51289f35b Update pds service entrypoint's node version (#4556)
* update pds service entrypoint to node v20.20

* build

* Revert "build"

This reverts commit 8b580da1d1b614f5fa91a69d09360a2bd19d1b11.
2026-01-16 17:25:01 -05:00
rafael 162aa5f82a use node 20.20 in bsky (#4551) 2026-01-15 12:28:27 -03:00
github-actions[bot] 9f06e9a31d Version packages (#4553)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-15 07:23:32 -08:00
Samuel Newman ccd8964331 Changeset for Drafts Lexicon PR (#4552)
Changeset for previous PR
2026-01-15 07:22:21 -08:00
rafael f7ae44d7f6 Drafts lexicons (#4548) 2026-01-14 18:03:53 -03:00
github-actions[bot] a6cf4b29b6 Version packages (#4545)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-13 21:18:01 -06:00
Eric Bailey f58029ba54 [APP-1767] Add liveEventPreferences to user prefs and API SDK (#4543)
* Add liveEventPreferences to user prefs and API SDK

* Format

* Return updated prefs
2026-01-13 20:59:52 -06:00
rafael 716819fb25 Statsig FF to get user sugg from discover vs topic (#4541) 2026-01-13 09:26:47 -03:00
github-actions[bot] c2615a7eee Version packages (#4530) 2026-01-12 21:13:16 +02:00
Samuel Newman 3ffebd0bf2 Add cashtag detection support to rich text parser (#4539)
Implements detection and facet generation for cashtags (stock tickers) like $AAPL
and $BTC. Cashtags are identified by a dollar sign followed by 1-5 alphanumeric
characters, with the first character being a letter. Detected cashtags are
normalized to uppercase and included as tag facets in the rich text output.

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
2026-01-12 10:59:28 -08:00
Matthieu Sieben 0193467463 Make sure the destroy() promise resolves, even in case of error (#4534) 2026-01-09 17:27:12 +01:00
Matthieu Sieben dfd4bee4ab Improve distinction between external Cid interface and CID implementation (#4528)
* Improve distinction between external `Cid` interface and `CID` implementation

* tests

* tidy

* tidy
2026-01-09 14:05:31 +01:00
github-actions[bot] 08db98a744 Version packages (#4525)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-08 15:23:15 -06:00
Eric Bailey 6752056f46 Add unregisterPush to appview (#4524)
* Add unregisterPush to appview server routes

* Changeset
2026-01-08 14:47:10 -06:00
github-actions[bot] 5a384f136a Version packages (#4521)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-08 12:13:47 -06:00
Eric Bailey d2ed7311a2 [APP-1758] Improve status takedowns (#4520)
* Refactor includeTakedowns overrides, add one for statuses specifically

* Add view carve-out

* Add isDisabled to statusView

* Handle filtering of takendown statuses

* Changeset

* Only add isDisabled for owner

* Revert unneeded changes

* Update tests

* Format
2026-01-08 11:55:55 -06:00
github-actions[bot] 0b03086d35 Version packages (#4518)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-08 18:12:54 +01:00
Matthieu Sieben d78484f94d @atproto/lex improvements (bis) (#4512)
* `@atproto/lex` improvements (bis)

* tidy

* tidy
2026-01-08 15:31:49 +01:00
Matthieu Sieben 5b19d390b3 Rename lex-password-agent to lex-password-session (#4519)
* Rename `lex-password-agent` to `lex-password-session`

* tidy

* tidy

* tidy

* improve 2fa

* tests

* tidy

* tidy

* tidy

* tidy

* tidy
2026-01-08 15:02:44 +01:00
mary d40a66dfe1 fix: properly convert did:web to service endpoint (#4502)
* fix: properly convert did:web to service endpoint

* add changeset
2026-01-08 11:57:01 +01:00
mary b329266853 fix: properly convert did:web to service endpoint (#4502)
* fix: properly convert did:web to service endpoint

* add changeset
2026-01-08 11:56:50 +01:00
github-actions[bot] 1a87be24d6 Version packages (#4517)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-07 20:42:38 -06:00
Eric Bailey 7750b91500 [APP-1750] Add uri and cid to statusView on profile responses (#4516)
* Codegen

* Add uri and cid to status view

* Changeset

* Snapshotty

* Snapshots again

* Oh duh it's the uris
2026-01-07 20:39:57 -06:00
github-actions[bot] e3baf89412 Version packages (#4515)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-07 17:46:10 -06:00
Eric Bailey 7ef893563b [AAv2] Add minAccessAge to Age Assurance regional configs (#4513) 2026-01-07 18:38:40 -03:00
rafael d5414b0b14 Rewire takedowns inclusion for getActors (#4514)
Co-authored-by: Eric Bailey <git@esb.lol>
2026-01-07 18:38:29 -03:00
github-actions[bot] 8ffbaccc68 Version packages (#4507)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-06 10:32:13 -08:00
Ian Wesley-Smith 63f97ae9c1 add recId to SuggestedUserSkeleton (#4440)
* add recId field to getSuggestedUsersSkeleton

* Revert "add recId field to getSuggestedUsersSkeleton"

This reverts commit 6b936265cd7efd1fc5ec0e05404122feec97e397.

* Adds recId to SuggestedUsersSkeleton

* Do some codegen

* What if more codegen?

* Adds `recId` in `getSuggestUsers` presentation fn

---------

Co-authored-by: Eduardo Cuducos <4732915+cuducos@users.noreply.github.com>
2026-01-06 12:26:14 -05:00
github-actions[bot] 0093727fc4 Version packages (#4505)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-06 15:59:52 +01:00
Matthieu Sieben 2f78893ace Small @atproto/lex improvements (#4501)
* Avoid escaping export identifier when it is a known global

* Use a record type instead of a record schema type as the generic parameter for `ListRecord`

* Export everything from `@atproto/lex-data` and `@atproto/lex-json`

* Add lex-json and lex-data to lex readme

* lock

* Apply defaults when running `schema.$build()` on objects and records.

* changeset

* simplify $Typed and $TypedMaybe

* tidy

* tidy

* Add `enumBlobRefs` utility function

* Add an `indexFile` option that allows generating an "index.ts" file that re-exports every tld namespaces.

* readme

* Add `base64ToUtf8` and `utf8ToBase64` utilities

* Add service auth authentication method
2026-01-06 15:47:13 +01:00
github-actions[bot] 141f91b2fa Version packages (#4504)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-05 18:26:41 -06:00
Daniel Holmgren e3357e9c78 Tap: Lex Indexer (#4483)
* initial pass

* tidying stuff up

* tidy types

* tests

* readme

* app.bsky -> com.exmaple

* changeset

* readme tweak

* pr feedback

* use new Main type

* simplify types a bit
2026-01-05 18:23:30 -06:00
github-actions[bot] e60af07248 Version packages (#4499)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-02 17:40:49 +01:00
Matthieu Sieben e9f065fce8 Add AbortSignal to lex-server handler context (#4498)
* Add AbortSignal to lex-server handler context

* tidy

* lint
2026-01-02 17:36:49 +01:00
Matthieu Sieben 43633250f4 ci 2026-01-02 14:39:11 +01:00
Matthieu Sieben 9a11a674ab ci 2026-01-02 14:31:29 +01:00
Matthieu Sieben cbd7c677bc ci 2026-01-02 14:19:26 +01:00
Matthieu Sieben e1ca2ff2a4 lint 2026-01-02 14:15:44 +01:00
Matthieu Sieben 627f49e33e ci 2026-01-02 14:10:14 +01:00
Matthieu Sieben 87e335af62 ci 2026-01-02 14:07:26 +01:00
Matthieu Sieben cffd7e98bc ci 2026-01-02 14:02:08 +01:00
Matthieu Sieben 538e3b0d55 npm publish OIDC 2026-01-02 12:53:28 +01:00
github-actions[bot] 8e759970af Version packages (#4497)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-02 09:48:32 +01:00
Matthieu Sieben 9af7a2d122 Password based agent implementation (#4443)
* Password based agent implementation

* tidy

* tidy

* wip

* tidy

* wip

* tidy

* wip

* tests

* tidy

* websocket

* tidy

* tidy

* tidy

* tidy

* tidy

* tidy

* tidy

* tidy

* changeset

* codegen

* tidy

* tidy

* tidy

* tidy

* tests

* tidy

* tidy

* tests

* tidy

* tidy

* tidy

* wip

* tidy

* memoize

* tidy

* tests

* tidy

* files reorg

* Ensure that default values match constraints

* wip

* use vitest to test lex

* Add readme

* fix lint

* add vitest workspace config

* vitest config

* vitest-cfg

* tests

* ignore coverage

* tidy
2026-01-01 13:28:29 +01:00
github-actions[bot] 95bd491ecb Version packages (#4466)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-30 10:26:18 -06:00
Eric Bailey ce497e8543 [APP-1653] Update AA config for Virginia (#4426)
* Add config for Virginia

* Changeset

* Update rules
2025-12-30 10:02:41 -06:00
Matthieu Sieben c4b967ff70 codegen 2025-12-29 14:33:39 +01:00
Matthieu Sieben 10cf1c1018 Improve session management (#4470)
* Improve session management

* fix test

* tidy

* tidy

* tidy

* tidy
2025-12-29 13:29:40 +01:00
Aviv Keller 3cf5b31a2d feat(doc): add @avivkeller to disclosure credits (#4482) 2025-12-23 16:06:05 -08:00
Daniel Holmgren e15e7cf1a0 Tap: add changeset for generating invalid URLs (#4481)
changset
2025-12-22 15:41:58 -06:00
Emelia Smith a164f89535 Fix Tap generating invalid URLs (#4478)
* Fix Tap generating invalid URLs

Fixes #4476

* Fix typo

* Fix typo
2025-12-22 15:40:18 -06:00
Foysal Ahamed 5bff33f7ce 🔊 Add logging around materialized view refresh in ozone (#4471) 2025-12-19 17:45:06 +01:00
Eric Bailey 57811e4647 Reserve field on ActorInfo (#4469) 2025-12-18 12:20:25 -06:00
Matthieu Sieben c7f5a86883 Fix redirect uri validation (#4468) 2025-12-17 19:31:49 +01:00
Matthieu Sieben e6b6107e02 Lex SDK improvements (#4457)
* Lex SDK improvements

* changeset

* tidy

* tidy

* Fix `include` option in `tsconfig.test.json` files

* tidy

* ignore "require" in cjs files

* tidy

* tidy

* Improve error management

* rename xrpc-error file

* tests

* fix lint

* lint

* tidy

* puppeteer cache busting

* fix oauth tests

* tidy

* wip

* tidy

* tidy

* tidy

* Forbid use of unsafe integers
2025-12-17 16:14:15 +01:00
Matthieu Sieben a78380c89c Minor ui tweak (#4467)
* Minor ui tweak

* tidy
2025-12-17 15:48:53 +01:00
Matthieu Sieben 95ef3c24e8 Improve error message in case of invalid redirect uri (#4465) 2025-12-17 15:01:17 +01:00
Emelia Smith 5d8e7a6588 Support initiating user registration via OAuth flow with prompt=create (#4461)
* Add prompt_values_supported to Authorization Server Metadata

* Expose prompt_values_supported in Authorization Server Metadata

* Support selecting view in oauth-provider-ui based on prompt parameter

* Support initiating user registration via prompt=create

* Add support to OAuth Client Browser Example for prompt=create

* Add test coverage for prompt=create
2025-12-17 14:57:16 +01:00
github-actions[bot] 64dc2ed0a6 Version packages (#4462)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-16 13:42:27 -06:00
Eric Bailey dd0fe8d5e7 [APP-1713] Add Age Assurance config for Tennessee (#4460)
* Add Age Assurance config for Tennessee

* Update changeset

Add Age Assurance configuration specific to Tennessee.
2025-12-16 13:35:12 -06:00
bnewbold 790fd3cb50 repo: mst key example fixes (#4421)
* repo: fix mst key height examples

* repo: update MST interop test fixtures (FAILING)

* reformat JSON

* raw codec -> dag-cbor

* add additional MST test case (from snarfed)

---------

Co-authored-by: dholms <dtholmgren@gmail.com>
2025-12-15 18:12:53 -08:00
bnewbold 046efb3f71 add contact endpoints to app.bsky.authFullApp permission set (#4454) 2025-12-12 13:09:05 -08:00
rafael f9ddcb253f fix contact import error handling (#4455) 2025-12-12 17:33:09 -03:00
bnewbold d56fb0d1d4 Initial Permission Sets for app.bsky.* and chat.bsky.* (#4349)
* early prototype perm sets for chat.bsky

* iterate on chat permission-set

* add initial app.bsky.* permission sets

* fix NSID for authManageLabelerService

* add just-merged contact verification endpoints to authFullApp

* fix more NSIDs

* Revert "add just-merged contact verification endpoints to authFullApp"

Will check with app team about which of these actually need client app
perms.

* rename authManageNotifications

* remove authDeletePosts (probably needs broader scope)
2025-12-12 10:12:40 -08:00
github-actions[bot] 4c6d37eace Version packages (#4447)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-12 11:59:07 -03:00
rafael 2e5a24cb87 lexicon: Remove WARNING from contact lexicons (#4452)
* lexicon: Remove WARNING from contact lexicons

* changeset
2025-12-12 06:53:11 -08:00
Daniel Holmgren b4a76bae7b Sync tool library (#4290)
* first pass

* tweaks

* tidy

* alt ack example

* websocket keep alive & callback/handler interface for channel

* simplify evt schemas

* readme

* thread through signal

* small fix

* router -> indexer & tidy wording

* small tweaks

* add type to ack msg

* pr feedback

* no implicit any

* resolve after buffered ack is actually sent

* create new websocket lib

* switch out ozone impl

* keepalive test

* remove websocket keepalive code from xrpc-server

* add the package in all the spots

* use websocket lib

* fix returned promise in simple-indexer

* websocket -> ws-client

* missed ref

* websocket -> ws-client

* nexus -> tap

* tidy readme

* admin password auth & fix some routes

* user evt -> identity evt

* small fixes

* add missing methods & types

* fix type in resolveDid

* update version

* pr feedback, assureAdminAUth util & README fixes

* more readme

* couple fixups

* lockfile

* tidy deps

* tests

* changesets

* lint readme

* add note to readme
2025-12-11 20:14:11 -06:00
Foysal Ahamed 5605e4d619 🐛 Avoid repo_push_event insertion if no rows are created (#4451)
* 🐛 Avoid repo_push_event insertion if no rows are created

* 📝 Add changeset
2025-12-12 01:41:16 +01:00
rafael 9917b9f1c8 bsky: Hookup missing endpoint (#4450) 2025-12-11 20:10:55 -03:00
Samuel Newman 13edecd0d3 Change contacts lexicon errors to Pascal case (#4446)
* PascalCase errors

* codegen

* convert error names

* fix error names for internal case

---------

Co-authored-by: rafaeleyng <rafaeleyng@gmail.com>
2025-12-11 12:32:00 -08:00
Foysal Ahamed d818622dc2 🔊 Add logging for ozone emitEvent endpoint (#4448) 2025-12-11 21:26:08 +01:00
Samuel Newman 5622bcf023 Add changeset for XRPC errors PR (#4445)
Add changeset for XRPC errors
2025-12-11 11:49:24 -08:00
Eric Bailey 73c41b669d [APP-1667] Handle Rolodex service errors, translate to XRPCError (#4429) 2025-12-11 13:06:32 -03:00
rafael 0c26509f26 fix notification declarations hydration (#4444) 2025-12-11 11:07:58 -03:00
github-actions[bot] 1168691c36 Version packages (#4442)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-10 15:23:01 -06:00
Eric Bailey 45928bfcd6 [AAv2] Retain existing status/access when re-starting AA flow (#4441)
* Retain existing status/access when re-starting AA flow

* Add test, fix dataplane v1-v2 bug
2025-12-10 15:17:25 -06:00
Ian Wesley-Smith c05dd09159 Revert "add recId field to getSuggestedUsersSkeleton"
This reverts commit 1a05fbb7a6.
2025-12-10 11:24:43 -08:00
Ian Wesley-Smith 1a05fbb7a6 add recId field to getSuggestedUsersSkeleton 2025-12-10 11:16:57 -08:00
github-actions[bot] d698904c4c Version packages (#4433)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-10 09:07:07 -08:00
Jim Calabro 397c62fe9c Sitemap Handlers (#4434)
* add sitemap protos

* cd packages/bsky && npm run buf:gen

* implement user sitemap handlers

* add a test

* lint:fix

* use /external

* use Readable pipe for slightly more effecient data transfer

* rename functions to include the word user

* improve not found case and add test
2025-12-10 11:50:44 -05:00
rafael e266405a89 bsky: Add contact-match notification type (#4436) 2025-12-10 13:43:09 -03:00
github-actions[bot] fa265521f0 Version packages (#4431)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-09 16:03:46 -06:00
Eric Bailey 39fa57080f [APP-1672] Add new read-only #declaredAgePref (#4432)
* Add new read-only #declaredAgePref

* Update descriptions

* Update schemas
2025-12-09 15:55:08 -06:00
Eric Bailey 6fab3940f6 [AAv2] Allow users to re-init Age Assurance if not in blocked state (#4428)
* Allow users to re-init Age Assurance if not in blocked state

* Skip test
2025-12-09 15:49:11 -06:00
rafael f4cef84494 bsky: Contact import notifications endpoint (#4430) 2025-12-09 15:29:01 -03:00
github-actions[bot] 104e6ed37b Version packages (#4412)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-08 16:05:29 -03:00
Foysal Ahamed 7eb99f2ac7 Store and expose AA access property in ozone event (#4427)
*  Store and expose AA access property in ozone event

* 📝 Add changeset
2025-12-08 17:34:56 +01:00
rafael 380aa3bfe7 appview: Contact import lexicons (#4393) 2025-12-08 11:47:20 -03:00
Foysal Ahamed a6e16cd0cd 🐛 Add minLength to enforce mandatory comment field in ozone events (#4423) 2025-12-05 19:42:42 +01:00
rafael fb95dc6e6f bsky: Remove not found profiles from queries (#4417) 2025-12-05 13:39:39 -03:00
Matthieu Sieben d551b0e352 Align lexicon document validation with the spec (#4416)
* Align lexicon document validation with the spec

* fix types

* fix tests
2025-12-05 12:58:55 +01:00
Matthieu Sieben 693784c3a0 Add support for base64url encoding/decoding (#4422)
* Add support for base64url encoding/decoding

* add tests

* tidy

* improve tests

* tidy

* tidy

* tidy
2025-12-05 11:40:29 +01:00
bnewbold b570086f08 makefile: clean command (#4420) 2025-12-04 16:05:16 -08:00
Foysal Ahamed cfa01edb9c 🐛 Fix isDelivery flag for ozone's email event (#4419) 2025-12-04 21:50:05 +01:00
rafael 308f432f7a bsky: Expand getRelationships to include blocks (#4418) 2025-12-04 16:36:05 -03:00
Matthieu Sieben e39ca114ac Fix support for NodeJS version <18.18, 19.x, <20.4 and 21.x (#4413)
Add support for NodeJS version <18.18, 19.x, <20.4 and 21.x
2025-12-03 19:15:33 +01:00
Matthieu Sieben 7e1d45877b Update name of dist files (#4411) 2025-12-03 13:46:09 +01:00
Matthieu Sieben b0ecba0211 Add missin service files from Docker build 2025-12-03 08:58:38 +01:00
github-actions[bot] dc08244c24 Version packages (#4386)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-02 11:05:55 -06:00
Eric Bailey 90f15698ee Age Assurance V2 (#4407)
Age Assurance v2 endpoints and config
2025-12-02 10:58:29 -06:00
Matthieu Sieben 2d13d05ab0 Allow .call() argument "params" to be undefined (#4405)
Allow `.call()` argument "`params`" to be `undefined` when all params are optional
2025-12-02 14:08:58 +01:00
Matthieu Sieben b643521a5e Revert "Increase strictness of DID Document validation"
This reverts commit 468980f228.
2025-12-02 12:55:08 +01:00
Matthieu Sieben 6b28b116f8 Revert "Improve validation of DID Documents"
This reverts commit 86ff431100.
2025-12-02 12:55:03 +01:00
Matthieu Sieben 468980f228 Increase strictness of DID Document validation 2025-12-02 12:53:09 +01:00
Matthieu Sieben 86ff431100 Improve validation of DID Documents 2025-12-02 12:50:35 +01:00
Matthieu Sieben a487ab8afe Add tests (#4401)
* Add tests

* Fixes

* drop `lexiconType` property

* Simplify `ParamsSchema` interface

* tidy

* More tests
2025-12-01 17:12:47 +01:00
Matthieu Sieben 03a2a4bb38 Add lex-document test cases (#4400) 2025-12-01 14:08:23 +01:00
Matthieu Sieben 8012627a12 Migrate OAuth libs to new @atproto/lex utils (#4383)
* Migrate Oauth libs to new @atproto/lex utils

* pnpm-lock

* tidy

* fix

* tidy

* tidy

* tidy

* tidy

* Implement lex resolution logging through hooks
2025-12-01 12:24:01 +01:00
Matthieu Sieben a17d2e8a59 Add ignoreInvalidLexicons when building lexicon schemas (#4398)
* Add `ignoreInvalidLexicons` when building lexicon schemas

* tidy
2025-12-01 08:41:54 +01:00
Matthieu Sieben 688f9d6759 Add changelog to npm packages and @atproto/lex readme (#4397)
* Add changelog to npm packages and `@atproto/lex` readme

* changeset
2025-11-30 20:02:12 +01:00
Matthieu Sieben 6e55972a11 changeset (#4399) 2025-11-30 20:01:42 +01:00
Matthieu Sieben bcae2b77b6 Increase string format typing strictness (#4389)
* Increase string format typing strictness

* fix tests

* tidy

* Use string formats from `@atproto/syntax`

* tidy

* `key` field in `record` definitions is non optional and now properly validated

* add missing /*@__NO_SIDE_EFFECTS__*/
2025-11-30 19:46:07 +01:00
Matthieu Sieben 1d445af2a7 lex SDK improvements (#4390)
* Add `l.nullable` schema builder

* Use unique symbol to describe Validator type metadata

* fixup! Add `l.nullable` schema builder

* Rework object validation logic to work without `options` argument

* Do not use symbol for type inference

* Use `Issue` classes to represent validation issues

* Properly apply default value with `const` and `enum` schemas

* style

* Require `l.discriminatedUnion` discriminator field to be a literal or enum schema

* Add `l.refined` schema

* Add more lexicons document validation tests

* wip

* use "assert" fn

* rework refine system

* use assert instead of check fn

* tidy

* Rename schema methods `validate`, `check` and `maybe` to `safeParse`, `matches` and `ifMatches` respectively.

* docs

* changeset
2025-11-30 14:35:15 +01:00
Matthieu Sieben 9f87ff3aa6 Type dict keys as optional (#4387)
* Type `dict` keys as optional

* tidy
2025-11-25 21:48:26 +01:00
Matthieu Sieben 0adc852c31 Use arrays for "account" permission action attributes (#4353)
* Use arrays for "account" permission `action` attributes

* Allow lexicon permission data to be readonly

* changeset

* tidy

* tidy

* tidy
2025-11-25 21:48:10 +01:00
Matthieu Sieben d396de016d Validate repo signature when fetching lexicon document with @atproto/lex-resolver (#4384)
* Expose `matchesIdentifier` and `extractAtprotoData` utilities.

* Update `formatDidKey` return type to `did🔑${string}`

* fixup! Expose `matchesIdentifier` and `extractAtprotoData` utilities.

* Validate repo signature when fetching lexicon document

* Use new lex-resolver proof as CID

* tidy

* tidy

* fic build

* fic build

* tidy

* tidy
2025-11-25 20:49:51 +01:00
Matthieu Sieben 0f2fc6592f Properly order lexicons in --ci mode (#4385)
* Properly order lexicons in `--ci` mode

* tidy

* tidy

* tidy
2025-11-25 20:21:26 +01:00
Matthieu Sieben be8e6c1f25 Permission-sets pre-release changes (#4382)
* Remove ability to define `blob` permission in permission sets

* Disallow `rpc` permissions with specific `aud` in permission-sets

* Add `toScopes()` utility on `IncludeScope`

* tidy
2025-11-25 19:51:02 +01:00
github-actions[bot] 86f6860418 Version packages (#4381)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-25 11:37:09 +01:00
Matthieu Sieben 23c271fcac @atproto/lex improvements (#4380)
* Deterministically order lexicon manifest items (Fixes #4378)

* Use `literal` value as default

* Code style

* Remove `--build`/`--no-build` argument from the `install` command

Fixes #4377

* Add ability to configure file extenstion and import file extension in `lex build`

Fixes #4376

* Fix description of `--allowLegacyBlobs` argument
2025-11-25 11:28:34 +01:00
github-actions[bot] d4d23e1950 Version packages (#4375)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-25 01:16:27 +01:00
Matthieu Sieben 5ffd612990 Msi/lex fixes (#4374)
* Properly await cleanup of iterator when encountering multiple lexicon documents with the same id.

* Add missing files from package.json
2025-11-25 01:07:19 +01:00
github-actions[bot] 151a7b0c6d Version packages (#4373)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-24 23:52:41 +01:00
Matthieu Sieben 7456f53e45 Fix @atproto/lex install (#4372) 2025-11-24 23:45:56 +01:00
github-actions[bot] 72d4bfcfc5 Version packages (#4370)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-24 23:27:19 +01:00
Matthieu Sieben 46550d6c1f Release of @atproto/lex packages (#4371) 2025-11-24 23:18:28 +01:00
bnewbold 69f53d632d lexicons: update string knownValue token refs to be fully-qualified (#4347)
* lexicons: update string knownValue token refs to be fully-qualified

* run codegen

* add changeset
2025-11-24 14:10:48 -08:00
github-actions[bot] 4dede90ea5 Version packages (#4369)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-24 22:26:10 +01:00
Matthieu Sieben aeb9b69306 remove changeset (#4368) 2025-11-24 22:18:39 +01:00
Matthieu Sieben 3182731f11 Fix docker build (#4367) 2025-11-24 22:07:16 +01:00
Matthieu Sieben 261968fd65 New TS SDK (#4366)
* lex

* packaging

* moke packaging

* revert test changes

* do not build temp

* tidy

* automatically build the list of `@atproto/lex/com` lexicons

* fix build

* Remove "com" export

* ridy

* remove manifest option

* tidy

* rename

* tidy

* tidy

* tidy

* tests

* add procedure params

* stricter tests

* tidy

* Improve ui8 parsing

* tidy

* tidy

* code split

* code split

* fix reserved keywords conflict

* exclude packages/lex/src/tests/lexicons from lint

* reserved keywords

* safe identifier

* fix build

* move lib.js to src/lib.ts

* Move tests dir

* fix ci ?

* increast lint size

* Remove `Record` type alias for recordsz

* fix package json exports

* Add support for unsafe characters in defs and nsids

* tidy

* token tests

* tidy

* name consistency

* remove unused `unknownKeys` params option

* Fix "moving" keys in `DictSchema` (remove `IntersectionSchema`)

* REview comments

* adapt shebang in `env`

* Make sure union object have their $type property set in typings

* fix

* Improve typing of `UnknownTypedObject`

* lex improvements

* code reorg

* split lex-builder

* tidy

* improve packaging

* rename lex-validation to lex-schema

* lex client

* rename prettifier option

* add lex-client as dependency to "lex"

* Export client as part of main export

* re-write example app using @atproto/lex

* add missing lex-client to tsconfig

* tidy

* add "null" schema type

* Smaller bundle code footprint

* tidy

* correctness

* tidy

* code split and improved testing

* tidy

* refactor common utils

* test all implementations

* improve tests

* tidy

* fix build

* fixes

* tidy

* lint

* tests

* tidy

* fix oauth-example app

* tidy

* tidy

* tests

* tidy

* Return an actual `Uint8Array` from `fromBase64Node`

* tidy

* adapt xrpc-server

* Rename `Lex` to `LexValue`

* minor fixes

* fix tests

* fix tests

* tidy

* fix

* tidy

* tidy

* fix `verifyCidForBytes` implementation

* fix imports

* tidy

* split lex-json in own package

* make base64 tests faster

* Add interop tests

* lint error

* tidy

* tidy

* changeset

* implement lex-resolver and lex-install

* remove need for polyfill

* readme

* more details

* tidy

* allow specifying `service` header on a per request basis

* tidy

* tidy

* tidy

* tidy

* add custom/intersection validation schemas

* tidy

* tidy

* remive un-necessary util

* improve typing of `l.object` output

* make "name" required in lexicon method errors

* fix tests

* tidy

* tidy

* add error responses

* update readme

* add "like " to example

* readme improvements

* tidy

* error management improvements

* Improve error results

* tidy

* refactor

* tidy

* lock

* Update binary to `ts-lex`

* tidy

* tidy

* Add "Overview" section

* fix build

* update bin

* readme-improvements

* paul's feedback

* Update packages/lex/lex/README.md

Co-authored-by: Daniel Holmgren <dtholmgren@gmail.com>

* Update packages/lex/lex/README.md

Co-authored-by: Daniel Holmgren <dtholmgren@gmail.com>

* Update packages/lex/lex/README.md

Co-authored-by: Daniel Holmgren <dtholmgren@gmail.com>

* Update packages/lex/lex/README.md

Co-authored-by: Daniel Holmgren <dtholmgren@gmail.com>

* Update packages/lex/lex/README.md

Co-authored-by: Daniel Holmgren <dtholmgren@gmail.com>

* Initial plan

* Address README review comments

Co-authored-by: matthieusieben <813661+matthieusieben@users.noreply.github.com>

* Clarify client configuration inheritance behavior

Co-authored-by: matthieusieben <813661+matthieusieben@users.noreply.github.com>

* Document allowLegacyBlobs default and compatibility implications (#15)

* Initial plan

* Add notes about default setting and compatibility for allowLegacyBlobs

Co-authored-by: matthieusieben <813661+matthieusieben@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: matthieusieben <813661+matthieusieben@users.noreply.github.com>

* Rename `Json` to `JsonValue` in lex-json package (#14)

* Initial plan

* Rename Json to JsonValue in lex-json package and dependent packages

Co-authored-by: matthieusieben <813661+matthieusieben@users.noreply.github.com>

* Remove import alias for JsonValue in ipld.ts

Co-authored-by: matthieusieben <813661+matthieusieben@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: matthieusieben <813661+matthieusieben@users.noreply.github.com>

* Update packages/lex/lex/README.md

Co-authored-by: Paul Frazee <pfrazee@gmail.com>

* Update packages/lex/lex/README.md

Co-authored-by: Paul Frazee <pfrazee@gmail.com>

* Update packages/lex/lex/README.md

Co-authored-by: Paul Frazee <pfrazee@gmail.com>

* Update packages/lex/lex/README.md

Co-authored-by: Paul Frazee <pfrazee@gmail.com>

* Update packages/lex/lex/README.md

Co-authored-by: Paul Frazee <pfrazee@gmail.com>

* Update packages/lex/lex/README.md

Co-authored-by: Paul Frazee <pfrazee@gmail.com>

* review comments and fixes

* Add lex to the dockerfiles

* tidy

* changeset for lex packages

* tidy

* Tidy

* tidy

* tidy

* Move language parsing to lex-data

* tidy

* doctoc

* error handling

* tidy

* tidy

* tidy

* fix

---------

Co-authored-by: Daniel Holmgren <dtholmgren@gmail.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: matthieusieben <813661+matthieusieben@users.noreply.github.com>
Co-authored-by: Paul Frazee <pfrazee@gmail.com>
2025-11-24 21:01:16 +01:00
Eric Bailey 39ab1b06bd [AAv2] Add new lexicons (#4363)
* Add new lexicon defs

* Codegen

* Add missing unions, reorder
2025-11-24 11:14:41 -06:00
Foysal Ahamed 0748d919d1 🐛 Allow querying for reviewNone status in ozone (#4360)
* 🐛 Allow querying for reviewNone status in ozone

* 📝 Add knownValues for reviewState
2025-11-21 00:10:56 +01:00
rafael 1d1d94e80d Adds tests for takendown cases in getPosts (#4359) 2025-11-20 16:21:31 -03:00
rafael 351f024e7a remove docker compose version (#4358) 2025-11-20 12:00:09 -03:00
github-actions[bot] 4021b08a58 Version packages (#4355)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-19 10:39:02 +01:00
Foysal Ahamed 032abf6b50 Update ozone action scheduler to queue emailing (#4340)
*  Update ozone action scheduler to queue emailing

* 🧹 Cleanup

* 📝 Add changeset
2025-11-19 10:26:14 +01:00
Daniel Holmgren 1dd20d3a81 WebSocket Library (#4348)
* create new websocket lib

* switch out ozone impl

* keepalive test

* remove websocket keepalive code from xrpc-server

* add the package in all the spots

* websocket -> ws-client

* missed ref

* fix import in ozone test
2025-11-18 17:34:41 -06:00
Foysal Ahamed 9115325c7b ❇️ Ozone takedown target service (#4344)
*  Add targetServices to takedown action to drive pds vs. appview takedowns

* 📝 Add changeset
2025-11-18 21:26:33 +01:00
github-actions[bot] b7bc95d6ab Version packages (#4336)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-14 09:47:57 -08:00
Misty De Méo 3628cebfbb explicit-slurs: remove stray match for unrelated word (#4330) 2025-11-13 12:00:45 -08:00
Emelia Smith f8e56b387f bsky: improve error messaging for DPoP bound access tokens (#4333) 2025-11-11 17:35:22 +01:00
Matthieu Sieben 82e75bf6c1 Remove non-existing reporter_stats from materliaziled view to refresh (#4335) 2025-11-10 17:38:46 +01:00
Eric Bailey 532b22822f [APP-1484] Add thread/search exploration code and feature gate (#4327)
* Add ranking exploration code and feature gate

* Import sort
2025-11-07 09:06:37 -06:00
hailey 92702322c3 ozone: check labels when asserting protected tags (#4283)
* check labels when asserting protected tags

* fix

* fix'

---------

Co-authored-by: Devin Ivy <devinivy@gmail.com>
2025-11-06 08:24:49 -08:00
github-actions[bot] 6f59d64aa1 Version packages (#4320)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-05 12:33:24 -06:00
Eric Bailey 39b5c08e07 Clean up getPostThreadV2 in prep for future work (#4269)
* Deprecate prioritizeFollowedUsers, codegen

* Remove from agent response types

* Remove codepaths

* Format

* Changeset

* Remove fields entirely

* Changeset

* Swap changeset
2025-11-05 11:11:32 -06:00
bnewbold 94ddc8219c resolveLexicon endpoint (#4227)
* Add resolveLexicon.json for lexicon resolution

* Update lexicons/com/atproto/lexicon/resolveLexicon.json

* chore: formatting and linting

* chore: Adding changeset information

* documentation: Updating descriptions for properties.

* refactor: Updating com.atproto.lexicon.resolveLexicon lexicon schema from PR discussion and feedback

* Update lexicons/com/atproto/lexicon/resolveLexicon.json

Co-authored-by: Nick Gerakines <12125+ngerakines@users.noreply.github.com>

* tweak changeset

* lexicon codegen

---------

Co-authored-by: Nick Gerakines <12125+ngerakines@users.noreply.github.com>
Co-authored-by: Matthieu Sieben <matthieusieben@users.noreply.github.com>
2025-11-02 12:04:06 -05:00
rafael 10021207b6 Search filtering by tag (#4312) 2025-10-31 16:12:23 -03:00
Roscoe Rubin-Rottenberg 756ab5d87f fix wildcard endings in firehose filtercollections (#4038)
* allow filtercollections to have a wildcard ending

* changeset

* fix parameters
2025-10-30 17:56:57 -05:00
Matthieu Sieben 3202dce91b oauth example app rework (#4319)
* oauth example app rework

* changeset
2025-10-30 16:08:57 +01:00
github-actions[bot] fc9f8e3ea0 Version packages (#4318)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-10-29 17:46:36 -05:00
Eric Bailey 15fe80c39f Add optional via property to follow records (#4317) 2025-10-29 17:16:06 -05:00
github-actions[bot] 632e1ba91f Version packages (#4313)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-10-29 10:04:09 -05:00
Eric Bailey 7c1429fe36 Add missed changeset (#4314) 2025-10-28 13:02:14 -05:00
Eric Bailey cdb6b27fc6 [TS-160] Add debug field to PostView and ProfileView*s (#4297)
* Add debug field to lex and proto, codegen

* Include debug field for DIDs configured via env var

* Gen bufs

* Update types and view

* Add debug field to profile views

* Safely populate debug field

* Format

* Use util

* Simplify existence check

* Values may be undefined

* Clarify type

* Update descriptions

* Include default value for dev-env

* Add test harness

(cherry picked from commit 7e49b4cf4cf3cbb989114540081e74f7a34acb6f)

* Use unknown field type instead of serializing

* KISS keep it set stupid

* Fix comments

* Differentiate between source of debug

* Add matching profile view test

* Specifying types of the merged objects isn't really helpful

* Changeset

* Add comment

* Put includeDebugField on HydrateCtx and use in views

* Mutate TestNetwork innards for tests

* Format

* Use separate debug schema for profile test

* Little more terse

* Oops, remove last usage

* Remove vestigial props

* Include other actor metadata

* Simply views, remove debugField
2025-10-28 11:59:50 -05:00
Matthieu Sieben d764c54fe4 Minor oauth-client-browser-example improvements (#4311)
* Minor oauth-client-browser-example improvements

* pnpm lock
2025-10-28 16:00:06 +01:00
Emelia Smith 1d219ff3f6 Fix: Label creation notification race condition (#4304)
Previously the notification for the label creation was dispatched before the label was actually inserted into the database. In all other cases where we use postgresql's notify, we do it after the insertInto, such that the listener is guaranteed to receive the newly created record when querying
2025-10-27 10:19:49 -07:00
github-actions[bot] 014674dce7 Version packages (#4307)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-10-27 17:11:43 +01:00
Foysal Ahamed 601401afce Add account level strike count in ozone (#4279)
*  Add account level strike count in ozone

*  Update test snapshot

* 📝 Update lexicons

* 🧹 Cleanup

*  Allow objects in policy-sev level association

*  Allow negative strikeCount

* 📝 Add changeset
2025-10-27 16:45:22 +01:00
github-actions[bot] a37a7de809 Version packages (#4302)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-10-27 14:08:11 +01:00
Matthieu Sieben f496fa2c4d Set dark background on authorization pages <body> in dark mode (#4301)
* Set dark background on authorization pages `<body>` in dark mode

* tidy
2025-10-24 16:17:46 +02:00
github-actions[bot] 33435c2e83 Version packages (#4298)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-10-24 13:02:23 +02:00
Matthieu Sieben 8ff5ec4caa OAuth client validation improvements (#4289)
* OAuth client validation improvements

* Remove `isLocalHostname` export

* tidy
2025-10-24 12:53:03 +02:00
Matthieu Sieben 1e702ea675 Add account data in pwd reset hooks (#4265)
* Add account data in pwd reset hooks

* tidy

* tidy

* tidy
2025-10-24 12:50:34 +02:00
Matthieu Sieben 1a7bd8c0d2 Remove abortcontroller-polyfill from @atproto/oauth-client-expo (#4300)
* Remove `abortcontroller-polyfill` that was causing "Property 'DOMException' doesn't exist" errors

* changeset
2025-10-24 12:38:31 +02:00
Matthieu Sieben 8c03d75b6c Remove un-implemented introspect endpoint from OAuth Server metadata (#4293)
Remove un-implemented `introspect` endpoint from OAuth Authorization Server metadata
2025-10-24 12:10:37 +02:00
Matthieu Sieben a8e307ef48 Record types are now also exported as .Main (#4299)
Record types are now exported as both `.Record` (as they used to) and `.Main` (for consistency)
2025-10-24 12:10:16 +02:00
Matthieu Sieben ac1d29ec0f Export record types using the Main identifier (#4294)
* Export record types using the `Main` identifier

* codegen
2025-10-24 11:55:36 +02:00
devin ivy dcf7ccd2fe dev-env: fix flaky handle resolutions in tests (#4296)
dev-env: place network mocks before data is flowing
2025-10-23 17:19:07 -05:00
devin ivy c518cf4f62 Fix builds: .nvmrc with most recent working node version in CI (#4284)
* fix builds: nvmrc with most recent working node version in ci

* apply to test and verify build steps
2025-10-18 00:20:27 -04:00
letreturn 1e49025331 chore: remove repetitive word in comment (#4276)
Signed-off-by: letreturn <letreturn@outlook.com>
2025-10-14 14:43:42 -07:00
Aaron Parecki dca500186e update links to ietf docs (#4273) 2025-10-14 14:43:01 -07:00
github-actions[bot] 3cd613f2f6 Version packages (#4270)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-10-14 09:37:23 +02:00
Foysal Ahamed ca768fe1b0 🐛 Fix query for ozone subject status with blobs (#4274)
* 🐛 Fix query for ozone subject status with blobs

* 📝 Add changeset
2025-10-14 09:31:58 +02:00
Eric Bailey 386f583cff Bump threadgate limits (#4268)
Bump threadgate max hidden replies to 300
2025-10-10 10:03:01 -05:00
github-actions[bot] 4c15fb47ce Version packages (#4263)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-10-08 10:44:18 -05:00
Eric Bailey 1cb5b9b80c Finalize report reasons (#4262)
* Finalize report reason lexicons

* Codegen

* Update NEW_TO_OLD_REASON_MAPPING in Ozone

* Changeset
2025-10-08 09:39:20 -05:00
github-actions[bot] bd469a6861 Version packages (#4247)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-10-06 19:13:28 +02:00
Foysal Ahamed 9c6b711c2e Remove flaky test since we already have coverage for randomization (#4260) 2025-10-06 19:05:47 +02:00
Matthieu Sieben e71d265dd4 Minor oauth jwk tweaks (#4256)
* Minor oauth jwk changes

* tidy
2025-10-06 15:45:05 +02:00
Foysal Ahamed 591de19524 Ozone delayed action (#4241)
*  Add delayed takedown feature to ozone

*  Apply tag auth check and add takedown event push

*  Add tests for takedown restrictions

* 🐛 Remove only test block

* 🐛 Add service forwarding

*  Copy event properties into scheduled takedown event

* 🧹 Cleanup

* 🐛 Make statuses required in listScheduledActions

* 🐛 Define required field in lexicon

*  Add tag when scheduling and cancelling scheduled takedown
2025-10-04 12:56:55 +02:00
Matthieu Sieben 09439d7d68 OAuth client improvements (#4216)
* wip

* Various OAuth client & API improvements

* pnpm lock

* Minor typing improvements

* ci

* fix
2025-10-02 16:21:17 +02:00
Matthieu Sieben f560cf2266 Allow "use" claims only in public jwk (#4103)
Disallow use of `use` claim in private JWK (replaced with `key_ops`)
2025-10-02 13:33:56 +02:00
Matthieu Sieben fefe70126d oauth-client-expo (#4220)
* `oauth-client-expo`

* working on android

* remove example app

* tidy

* tidy

* Do not install full expo

* tidy

* chngeset

* chngeset

* load expo

* tidy
2025-10-02 11:59:16 +02:00
Matthieu Sieben 93516238c9 Disable linting/formatting of protobuf files (#4185) 2025-10-02 10:00:07 +02:00
Daniel Holmgren d239f0bcf5 Add patent pledge link to README (#4246)
add patent pledge link to readme
2025-10-01 13:30:47 -05:00
github-actions[bot] d7154a7889 Version packages (#4233)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-09-30 11:00:51 -05:00
dan dba2d30e2c Restore session on network error (#4238)
* Split code in two branches

* Restore session on network error

* Changeset
2025-09-30 10:53:28 -05:00
rafael 7f38ee03c0 Add pronouns to profileView and profileViewBasic (#4232) 2025-09-26 18:02:37 -03:00
github-actions[bot] 778f76320e Version packages (#4229)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-09-26 11:51:16 -05:00
Daniel Holmgren 8dd77bad2f Async CAR fixes (#4223)
* chunk incoming car & yield to event loop

* add ui8reader

* 20 -> 25

* changeset

* Update packages/repo/src/car.ts

* build branch

* use node setImmediate

* dont build branch
2025-09-26 10:25:37 -05:00
rafael 1a5d7427bf Endpoint for onboarding starter packs (#4228) 2025-09-25 17:39:34 -03:00
hailey c16080e2c6 explicitly check for empty string in labelers header (#4225) 2025-09-25 13:36:56 -07:00
github-actions[bot] 7d0ecd8f3e Version packages (#4219)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-09-24 17:36:29 -03:00
rafael 8dc4caf558 Expand profile fields (#4224) 2025-09-24 17:30:10 -03:00
Daniel Holmgren 0c20539c71 Enforce max size on importRepo (#4083)
* enforce max size

* changesets

* fix build err

* use route-specific blob limit

* tidy changeset
2025-09-24 13:43:23 -05:00
rafael 86bb25768d Use static DID for appview in dev-env (#4181) 2025-09-23 10:11:20 -03:00
2555 changed files with 162096 additions and 125367 deletions
@@ -0,0 +1,904 @@
---
name: backend-lexification
description: >
Migrate backend apis from `@atproto/api`, `@atproto/lexicon`, `@atproto/xrpc`,
and `@atproto/lex-cli` to `@atproto/lex`. Use this skill whenever the user
wants to adopt `@atproto/lex` in a package that currently relies on
`@atproto/lex-cli` to perform code generation, replace AtpAgent with Client,
migrate generated lexicon code to the new `lex build` output, replace
`ids.XxxYyy` with namespace accessors, adopt branded string types, switch from
`jsonStringToLex` to `lexParse`, or any refactoring that moves code from the
old generated lexicon system to `@atproto/lex`. Also trigger when the user
mentions "lexification", "lex SDK", "lex migration", or asks about replacing
`@atproto/api` imports in service code.
---
# Lexification: Migrating to `@atproto/lex`
This skill describes how to refactor an AT Protocol service package to replace:
- `@atproto/api` (the old high-level client)
- `@atproto/lexicon` (the old runtime lexicon library)
- `@atproto/xrpc` (the old XRPC client types)
- `@atproto/lex-cli` codegen (the old code generator that produced `src/lexicon/` directories)
...with `@atproto/lex`, which provides generated TypeScript schemas with type-safe validation, type guards, builders, and an XRPC client.
For the full `@atproto/lex` API reference, read `packages/lex/lex/README.md`.
## Guiding Principles
- **Minimize runtime changes.** Prefer type-level changes over runtime changes. If the old code works correctly at runtime, keep its logic and just improve the types around it.
- **Tests still use `@atproto/api` exclusively.** Do not migrate test files — they will be migrated in a separate phase. Tests that imported from the old `src/lexicon/` should switch to importing from `@atproto/api` instead.
## What `@atproto/lex` Provides
- **Generated TypeScript schemas** via `lex build` (replaces `lex-cli`'s `lex gen-server`)
- **Runtime utilities**: `lexParse`, `lexStringify`, `toDatetimeString`, `currentDatetimeString`
- **Branded string types**: `DidString`, `HandleString`, `AtIdentifierString`, `AtUriString`, `UriString`, `DatetimeString`
- **Type guards**: `isDidString`, `isHandleString`, `isAtIdentifierString`, `isAtUriString`
- **Data types**: `Cid`, `parseCid`, `BlobRef` (from `@atproto/lex-data` sub-export, also re-exported from `@atproto/lex`)
- **`Client` class**: replaces `AtpAgent` for service-to-service XRPC calls
- **`xrpc` / `xrpcSafe` functions**: standalone XRPC requests with structured error handling
- **`XrpcError`**: replaces `XRPCError` from `@atproto/xrpc`
- **Type-safe schema accessors**: `$type`, `$matches`, `$isTypeOf`, `$build`, `$safeParse`, `$lxm`, etc.
The generated schemas live in `src/lexicons/` (note the plural). They expose a namespace-based API (e.g., `app.bsky.feed.post`) with `$type`, `$build`, `$check`, `$matches`, `$isTypeOf`, `$parse`, `$validate`, `$safeParse`, `$lxm`, and other utilities.
## Overview of Changes
The refactor touches these areas (in recommended order):
1. **Project Configuration** - Update `package.json` dependencies, build scripts and git configuration
2. **Project Code Setup** - Replace server creation, endpoint registration, imports from old generated code, app context initialization (`AtpAgent``Client`), and type aliases with new patterns from `@atproto/lex`
3. **Endpoint Handlers Registration** - New `server.add()` pattern, handler return types, LXM references, and parameter/output/record type replacements
4. **XRPC Client Calls** - Replace `AtpAgent` API calls with `Client.xrpc()`, `Client.call()`, or `xrpcSafe()`
5. **Type Strictness** - Use branded types (`DidString`, `HandleString`, `AtUriString`, `Cid`, etc.)
6. **Data Utilities** - Replace old data manipulation functions (`jsonStringToLex``lexParse`, datetime helpers, `BlobRef`, etc.)
7. **Schema Validation and Type Guards** - Replace old `isX()` functions with `$matches()` / `$isTypeOf` / `$build()`
## 1. Project Configuration
### Dependencies
Add `@atproto/lex` as a dependency.
Remove from dependencies (if present):
- `multiformats` (CID handling is now in `@atproto/lex-data`)
Remove from devDependencies:
- `@atproto/api` (unless still needed for tests in this phase)
- `@atproto/lexicon`
- `@atproto/lex-cli`
- `@atproto/xrpc`
### Lexicon installation
Remove the old `src/lexicon/` directory (singular) and any codegen scripts that referenced `lex gen-server`. We will also remove the (manually maintained) `./lexicons/` directory in order to manage installed lexicons with the new `lex install` commands:
```sh
rm -rf ./src/lexicon
rm -rf ./lexicons
```
Install every lexicon NSID that the code uses. Run `lex install` once per NSID (or pass multiple NSIDs at once):
```sh
lex install com.atproto.identity.resolveHandle app.bsky.feed.post
```
> [!NOTE]
> Some systems (like MacOS) already have a `lex` command. If that is the case, use `npx lex`, `pnpm exec lex` or `yarn lex` to run the correct binary.
This creates a `manifest.json` and a local `./lexicons/` directory with the schema files the package depends on.
The `manifest.json` file and `./lexicons/` directory (schema inputs) should be committed to git.
### Code generation
Replace the old codegen script with `lex build` as a prebuild step:
```diff
- "codegen": "lex gen-server --yes ./src/lexicon ./lexicons/com/atproto/*/* ./lexicons/app/bsky/*/* ...",
+ "prebuild": "lex build --lexicons ./lexicons --clear --indexFile",
+ "postinstall": "lex install --ci",
```
The `--indexFile` flag generates an index file that re-exports all root-level namespaces, and `--clear` ensures a clean output directory on each build.
The `lex install --ci` command will ensure that the `manifest.json` is up to date with the installed lexicons. Using the `postinstall` hook ensures that the command runs after `npm install` or `yarn install`, which ensures lexicon integrity in CI environments.
The `./src/lexicons/` directory (generated output) should be gitignored since it is regenerated on every build:
```sh
echo '/src/lexicons/' >> .gitignore
```
## 2. Project Code Setup
After running `lex build`, the new generated code lives in `src/lexicons/` (plural). Import the namespace objects from the index file:
```typescript
import { app, com, chat } from '../lexicons/index.js'
```
Each namespace provides access to schemas through dot notation:
- `app.bsky.feed.post` - a record schema
- `app.bsky.feed.defs.postView` - an object definition
- `com.atproto.admin.defs.repoRef` - another object definition
- `app.bsky.feed.getAuthorFeed` - a query/procedure schema
The old codegen used `ids` for NSID string constants (e.g., `ids.AppBskyFeedPost`). These are replaced with `$type` or `$lxm` properties on the schema objects.
> [!NOTE]
>
> If the app's build process & bundler supports it, consider using path aliases to simplify imports from `src/lexicons/index.js` (e.g., `import { app } from '#lexicons'`).
### Server Creation
The `createServer` function now comes from `@atproto/xrpc-server` directly, not from generated code:
```diff
- import { createServer } from './lexicon'
+ import { createServer } from '@atproto/xrpc-server'
```
The call signature changes slightly:
```diff
- let server = createServer({
+ const server = createServer([], {
validateResponse: config.debugMode,
payload: { ... },
})
```
Note the empty array `[]` as first argument.
The `Server` type used in handler files also changes:
```diff
- import { Server } from '../../../../lexicon'
+ import { Server } from '@atproto/xrpc-server'
```
The server's express router is accessed differently:
```diff
- app.use(server.xrpc.router)
+ app.use(server.router)
```
### App Context / Initialization
Replace `AtpAgent` with `Client` from `@atproto/lex`:
```diff
- import { AtpAgent } from '@atproto/api'
+ import { Client } from '@atproto/lex'
```
In context/config files, rename fields:
```diff
- searchAgent: AtpAgent | undefined
+ searchClient: Client | undefined
```
Client instantiation:
```diff
- const myServiceAgent = config.serviceUrl
- ? new AtpAgent({ service: config.serviceUrl })
- : undefined
- if (myServiceAgent && config.serviceApiKey) {
- myServiceAgent.api.setHeader('authorization', `Bearer ${config.serviceApiKey}`)
- }
+ const myServiceClient = config.serviceUrl
+ ? new Client({
+ service: config.serviceUrl,
+ headers: config.serviceApiKey
+ ? { authorization: `Bearer ${config.serviceApiKey}` }
+ : undefined,
+ })
+ : undefined
```
Headers are passed directly in the `Client` constructor options rather than being set imperatively after construction.
### Type Aliases File (if applicable)
The recommended pattern is to import from the generated code directly where needed, using import aliases. However, if the project contains a centralized types file that re-exports types from generated schemas, update it to import from the new generated code.
```typescript
import { app, chat, com } from '../lexicons/index.js'
// Type aliases
export type PostRecord = app.bsky.feed.post.Main
export type PostView = app.bsky.feed.defs.PostView
export type Label = com.atproto.label.defs.Label
export type StrongRef = com.atproto.repo.strongRef.Main
// Type guard aliases
export const isPostRecord = app.bsky.feed.post.$matches
export const isImagesEmbed = app.bsky.embed.images.$matches
// Validation aliases
export const parseStrongRef = com.atproto.repo.strongRef.$safeParse
```
The pattern is consistent:
- **Type**: `export type Foo = namespace.path.TypeName`
- **Type guard**: `export const isFoo = namespace.path.$matches`
- **Validation**: `export const parseFoo = namespace.path.$safeParse`
Types for the "main" definition of a record/object use `.Main`, sub-definitions use their specific name (e.g., `.ReplyRef`, `.ViewRecord`).
## 3. Endpoint Handlers Registration
The old pattern used method-chain registration on the server object. The new pattern uses `server.add()` with the schema object:
```diff
- server.app.bsky.feed.getAuthorFeed({
+ server.add(app.bsky.feed.getAuthorFeed, {
auth: ctx.authVerifier.optionalStandardOrRole,
handler: async ({ params, auth, req }) => { ... },
})
```
```diff
- server.com.atproto.identity.resolveHandle(async ({ req, params }) => {
+ server.add(com.atproto.identity.resolveHandle, async ({ params }) => {
```
The schema object is always imported from the generated `lexicons/` directory.
### Handler Return Type Safety
When handlers return JSON responses, use `'application/json' as const` for the encoding field to satisfy the return type:
```typescript
return {
encoding: 'application/json' as const,
body: { preferences },
}
```
Without the `as const`, TypeScript widens the string literal type and the handler's return type won't match. This is a type-level change only.
Alternatively, a `satisfies` clause can be used to ensure the returned object matches the expected schema output type:
```typescript
return {
encoding: 'application/json',
body: { preferences },
} satisfies app.bsky.actor.getPreferences.$Output
```
### LXM References in Handlers
Inside handlers, access the `$lxm` property from the schema for auth/proxy computations:
```typescript
const lxm = app.bsky.actor.getPreferences.$lxm
const aud = computeProxyTo(ctx, req, lxm)
permissions.assertRpc({ aud, lxm })
```
### Query/Procedure Parameter Types
Replace old codegen type imports with `$Params` on the schema:
```diff
- import { QueryParams } from '../../../../lexicon/types/app/bsky/feed/getAuthorFeed'
+ import { app } from '../../../../lexicons/index.js'
- type Params = QueryParams
+ type Params = app.bsky.feed.getAuthorFeed.$Params
```
### Output Types
Use `$Output` for response type annotations with `satisfies`:
```typescript
return {
encoding: 'application/json' as const,
body: { actor, relationships },
} satisfies app.bsky.graph.getRelationships.$Output
```
### Record/Object Types
Replace individual type imports with namespace-based access:
```diff
- import { Record as PostRecord } from '../lexicon/types/app/bsky/feed/post'
+ // Use the types file or direct namespace access:
+ import { PostRecord } from './types.js'
+ // or: app.bsky.feed.post.Main
```
### Type Aliases for Defs
When code uses types from `defs` files, reference them through the namespace:
```diff
- import { StatusAttr } from '../../lexicon/types/com/atproto/admin/defs'
+ // Use namespace access:
+ type StatusAttr = com.atproto.admin.defs.StatusAttr
```
```diff
- type CodeDetail = SomeCustomType
+ type CodeDetail = com.atproto.server.defs.InviteCode
+ type CodeUse = com.atproto.server.defs.InviteCodeUse
```
Whenever a new object with a `$type` needs to be constructed, use the `$build()` method on the schema:
```typescript
const code = com.atproto.server.defs.inviteCode.$build({
code: invite.code,
available: invite.availableUses - invite.uses.length,
disabled: invite.disabled === 1,
forAccount: invite.forUser,
createdBy: invite.createdBy,
createdAt: invite.createdAt,
uses: invite.uses,
})
```
Otherwise, for plain data objects that don't require a `$type`, just use the namespace types for type annotations without changing the construction logic:
```typescript
const code: com.atproto.server.defs.InviteCode = {
code: invite.code,
available: invite.availableUses - invite.uses.length,
disabled: invite.disabled === 1,
forAccount: invite.forUser,
createdBy: invite.createdBy,
createdAt: invite.createdAt,
uses: invite.uses,
}
```
### Token Values
For accessing lexicon "token" string constants:
```diff
- import { CURATELIST, MODLIST } from '../../../../lexicon/types/app/bsky/graph/defs'
+ import { app } from '../../../../lexicons/index.js'
+ const CURATELIST = app.bsky.graph.defs.curatelist.value
+ const MODLIST = app.bsky.graph.defs.modlist.value
```
### NSID String Constants
The old `ids` object (e.g., `ids.AppBskyFeedPost`) is replaced by `$type` on the schema:
```diff
- import { ids } from '../../../lexicon/lexicons'
- if (uri.collection === ids.AppBskyGraphList) {
+ import { app } from '../../../lexicons/index.js'
+ if (uri.collection === app.bsky.graph.list.$type) {
```
For LXM (lexicon method) checks in auth:
```diff
- method === ids.AppBskyFeedGetFeedSkeleton
+ method === app.bsky.feed.getFeedSkeleton.$lxm
```
For simple comparisons in utility code, plain string literals are also acceptable:
```diff
- if (uri.collection === ids.AppBskyFeedPost) {
+ if (uri.collection === 'app.bsky.feed.post') {
```
## 4. XRPC Client Calls
### Using `Client.xrpc()`
Replace `AtpAgent` API calls with `Client.xrpc()`:
```diff
- const res = await ctx.suggestionsAgent.api.app.bsky.unspecced.getSuggestionsSkeleton(
- { viewer: params.hydrateCtx.viewer, relativeToDid },
- { headers: params.headers },
- )
- return {
- suggestedDids: res.data.actors.map((a) => a.did),
- headers: res.headers,
- }
+ const res = await ctx.suggestionsClient.xrpc(
+ app.bsky.unspecced.getSuggestionsSkeleton,
+ {
+ params: { viewer: params.hydrateCtx.viewer, relativeToDid },
+ headers: params.headers,
+ },
+ )
+ return {
+ suggestedDids: res.body.actors.map((a) => a.did),
+ contentLanguage: res.headers.get('content-language') ?? undefined,
+ }
```
Key differences:
- Response data is on `.body` (not `.data`)
- Response headers use the standard `Headers` API (`.get()`)
- Parameters go in a `params` sub-object
- Procedure input goes in an `input` sub-object (not used in this example)
### Using `Client.call()`
Calls that only need the response body, and are happy letting any error propagate, can use the simpler `call()` method:
```typescript
const body = await ctx.suggestionsClient.call(
app.bsky.unspecced.getSuggestionsSkeleton,
// "params" for Queries, "input" for Procedures:
{ viewer: params.hydrateCtx.viewer, relativeToDid },
// Optional additional options (see API reference):
{
headers,
signal,
},
)
```
### Using `xrpcSafe()` for Error Handling
For calls where you want to handle errors without exceptions, use `xrpcSafe()`:
```diff
- import { AtpAgent } from '@atproto/api'
- import { ResponseType, XRPCError } from '@atproto/xrpc'
+ import { xrpcSafe } from '@atproto/lex'
- const agent = new AtpAgent({ service: fgEndpoint })
- try {
- const result = await agent.api.app.bsky.feed.getFeedSkeleton(
- { feed, limit, cursor },
- { headers },
- )
- skeleton = result.data
- } catch (err) {
- if (err instanceof AppBskyFeedGetFeedSkeleton.UnknownFeedError) { ... }
- if (err instanceof XRPCError) {
- if (err.status === ResponseType.Unknown) { ... }
- if (err.status === ResponseType.InvalidResponse) { ... }
- }
- throw err
- }
+ const result = await xrpcSafe(fgEndpoint, app.bsky.feed.getFeedSkeleton, {
+ headers,
+ params: { feed, limit, cursor },
+ })
+ if (!result.success) {
+ if (result.matchesSchemaErrors()) {
+ throw new InvalidRequestError(result.message, result.error)
+ }
+ if (result.error === 'InternalServerError') { ... }
+ if (result.error === 'UpstreamFailure') { ... }
+ throw result.reason
+ }
+ // result.body is the typed response
```
### XrpcError
Replace `XRPCError` from `@atproto/xrpc` with `XrpcError` from `@atproto/lex`:
```diff
- import { XRPCError } from '@atproto/xrpc'
+ import { XrpcError } from '@atproto/lex'
```
## 5. Type Strictness (Branded Types)
`@atproto/lex` exports branded string types that improve type safety. Apply these at type boundaries — function signatures, interface fields, database schema types — while keeping runtime code unchanged where possible.
### `DidString`
```diff
- did: string
+ did: DidString
```
```diff
- iss: string
+ iss: DidString | `${DidString}#${string}`
```
Use the type guard instead of string prefix checks:
```diff
- if (typeof iss !== 'string' || !iss.startsWith('did:')) {
+ if (typeof iss !== 'string' || !isDidString(iss)) {
```
Import from `@atproto/lex`:
```typescript
import { DidString, isDidString } from '@atproto/lex'
```
### `HandleString`
```diff
- handle: string
+ handle: HandleString
```
```typescript
import { HandleString, isHandleString } from '@atproto/lex'
```
### `AtIdentifierString`
For parameters that accept either a DID or a handle:
```diff
- handleOrDid: string
+ handleOrDid: AtIdentifierString
```
Use the guard before passing to functions that require a specific type:
```typescript
import { AtIdentifierString, isAtIdentifierString } from '@atproto/lex'
if (!isAtIdentifierString(actor)) {
throw new InvalidRequestError('Invalid actor identifier')
}
const account = await getAccount(actor)
```
### `AtUriString`
Apply to URI fields coming from data plane responses:
```diff
- post: { uri: item.uri, cid: item.cid || undefined },
+ post: { uri: item.uri as AtUriString, cid: item.cid || undefined },
```
### `Cid`
Replace `CID` from `multiformats` with `Cid` from `@atproto/lex-data`:
```diff
- import { CID } from 'multiformats/cid'
+ import { Cid, parseCid } from '@atproto/lex-data'
```
Or import from `@atproto/lex` directly:
```typescript
import { Cid } from '@atproto/lex'
```
### `DatetimeString`
For datetime fields in database schemas and interfaces:
```diff
- indexedAt: string
+ indexedAt: DatetimeString
```
### Type Narrowing at Data Boundaries
When data comes from external sources (protobuf, data plane, Kysely queries), cast to branded types at the boundary:
```diff
- suggestedDids: dids,
+ suggestedDids: dids as DidString[],
```
```diff
- qb.where('actor.did', '=', filter.sub!)
+ qb.where('actor.did', '=', filter.sub! as DidString)
```
This pattern is common when Kysely query builders need branded types that the query parameter doesn't naturally have.
### `HeadersMap`
Replace `Record<string, string>` headers with proper `Headers` type:
```diff
- import { HeadersMap } from '@atproto/xrpc'
+ import { Headers as HeadersMap } from '@atproto/xrpc-server'
```
> [!NOTE]
>
> `Headers` from `@atproto/xrpc-server` conflicts with the standard `Headers` type, so we alias it as `HeadersMap` to avoid confusion.
Response headers from `xrpc()` calls use the standard `Headers` API:
```diff
- result.headers['content-language']
+ result.headers.get('content-language')
```
## 6. Data Utilities
### JSON/Lex Parsing
Replace `jsonStringToLex` from `@atproto/lexicon` with `lexParse` from `@atproto/lex`:
```diff
- import { jsonStringToLex } from '@atproto/lexicon'
+ import { lexParse } from '@atproto/lex'
- const parsed = jsonStringToLex(
- Buffer.from(payload).toString('utf8'),
- ) as SubjectActivitySubscription
+ const parsed = lexParse<app.bsky.notification.defs.SubjectActivitySubscription>(
+ Buffer.from(payload).toString('utf8'),
+ )
```
`lexParse` accepts a type parameter, eliminating the need for `as` casts.
### Datetime Strings
Replace `new Date().toISOString()` with branded datetime utilities for AT Protocol datetime fields:
```diff
- createdAt: new Date().toISOString(),
+ createdAt: currentDatetimeString(),
```
For converting an existing `Date` object:
```diff
- indexedAt: someDate.toISOString(),
+ indexedAt: toDatetimeString(someDate),
```
```typescript
import { toDatetimeString, currentDatetimeString } from '@atproto/lex'
```
### BlobRef
The old `BlobRef` class from `@atproto/lexicon` is replaced by a simple interface from `@atproto/lex-data`. It is no longer a class, so `instanceof` checks are not possible anymore. Instead, use type guards to check if an object is a `BlobRef`:
```diff
- import { BlobRef } from '@atproto/lexicon'
+ import { BlobRef } from '@atproto/lex-data'
- export const cidFromBlobJson = (json: BlobRef) => {
- if (json instanceof BlobRef) {
- return json.ref.toString()
- }
- if (json['$type'] === 'blob') {
- return (json['ref']?.['$link'] ?? '') as string
- }
- return (json['cid'] ?? '') as string
- }
+ export const cidFromBlobJson = (json: BlobRef): string => {
+ return json.ref.toString()
+ }
```
```diff
- if (value instanceof BlobRef) { ... }
+ if (isBlobRef(value)) { ... }
```
### Legacy BlobRefs
Legacy blob references (`{ cid: string, mimeType: string }`) are automatically handled based on the **strict mode** setting. When `strict: false`, both standard and legacy blob formats are accepted. When `strict: true` (the default), only standard `TypedBlobRef` format is accepted.
```typescript
import {
TypedBlobRef,
LegacyBlobRef,
isTypedBlobRef,
isLegacyBlobRef,
} from '@atproto/lex-data'
// Check for standard BlobRef
if (isTypedBlobRef(value)) {
console.log(value.ref.toString())
}
// Check for legacy format
if (isLegacyBlobRef(value)) {
console.log(value.cid)
}
```
New utility functions are available for working with both formats:
```typescript
import {
BlobRef,
getBlobCid,
getBlobCidString,
getBlobMime,
getBlobSize,
} from '@atproto/lex-data'
declare const blobRef: BlobRef // TypedBlobRef | LegacyBlobRef
const cid = getBlobCid(blobRef) // Returns Cid object
const cidString = getBlobCidString(blobRef) // Returns string (optimized)
const mimeType = getBlobMime(blobRef)
const size = getBlobSize(blobRef) // Returns number | undefined (legacy refs don't have size)
```
### Strict Mode in Validation
All schema validation methods (`$parse`, `$safeParse`, `$validate`, `$safeValidate`) accept an optional `{ strict }` option that controls validation behavior uniformly across both parse and validate modes:
**Strict mode (`strict: true`, the default):**
- Datetime strings must have proper timezone information
- Blob MIME types and size constraints are enforced
- Only raw CIDs are allowed in blob references
- Legacy blob references are rejected
**Non-strict mode (`strict: false`):**
- Datetime strings without timezones are accepted
- Blob MIME type and size constraints are not enforced
- Any valid CID is allowed in blob references
- Legacy blob references are accepted
```typescript
// Default strict validation
const result1 = schema.$safeParse(data) // strict: true by default
// Explicit strict validation
const result2 = schema.$safeParse(data, { strict: true })
// Non-strict validation (lenient)
const result3 = schema.$safeParse(data, { strict: false })
// Applies to all validation methods
schema.$validate(data, { strict: false })
schema.$parse(data, { strict: false })
schema.$safeValidate(data, { strict: false })
```
The `Client` class has a `strictResponseProcessing` option that controls the default strict mode for all XRPC calls:
```typescript
const client = new Client(session, {
strictResponseProcessing: false, // Use non-strict mode for all calls
})
```
When `strictResponseProcessing: false`, response validation will use `strict: false`, which means legacy blobs and other lenient data formats are automatically accepted. Individual calls can override this with per-call options.
### Lex Stringify
```diff
- import { stringifyLex } from '@atproto/lexicon'
+ import { lexStringify } from '@atproto/lex'
```
## 7. Schema Validation and Type Guards
### `$matches()` — Validates and Narrows Unknown Data
Use `$matches()` when the data has not been pre-validated (e.g., it comes from an external source, or you need full runtime validation):
```diff
- import { isRepoRef } from '../../../../lexicon/types/com/atproto/admin/defs'
- if (isRepoRef(subject)) { ... }
+ if (com.atproto.admin.defs.repoRef.$matches(subject)) { ... }
```
```diff
- repost: isSkeletonReasonRepost(item.reason) ? ... : undefined,
+ repost: app.bsky.feed.defs.skeletonReasonRepost.$matches(item.reason) ? ... : undefined,
```
### `$isTypeOf` — Discriminates Pre-Validated Data by `$type`
Use `$isTypeOf` when the data is already validated and you only need to discriminate based on the `$type` property. This is faster than `$matches()` because it skips validation. Common in `.find()` and `.filter()` callbacks on arrays of already-parsed preference objects or union members:
```diff
- const personalDetailsPref = prefs.find(
- (pref) => pref.$type === 'app.bsky.actor.defs#personalDetailsPref'
- )
+ const personalDetailsPref = prefs.find(
+ app.bsky.actor.defs.personalDetailsPref.$isTypeOf,
+ )
```
`$isTypeOf` is a type predicate function, so TypeScript narrows the type automatically when used in conditionals or `.find()`.
### `$build()` — Constructs Typed Objects
Use `$build()` instead of manually setting `$type`:
```diff
- return {
- $type: 'app.bsky.graph.defs#relationship',
- did,
- following: subject.following,
- }
+ return app.bsky.graph.defs.relationship.$build({
+ did,
+ following: subject.following,
+ })
```
```diff
- prefs.push({
- $type: 'app.bsky.actor.defs#declaredAgePref',
- isOverAge13: age >= 13,
- isOverAge16: age >= 16,
- isOverAge18: age >= 18,
- })
+ prefs.push(
+ app.bsky.actor.defs.declaredAgePref.$build({
+ isOverAge13: age >= 13,
+ isOverAge16: age >= 16,
+ isOverAge18: age >= 18,
+ }),
+ )
```
`$build()` automatically sets the `$type` field and returns a properly typed object.
## Tests
Tests still rely exclusively on `@atproto/api`. When tests previously imported from the old `src/lexicon/` directory, redirect those imports to `@atproto/api`:
```diff
- import { ids } from '../../src/lexicon/lexicons'
- import { RepoRef, isRepoRef } from '../../src/lexicon/types/com/atproto/admin/defs'
- import { $Typed } from '../../src/lexicon/util'
+ import { $Typed, AtpAgent, ComAtprotoAdminDefs, ids } from '@atproto/api'
```
Do not change how tests make XRPC calls — they continue to use `AtpAgent` from `@atproto/api`. This allows to ensure that the refactor does not break existing functionality at runtime. Tests will be migrated to `@atproto/lex` in a separate phase after all service code has been lexified.
## Common Pitfalls
1. **`$type` vs `$lxm`**: Use `$type` for record/object type strings (e.g., `app.bsky.feed.post.$type` = `'app.bsky.feed.post'`). Use `$lxm` for XRPC method identifiers used in auth checks and proxy routing. `$nsid` is also available for the raw NSID string if needed, this is especially useful for lexicon defs that don't have a `main` type but still need to reference their NSID.
2. **`$matches` vs `$isTypeOf`**: Use `$matches()` when data needs validation (unknown input). Use `$isTypeOf` when data is already validated and you just need to check the `$type` tag (e.g., union discrimination, filtering an array of pre-parsed objects).
3. **Branded type casts at boundaries**: Data from protobuf/data plane/Kysely returns plain strings. Cast to branded types (`as DidString`, `as AtUriString`) at the point where data enters the typed domain. Avoid `assert()` — use `as` casts at known-safe boundaries instead.
4. **`'application/json' as const`**: Handler return values need `as const` on the encoding string literal to satisfy the return type. Without it, TypeScript widens the type.
5. **Response header changes**: Old `AtpAgent` returned headers as a plain object with string indexing. New `Client`/`xrpc` returns standard `Headers` objects requiring `.get()`.
6. **`@atproto/lex-data` sub-export**: `Cid`, `parseCid`, and `BlobRef` are available from `@atproto/lex-data` for files that only need data types without the full `@atproto/lex` package. Both import paths work.
7. **Prefer `@atproto/lex` imports** over `@atproto/syntax` when both export the same symbol (e.g., `DidString`, `AtUriString`).
8. **Avoid `assert()` calls.** Use type guards (`isDidString()`, `isHandleString()`) with conditional logic rather than assertions.
## Import Source Changes Summary
| Before | After |
| ------------------------------------------------- | ---------------------------------------------------------------------------- |
| `@atproto/api` (`AtpAgent`) | `@atproto/lex` (`Client`) |
| `@atproto/lexicon` (`jsonStringToLex`, `BlobRef`) | `@atproto/lex` (`lexParse`, `BlobRef`) |
| `@atproto/lexicon` (`stringifyLex`) | `@atproto/lex` (`lexStringify`) |
| `@atproto/xrpc` (`HeadersMap`, `XRPCError`) | `@atproto/xrpc-server` (`Headers`), `@atproto/lex` (`XrpcError`, `xrpcSafe`) |
| `multiformats/cid` (`CID`) | `@atproto/lex` (`Cid`, `parseCid`) |
| `@atproto/syntax` (`DidString`, etc.) | `@atproto/lex` (`DidString`, `HandleString`, etc.) — prefer `@atproto/lex` |
| `../lexicon` (`Server`, `createServer`) | `@atproto/xrpc-server` (`Server`, `createServer`) |
| `../lexicon/lexicons` (`ids`) | `../lexicons/index.js` (`app`, `com`, `chat`) |
| `../lexicon/types/...` (types, guards) | `../lexicons/index.js` (namespace-qualified access) |
+247
View File
@@ -0,0 +1,247 @@
---
name: vitest-patterns
description: Patterns and conventions for writing vitest tests in this project. This skill should be used when writing new tests, adding test cases to existing test files, or reviewing test code for correctness. Trigger whenever the user asks to write tests, add test coverage, create test files, or mentions vitest/testing.
---
# Vitest Test Patterns
Conventions and patterns for writing vitest tests in this codebase.
## Imports
Always import test utilities from `vitest`. Use named imports:
```ts
import { assert, describe, expect, it, vi } from 'vitest'
```
Only import what you use. Add `vi` only when using mock functions. Add `assert` only when narrowing types.
## Test Structure
### Describe labels
Pass the function/class under test directly as the `describe` label when possible. Use string labels for conceptual groupings:
```ts
// Function reference as label (preferred when testing a single export)
describe(parseCid, () => { ... })
describe(isLexValue, () => { ... })
// String label for conceptual groups or when testing multiple related behaviors
describe('roundtrip toBase64 <-> fromBase64', () => { ... })
describe('isObject', () => { ... })
```
### Grouping
Nest logical groupings inside the top-level describe. Common groupings:
```ts
describe(someFunction, () => {
describe('valid inputs', () => { ... })
describe('invalid inputs', () => { ... })
describe('edge cases', () => { ... })
})
```
For features with a default behavior and an override, cover both:
```ts
describe('validateResponse', () => {
it('rejects invalid response body by default', ...)
it('accepts invalid response body when disabled', ...)
it('succeeds with valid response body when enabled', ...)
})
```
For safety-critical code, group edge cases under a `'safety'` or `'edge cases'` describe:
```ts
describe('safety', () => {
it('handles cyclic structures without infinite loops', () => { ... })
it('handles deep structures without exceeding call stack', () => { ... })
})
```
## Parameterized Tests
Use `for...of` loops over test case arrays instead of `it.each`:
```ts
describe(isLexScalar, () => {
for (const { note, value, expected } of [
{ note: 'string', value: 'hello', expected: true },
{ note: 'boolean', value: true, expected: true },
{ note: 'null', value: null, expected: true },
{ note: 'number (float)', value: 3.14, expected: false },
{ note: 'undefined', value: undefined, expected: false },
]) {
it(note, () => {
expect(isLexScalar(value)).toBe(expected)
})
}
})
```
This also works for running the same test suite against multiple implementations:
```ts
for (const utf8Len of [utf8LenNode!, utf8LenCompute!] as const) {
describe(utf8Len, () => {
it('computes utf8 string length', () => {
expect(utf8Len('a')).toBe(1)
})
})
}
```
## Assertions
### Type narrowing with `assert`
Use `assert()` from vitest for type narrowing and boolean checks. **Always prefer `assert(result.success)` over `expect(result.success).toBe(true)`** — the `assert` provides type narrowing in TypeScript, which allows the rest of the test to access narrowed properties without additional type guards.
```ts
// Narrow to a specific type before further assertions
assert(err instanceof XrpcFetchError)
expect(err.cause).toBeInstanceOf(TypeError)
// Discriminated union checks - PREFERRED
assert(result.success)
expect(result.body).toEqual({ value: 'hello' })
// TypeScript now knows result.body exists
assert(!result.success)
expect(result).toBeInstanceOf(XrpcResponseError)
// TypeScript now knows result has error properties
// DON'T do this - it doesn't narrow types
expect(result.success).toBe(true) // ❌ No type narrowing
if (result.success) {
expect(result.body).toEqual({ value: 'hello' }) // Still need type guard
}
```
### Error assertions with `rejects.toSatisfy`
For thrown errors, prefer `rejects.toSatisfy()` over `rejects.toThrow()` when you need multiple detailed assertions:
```ts
await expect(
someAsyncFn(),
).rejects.toSatisfy((err) => {
assert(err instanceof SomeError)
expect(err.cause).toBeInstanceOf(TypeError)
expect(err.message).toContain('failed')
return true // must return true
})
```
Always `return true` at the end of `toSatisfy` callbacks.
For simple "it throws" checks, `toThrow()` is fine:
```ts
expect(() => parseCid(invalidStr)).toThrow()
expect(() => cidForRawHash(new Uint8Array(31))).toThrow('Invalid SHA-256 hash length')
```
## Mock Functions
### Use `vi.fn()` with type parameters
When you need to inspect how a function was called, use `vi.fn<Type>()`:
```ts
const fetchHandler = vi.fn<FetchHandler>(async () =>
Response.json({ value: 'ok' }),
)
await xrpc(fetchHandler, testQuery, { params: { limit: 25 } })
expect(fetchHandler).toHaveBeenCalledOnce()
const [path, init] = fetchHandler.mock.calls[0]
expect(path).toContain('/xrpc/io.example.testQuery')
```
When you don't need to inspect calls, use a plain typed function:
```ts
const fetchHandler: FetchHandler = async () => Response.json({ value: 'hello' })
```
## Test Fixtures
Define reusable fixtures at the top of the file, outside describe blocks:
```ts
const invalidCidStr = 'invalidcidstring'
const cborCidStr = 'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a'
const cborCid = parseCid(cborCidStr, { flavor: 'cbor' })
```
Keep fixtures minimal and focused on what the tests need.
## TypeScript in Tests
### Intentionally invalid arguments
Use `// @ts-expect-error` with a description:
```ts
await xrpc(fetchHandler, testQuery, {
// @ts-expect-error intentionally passing invalid params
params: { limit: 'not-a-number' },
validateRequest: true,
})
```
## Global Stubbing
When testing code that uses a global, temporarily replace it and restore in a `finally` block:
```ts
it('throws TypeError when fetch is not available', () => {
const originalFetch = globalThis.fetch
try {
// @ts-expect-error removing fetch to simulate missing environment
globalThis.fetch = undefined
expect(() => buildAgent({ service: 'https://example.com' })).toThrow(TypeError)
} finally {
globalThis.fetch = originalFetch
}
})
```
Use `try/finally` (not `beforeEach`/`afterEach`) when the stub is scoped to a single test.
## Roundtrip Tests
When testing encode/decode or serialize/deserialize pairs, add a dedicated roundtrip describe:
```ts
describe('roundtrip toBase64 <-> fromBase64', () => {
it('roundtrips empty array', () => {
const original = new Uint8Array(0)
expect(ui8Equals(fromBase64(toBase64(original)), original)).toBe(true)
})
it('roundtrips all byte values', () => {
const allBytes = new Uint8Array(256)
for (let i = 0; i < 256; i++) allBytes[i] = i
expect(ui8Equals(fromBase64(toBase64(allBytes)), allBytes)).toBe(true)
})
})
```
## Workflow
- Do not worry about code formatting or lint issues. The user will review changes in their editor, which applies formatting automatically on save.
- Do not commit test changes. Leave them as unstaged modifications for the user to review.
## Running Tests
```bash
pnpm exec vitest run path/to/file.test.ts
```
+1
View File
@@ -1,4 +1,5 @@
node_modules
**/dist
.DS_Store
.git
Dockerfile
+22 -3
View File
@@ -1,5 +1,24 @@
dist
node_modules
# buf
packages/bsky/src/proto
packages/bsync/src/proto
# codegen
packages/api/src/client
packages/lexicon-resolver/src/client
packages/bsky/src/lexicon
packages/pds/src/lexicon
packages/ozone/src/lexicon
# @atproto/lex
packages/lexicon-resolver/src/lexicons
packages/lex/*/src/lexicons
packages/lex/*/tests/lexicons
packages/oauth/oauth-client-browser-example/src/lexicons
packages/pds/src/lexicons
packages/bsky/src/lexicons
packages/sync/src/lexicons
# others
packages/api/src/moderation/const/labels.ts
packages/oauth/*/src/locales/*/messages.ts
packages/oauth/oauth-client-expo/android/build
+6 -2
View File
@@ -22,7 +22,7 @@
"import/extensions": ["off", "ignorePackages"],
"import/export": "off",
"import/namespace": "off",
"import/no-deprecated": "error",
"import/no-deprecated": "off",
"import/no-absolute-path": "error",
"import/no-dynamic-require": "error",
"import/no-self-import": "error",
@@ -33,6 +33,9 @@
"distinctGroup": true,
"alphabetize": { "order": "asc" },
"newlines-between": "never",
"pathGroups": [
{ "pattern": "#/**", "group": "parent", "position": "before" }
],
"groups": [
"builtin",
"external",
@@ -71,7 +74,7 @@
"env": { "jest": true }
},
{
"files": "*.js",
"files": ["*.js", "*.cjs"],
"rules": {
"@typescript-eslint/no-var-requires": "off"
}
@@ -94,6 +97,7 @@
"typescript": {
"project": [
"tsconfig.json",
"packages/lex/*/tsconfig.json",
"packages/oauth/*/tsconfig.json",
"packages/oauth/*/tsconfig.src.json",
"packages/internal/*/tsconfig.json",
+9 -3
View File
@@ -1,7 +1,13 @@
packages/**/src/lexicon/** linguist-generated=true
packages/api/src/client/** linguist-generated=true
packages/lexicon-resolver/src/client/** linguist-generated=true
# buf
packages/bsky/src/proto/** linguist-generated=true
packages/bsync/src/proto/** linguist-generated=true
# codegen
packages/api/src/client/** linguist-generated=true
packages/ozone/src/lexicon/** linguist-generated=true
# @atproto/lex
packages/lexicon-resolver/src/lexicons/** linguist-generated=true
# i18n
packages/oauth/oauth-provider-ui/src/locales/**/messages.po linguist-generated=true
@@ -3,6 +3,8 @@ on:
push:
branches:
- main
- msi/pds-lexification
env:
REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }}
USERNAME: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_USERNAME }}
@@ -3,7 +3,8 @@ on:
push:
branches:
- main
- divy/etcd-dp-host-list
- msi/pds-lexification
env:
REGISTRY: ghcr.io
USERNAME: ${{ github.actor }}
@@ -3,6 +3,7 @@ on:
push:
branches:
- main
env:
REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }}
USERNAME: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_USERNAME }}
@@ -3,6 +3,7 @@ on:
push:
branches:
- main
env:
REGISTRY: ghcr.io
USERNAME: ${{ github.actor }}
@@ -3,7 +3,7 @@ on:
push:
branches:
- main
- divy/ozone-metadata
env:
REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }}
USERNAME: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_USERNAME }}
@@ -3,6 +3,7 @@ on:
push:
branches:
- main
env:
REGISTRY: ghcr.io
USERNAME: ${{ github.actor }}
@@ -3,6 +3,8 @@ on:
push:
branches:
- main
- msi/pds-lexification
env:
REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }}
USERNAME: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_USERNAME }}
@@ -3,7 +3,8 @@ on:
push:
branches:
- main
- divy/pds-build-dblock-fix
- msi/pds-lexification
env:
REGISTRY: ghcr.io
USERNAME: ${{ github.actor }}
+54
View File
@@ -0,0 +1,54 @@
name: Claude Code
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]
jobs:
claude:
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
actions: read # Required for Claude to read CI results on PRs
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# This is an optional setting that allows Claude to read CI results on PRs
additional_permissions: |
actions: read
# Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
# prompt: 'Update the pull request description to include a summary of changes.'
# Optional: Add claude_args to customize behavior and configuration
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://code.claude.com/docs/en/cli-reference for available options
# claude_args: '--allowed-tools Bash(gh pr:*)'
# NOTE(sfn): we can add a custom system prompt here
claude_args: |
--model claude-opus-4-5-20251101
+23 -16
View File
@@ -5,33 +5,40 @@ on:
branches:
- main
permissions:
id-token: write
contents: write
pull-requests: write
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
jobs:
build:
name: Build & Publish
name: Publish
if: github.repository == 'bluesky-social/atproto'
runs-on: ubuntu-22.04
permissions:
contents: write
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: corepack enable && corepack prepare --activate
- uses: actions/setup-node@v4
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v6
with:
node-version-file: package.json
cache: 'pnpm'
- run: pnpm i --frozen-lockfile
- run: pnpm build
- run: pnpm verify
cache: pnpm
node-version-file: '.nvmrc'
registry-url: 'https://registry.npmjs.org'
# Temporary workaround for an issue with Node.js v22
# https://github.com/npm/cli/issues/9151
# https://github.com/npm/cli/pull/9152
- run: npm install --global npm@11.11.1
- run: npm install --global npm@latest
- run: pnpm install --frozen-lockfile
env:
PUPPETEER_SKIP_DOWNLOAD: true
- name: Publish
id: changesets
uses: changesets/action@v1
id: changesets
with:
publish: pnpm release
version: pnpm version-packages
version: pnpm run version-packages
commit: 'Version packages'
title: 'Version packages'
+67 -56
View File
@@ -1,4 +1,4 @@
name: Test
name: Repository CI
on:
pull_request:
@@ -12,95 +12,106 @@ concurrency:
jobs:
build:
name: Build
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# https://github.com/actions/setup-node/issues/531#issuecomment-2960522861
- name: Install Node.js
uses: actions/setup-node@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v6
with:
node-version-file: package.json
- name: Enable Corepack
run: corepack enable
- name: Configure Dependency Cache
uses: actions/setup-node@v4
with:
cache: 'pnpm'
- name: Get current month
run: echo "CURRENT_MONTH=$(date +'%Y-%m')" >> $GITHUB_ENV
- uses: actions/cache@v4
name: Cache Puppeteer browser binaries
with:
path: ~/.cache
key: ${{ env.CURRENT_MONTH }}-${{ runner.os }}
- run: pnpm i --frozen-lockfile
cache: pnpm
node-version-file: '.nvmrc'
- run: pnpm install --frozen-lockfile
env:
PUPPETEER_SKIP_DOWNLOAD: true
- run: pnpm build
- uses: actions/upload-artifact@v4
with:
name: dist
retention-days: 2
path: |
packages/*/dist
packages/*/*/dist
packages/lex/*/src/lexicons
packages/lex/*/tests/lexicons
packages/oauth/oauth-client-browser-example/src/lexicons
packages/oauth/*/src/locales/*/messages.ts
retention-days: 1
packages/api/src/moderation/const/labels.ts
packages/bsky/src/lexicons
packages/pds/src/lexicons
packages/sync/src/lexicons
changeset:
name: Changeset
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # needed for git diff against base branch
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v6
with:
cache: pnpm
node-version-file: '.nvmrc'
- run: pnpm install --frozen-lockfile
env:
PUPPETEER_SKIP_DOWNLOAD: true
- run: pnpm changeset status --since=origin/${{ github.base_ref }}
test:
name: Test
needs: build
runs-on: ubuntu-22.04
# Puppeteer does not work in recent Ubuntu versions without a workaround due
# to sandboxing issues. Using "ubuntu-latest" results in the following
# error:
#
# No usable sandbox! If you are running on Ubuntu 23.10+ or another Linux
# distro that has disabled unprivileged user namespaces with AppArmor, see
# https://chromium.googlesource.com/chromium/src/+/main/docs/security/apparmor-userns-restrictions.md.
# Otherwise see
# https://chromium.googlesource.com/chromium/src/+/main/docs/linux/suid_sandbox_development.md
# for more information on developing with the (older) SUID sandbox. If you
# want to live dangerously and need an immediate workaround, you can try
# using --no-sandbox.
strategy:
fail-fast: false
matrix:
shard: [1/8, 2/8, 3/8, 4/8, 5/8, 6/8, 7/8, 8/8]
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
# https://github.com/actions/setup-node/issues/531#issuecomment-2960522861
- name: Install Node.js
uses: actions/setup-node@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v6
with:
node-version-file: package.json
- name: Enable Corepack
run: corepack enable
- name: Configure Dependency Cache
uses: actions/setup-node@v4
with:
cache: 'pnpm'
- name: Get current month
run: echo "CURRENT_MONTH=$(date +'%Y-%m')" >> $GITHUB_ENV
cache: pnpm
node-version-file: '.nvmrc'
- run: echo "CURRENT_MONTH=$(date +'%Y-%m')" >> $GITHUB_ENV
- uses: actions/cache@v4
name: Cache Puppeteer browser binaries
with:
path: ~/.cache
key: ${{ env.CURRENT_MONTH }}-${{ runner.os }}
- run: pnpm i --frozen-lockfile
path: ~/.cache/puppeteer
key: ${{ env.CURRENT_MONTH }}-${{ runner.os }}-${{ runner.arch }}
- run: pnpm install --frozen-lockfile
- uses: actions/download-artifact@v4
with:
name: dist
path: packages
- run: pnpm test:withFlags --maxWorkers=1 --shard=${{ matrix.shard }} --passWithNoTests
verify:
name: Verify
needs: build
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# https://github.com/actions/setup-node/issues/531#issuecomment-2960522861
- name: Install Node.js
uses: actions/setup-node@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v6
with:
node-version-file: package.json
- name: Enable Corepack
run: corepack enable
- name: Configure Dependency Cache
uses: actions/setup-node@v4
with:
cache: 'pnpm'
- name: Get current month
run: echo "CURRENT_MONTH=$(date +'%Y-%m')" >> $GITHUB_ENV
- uses: actions/cache@v4
name: Cache Puppeteer browser binaries
with:
path: ~/.cache
key: ${{ env.CURRENT_MONTH }}-${{ runner.os }}
- run: pnpm i --frozen-lockfile
cache: pnpm
node-version-file: '.nvmrc'
- run: pnpm install --frozen-lockfile
env:
PUPPETEER_SKIP_DOWNLOAD: true
- uses: actions/download-artifact@v4
with:
name: dist
+2
View File
@@ -15,3 +15,5 @@ test.sqlite
\#*\#
*~
*.swp
.claude/
coverage
+1 -1
View File
@@ -1 +1 @@
18
22
+17 -6
View File
@@ -7,13 +7,24 @@ pnpm-lock.yaml
.changeset
CHANGELOG.md
# Prettier is used to format the code during codegen
# buf
packages/bsky/src/proto
packages/bsync/src/proto
# codegen
packages/api/src/client
packages/lexicon-resolver/src/client
packages/bsky/src/lexicon
packages/pds/src/lexicon
packages/ozone/src/lexicon
# Automatically generated by lingui
# @atproto/lex
packages/lexicon-resolver/src/lexicons
packages/lex/*/src/lexicons
packages/lex/*/tests/lexicons
packages/oauth/oauth-client-browser-example/src/lexicons
packages/pds/src/lexicons
packages/bsky/src/lexicons
packages/sync/src/lexicons
# others
packages/api/src/moderation/const/labels.ts
packages/oauth/*/src/locales/*/messages.ts
packages/oauth/oauth-provider-frontend/src/routeTree.gen.ts
packages/oauth/oauth-client-expo/android/build
+2 -1
View File
@@ -3,6 +3,7 @@
"dbaeumer.vscode-eslint",
"wengerk.highlight-bad-chars",
"esbenp.prettier-vscode",
"streetsidesoftware.code-spell-checker"
"streetsidesoftware.code-spell-checker",
"vitest.explorer"
]
}
+3 -1
View File
@@ -1,6 +1,6 @@
{
"cSpell.language": "en,en-US",
"cSpell.userWords": [
"cSpell.words": [
"algs",
"appview",
"atproto",
@@ -8,6 +8,7 @@
"bluesky",
"bsky",
"bsync",
"cbor",
"clsx",
"consolas",
"dpop",
@@ -16,6 +17,7 @@
"hexeditor",
"ingester",
"insertable",
"ipld",
"jwks",
"keypair",
"kysely",
+2
View File
@@ -23,3 +23,5 @@ ATProto receives so many contributions that we could never list everyone who des
#### [daniel](https://hackerone.com/daniel), Security disclosure, November 2024
#### [imax](https://github.com/imax9000), Security disclosure, January 2025
#### [avivkeller](https://github.com/avivkeller), Security disclosure, December 2025
+1 -1
View File
@@ -1,6 +1,6 @@
Dual MIT/Apache-2.0 License
Copyright (c) 2022-2025 Bluesky Social PBC, and Contributors
Copyright (c) 2022-2026 Bluesky Social PBC, and Contributors
Except as otherwise noted in individual files, this software is licensed under the MIT license (<http://opensource.org/licenses/MIT>), or the Apache License, Version 2.0 (<http://www.apache.org/licenses/LICENSE-2.0>).
+5 -1
View File
@@ -11,7 +11,7 @@ help: ## Print info about all commands
@echo "NOTE: dependencies between commands are not automatic. Eg, you must run 'deps' and 'build' first, and after any changes"
.PHONY: build
build: ## Compile all modules
build: codegen ## Compile all modules
pnpm build
.PHONY: test
@@ -46,6 +46,10 @@ fmt-lexicons: ## Run syntax re-formatting, just on .json files
deps: ## Installs dependent libs using 'pnpm install'
pnpm install --frozen-lockfile
.PHONY: clean
clean: ## Deletes all 'dist' and 'node_package' directories (including nested)
rm -rf **/dist **/node_packages
.PHONY: nvm-setup
nvm-setup: ## Use NVM to install and activate node+pnpm
nvm install 18
+2
View File
@@ -113,3 +113,5 @@ This project is dual-licensed under MIT and Apache 2.0 terms:
- Apache License, Version 2.0, ([LICENSE-APACHE.txt](https://github.com/bluesky-social/atproto/blob/main/LICENSE-APACHE.txt) or http://www.apache.org/licenses/LICENSE-2.0)
Downstream projects and end users may chose either license individually, or both together, at their discretion. The motivation for this dual-licensing is the additional software patent assurance provided by Apache 2.0.
Bluesky Social PBC has committed to a software patent non-aggression pledge. For details see [the original announcement](https://bsky.social/about/blog/10-01-2025-patent-pledge).
+83 -6
View File
@@ -13,6 +13,7 @@
"maxGraphemes": 64,
"maxLength": 640
},
"pronouns": { "type": "string" },
"avatar": { "type": "string", "format": "uri" },
"associated": {
"type": "ref",
@@ -31,6 +32,10 @@
"status": {
"type": "ref",
"ref": "#statusView"
},
"debug": {
"type": "unknown",
"description": "Debug information for internal development"
}
}
},
@@ -45,6 +50,7 @@
"maxGraphemes": 64,
"maxLength": 640
},
"pronouns": { "type": "string" },
"description": {
"type": "string",
"maxGraphemes": 256,
@@ -69,6 +75,10 @@
"status": {
"type": "ref",
"ref": "#statusView"
},
"debug": {
"type": "unknown",
"description": "Debug information for internal development"
}
}
},
@@ -88,6 +98,8 @@
"maxGraphemes": 256,
"maxLength": 2560
},
"pronouns": { "type": "string" },
"website": { "type": "string", "format": "uri" },
"avatar": { "type": "string", "format": "uri" },
"banner": { "type": "string", "format": "uri" },
"followersCount": { "type": "integer" },
@@ -119,6 +131,10 @@
"status": {
"type": "ref",
"ref": "#statusView"
},
"debug": {
"type": "unknown",
"description": "Debug information for internal development"
}
}
},
@@ -133,7 +149,8 @@
"activitySubscription": {
"type": "ref",
"ref": "#profileAssociatedActivitySubscription"
}
},
"germ": { "type": "ref", "ref": "#profileAssociatedGerm" }
}
},
"profileAssociatedChat": {
@@ -143,6 +160,24 @@
"allowIncoming": {
"type": "string",
"knownValues": ["all", "none", "following"]
},
"allowGroupInvites": {
"type": "string",
"knownValues": ["all", "none", "following"]
}
}
},
"profileAssociatedGerm": {
"type": "object",
"required": ["showButtonTo", "messageMeUrl"],
"properties": {
"messageMeUrl": {
"type": "string",
"format": "uri"
},
"showButtonTo": {
"type": "string",
"knownValues": ["usersIFollow", "everyone"]
}
}
},
@@ -260,6 +295,7 @@
"#savedFeedsPref",
"#savedFeedsPrefV2",
"#personalDetailsPref",
"#declaredAgePref",
"#feedViewPref",
"#threadViewPref",
"#interestsPref",
@@ -268,7 +304,8 @@
"#bskyAppStatePref",
"#labelersPref",
"#postInteractionSettingsPref",
"#verificationPrefs"
"#verificationPrefs",
"#liveEventPreferences"
]
}
},
@@ -360,6 +397,24 @@
}
}
},
"declaredAgePref": {
"type": "object",
"description": "Read-only preference containing value(s) inferred from the user's declared birthdate. Absence of this preference object in the response indicates that the user has not made a declaration.",
"properties": {
"isOverAge13": {
"type": "boolean",
"description": "Indicates if the user has declared that they are over 13 years of age."
},
"isOverAge16": {
"type": "boolean",
"description": "Indicates if the user has declared that they are over 16 years of age."
},
"isOverAge18": {
"type": "boolean",
"description": "Indicates if the user has declared that they are over 18 years of age."
}
}
},
"feedViewPref": {
"type": "object",
"required": ["feed"],
@@ -398,10 +453,6 @@
"type": "string",
"description": "Sorting mode for threads.",
"knownValues": ["oldest", "newest", "most-likes", "random", "hotness"]
},
"prioritizeFollowedUsers": {
"type": "boolean",
"description": "Show followed users at the top of all replies."
}
}
},
@@ -575,6 +626,22 @@
}
}
},
"liveEventPreferences": {
"type": "object",
"description": "Preferences for live events.",
"properties": {
"hiddenFeedIds": {
"description": "A list of feed IDs that the user has hidden from live events.",
"type": "array",
"items": { "type": "string" }
},
"hideAllFeeds": {
"description": "Whether to hide all feeds from live events.",
"type": "boolean",
"default": false
}
}
},
"postInteractionSettingsPref": {
"type": "object",
"description": "Default post interaction settings for the account. These values should be applied as default values when creating new posts. These refs should mirror the threadgate and postgate records exactly.",
@@ -609,6 +676,8 @@
"type": "object",
"required": ["status", "record"],
"properties": {
"uri": { "type": "string", "format": "at-uri" },
"cid": { "type": "string", "format": "cid" },
"status": {
"type": "string",
"description": "The status for the account.",
@@ -620,6 +689,10 @@
"description": "An optional embed associated with the status.",
"refs": ["app.bsky.embed.external#view"]
},
"labels": {
"type": "array",
"items": { "type": "ref", "ref": "com.atproto.label.defs#label" }
},
"expiresAt": {
"type": "string",
"description": "The date when this status will expire. The application might choose to no longer return the status after expiration.",
@@ -628,6 +701,10 @@
"isActive": {
"type": "boolean",
"description": "True if the status is not expired, false if it is expired. Only present if expiration was set."
},
"isDisabled": {
"type": "boolean",
"description": "True if the user's go-live access has been disabled by a moderator, false otherwise."
}
}
}
@@ -33,6 +33,10 @@
},
"recId": {
"type": "integer",
"description": "DEPRECATED: use recIdStr instead."
},
"recIdStr": {
"type": "string",
"description": "Snowflake for this recommendation, use when submitting recommendation events."
}
}
+7
View File
@@ -20,6 +20,13 @@
"maxGraphemes": 256,
"maxLength": 2560
},
"pronouns": {
"type": "string",
"description": "Free-form pronouns text.",
"maxGraphemes": 20,
"maxLength": 200
},
"website": { "type": "string", "format": "uri" },
"avatar": {
"type": "blob",
"description": "Small image to be displayed next to posts from account. AKA, 'profile picture'",
+48
View File
@@ -0,0 +1,48 @@
{
"lexicon": 1,
"id": "app.bsky.ageassurance.begin",
"defs": {
"main": {
"type": "procedure",
"description": "Initiate Age Assurance for an account.",
"input": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["email", "language", "countryCode"],
"properties": {
"email": {
"type": "string",
"description": "The user's email address to receive Age Assurance instructions."
},
"language": {
"type": "string",
"description": "The user's preferred language for communication during the Age Assurance process."
},
"countryCode": {
"type": "string",
"description": "An ISO 3166-1 alpha-2 code of the user's location."
},
"regionCode": {
"type": "string",
"description": "An optional ISO 3166-2 code of the user's region or state within the country."
}
}
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "ref",
"ref": "app.bsky.ageassurance.defs#state"
}
},
"errors": [
{ "name": "InvalidEmail" },
{ "name": "DidTooLong" },
{ "name": "InvalidInitiation" },
{ "name": "RegionNotSupported" }
]
}
}
}
+255
View File
@@ -0,0 +1,255 @@
{
"lexicon": 1,
"id": "app.bsky.ageassurance.defs",
"defs": {
"access": {
"description": "The access level granted based on Age Assurance data we've processed.",
"type": "string",
"knownValues": ["unknown", "none", "safe", "full"]
},
"status": {
"type": "string",
"description": "The status of the Age Assurance process.",
"knownValues": ["unknown", "pending", "assured", "blocked"]
},
"state": {
"type": "object",
"description": "The user's computed Age Assurance state.",
"required": ["status", "access"],
"properties": {
"lastInitiatedAt": {
"type": "string",
"format": "datetime",
"description": "The timestamp when this state was last updated."
},
"status": {
"type": "ref",
"ref": "app.bsky.ageassurance.defs#status"
},
"access": {
"type": "ref",
"ref": "app.bsky.ageassurance.defs#access"
}
}
},
"stateMetadata": {
"type": "object",
"description": "Additional metadata needed to compute Age Assurance state client-side.",
"required": [],
"properties": {
"accountCreatedAt": {
"type": "string",
"format": "datetime",
"description": "The account creation timestamp."
}
}
},
"config": {
"type": "object",
"description": "",
"required": ["regions"],
"properties": {
"regions": {
"type": "array",
"description": "The per-region Age Assurance configuration.",
"items": {
"type": "ref",
"ref": "app.bsky.ageassurance.defs#configRegion"
}
}
}
},
"configRegion": {
"type": "object",
"description": "The Age Assurance configuration for a specific region.",
"required": ["countryCode", "minAccessAge", "rules"],
"properties": {
"countryCode": {
"type": "string",
"description": "The ISO 3166-1 alpha-2 country code this configuration applies to."
},
"regionCode": {
"type": "string",
"description": "The ISO 3166-2 region code this configuration applies to. If omitted, the configuration applies to the entire country."
},
"minAccessAge": {
"type": "integer",
"description": "The minimum age (as a whole integer) required to use Bluesky in this region."
},
"rules": {
"type": "array",
"description": "The ordered list of Age Assurance rules that apply to this region. Rules should be applied in order, and the first matching rule determines the access level granted. The rules array should always include a default rule as the last item.",
"items": {
"type": "union",
"refs": [
"#configRegionRuleDefault",
"#configRegionRuleIfDeclaredOverAge",
"#configRegionRuleIfDeclaredUnderAge",
"#configRegionRuleIfAssuredOverAge",
"#configRegionRuleIfAssuredUnderAge",
"#configRegionRuleIfAccountNewerThan",
"#configRegionRuleIfAccountOlderThan"
]
}
}
}
},
"configRegionRuleDefault": {
"type": "object",
"description": "Age Assurance rule that applies by default.",
"required": ["access"],
"properties": {
"access": {
"type": "ref",
"ref": "app.bsky.ageassurance.defs#access"
}
}
},
"configRegionRuleIfDeclaredOverAge": {
"type": "object",
"description": "Age Assurance rule that applies if the user has declared themselves equal-to or over a certain age.",
"required": ["age", "access"],
"properties": {
"age": {
"type": "integer",
"description": "The age threshold as a whole integer."
},
"access": {
"type": "ref",
"ref": "app.bsky.ageassurance.defs#access"
}
}
},
"configRegionRuleIfDeclaredUnderAge": {
"type": "object",
"description": "Age Assurance rule that applies if the user has declared themselves under a certain age.",
"required": ["age", "access"],
"properties": {
"age": {
"type": "integer",
"description": "The age threshold as a whole integer."
},
"access": {
"type": "ref",
"ref": "app.bsky.ageassurance.defs#access"
}
}
},
"configRegionRuleIfAssuredOverAge": {
"type": "object",
"description": "Age Assurance rule that applies if the user has been assured to be equal-to or over a certain age.",
"required": ["age", "access"],
"properties": {
"age": {
"type": "integer",
"description": "The age threshold as a whole integer."
},
"access": {
"type": "ref",
"ref": "app.bsky.ageassurance.defs#access"
}
}
},
"configRegionRuleIfAssuredUnderAge": {
"type": "object",
"description": "Age Assurance rule that applies if the user has been assured to be under a certain age.",
"required": ["age", "access"],
"properties": {
"age": {
"type": "integer",
"description": "The age threshold as a whole integer."
},
"access": {
"type": "ref",
"ref": "app.bsky.ageassurance.defs#access"
}
}
},
"configRegionRuleIfAccountNewerThan": {
"type": "object",
"description": "Age Assurance rule that applies if the account is equal-to or newer than a certain date.",
"required": ["date", "access"],
"properties": {
"date": {
"type": "string",
"format": "datetime",
"description": "The date threshold as a datetime string."
},
"access": {
"type": "ref",
"ref": "app.bsky.ageassurance.defs#access"
}
}
},
"configRegionRuleIfAccountOlderThan": {
"type": "object",
"description": "Age Assurance rule that applies if the account is older than a certain date.",
"required": ["date", "access"],
"properties": {
"date": {
"type": "string",
"format": "datetime",
"description": "The date threshold as a datetime string."
},
"access": {
"type": "ref",
"ref": "app.bsky.ageassurance.defs#access"
}
}
},
"event": {
"type": "object",
"description": "Object used to store Age Assurance data in stash.",
"required": ["createdAt", "status", "access", "attemptId", "countryCode"],
"properties": {
"createdAt": {
"type": "string",
"format": "datetime",
"description": "The date and time of this write operation."
},
"attemptId": {
"type": "string",
"description": "The unique identifier for this instance of the Age Assurance flow, in UUID format."
},
"status": {
"type": "string",
"description": "The status of the Age Assurance process.",
"knownValues": ["unknown", "pending", "assured", "blocked"]
},
"access": {
"description": "The access level granted based on Age Assurance data we've processed.",
"type": "string",
"knownValues": ["unknown", "none", "safe", "full"]
},
"countryCode": {
"type": "string",
"description": "The ISO 3166-1 alpha-2 country code provided when beginning the Age Assurance flow."
},
"regionCode": {
"type": "string",
"description": "The ISO 3166-2 region code provided when beginning the Age Assurance flow."
},
"email": {
"type": "string",
"description": "The email used for Age Assurance."
},
"initIp": {
"type": "string",
"description": "The IP address used when initiating the Age Assurance flow."
},
"initUa": {
"type": "string",
"description": "The user agent used when initiating the Age Assurance flow."
},
"completeIp": {
"type": "string",
"description": "The IP address used when completing the Age Assurance flow."
},
"completeUa": {
"type": "string",
"description": "The user agent used when completing the Age Assurance flow."
}
}
}
}
}
@@ -0,0 +1,17 @@
{
"lexicon": 1,
"id": "app.bsky.ageassurance.getConfig",
"defs": {
"main": {
"type": "query",
"description": "Returns Age Assurance configuration for use on the client.",
"output": {
"encoding": "application/json",
"schema": {
"type": "ref",
"ref": "app.bsky.ageassurance.defs#config"
}
}
}
}
}
@@ -0,0 +1,35 @@
{
"lexicon": 1,
"id": "app.bsky.ageassurance.getState",
"defs": {
"main": {
"type": "query",
"description": "Returns server-computed Age Assurance state, if available, and any additional metadata needed to compute Age Assurance state client-side.",
"parameters": {
"type": "params",
"required": ["countryCode"],
"properties": {
"countryCode": { "type": "string" },
"regionCode": { "type": "string" }
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["state", "metadata"],
"properties": {
"state": {
"type": "ref",
"ref": "app.bsky.ageassurance.defs#state"
},
"metadata": {
"type": "ref",
"ref": "app.bsky.ageassurance.defs#stateMetadata"
}
}
}
}
}
}
}
+35
View File
@@ -0,0 +1,35 @@
{
"lexicon": 1,
"id": "app.bsky.authCreatePosts",
"defs": {
"main": {
"type": "permission-set",
"title": "Create Bluesky Posts",
"title:lang": {},
"detail": "Can not update or delete posts.",
"detail:lang": {},
"permissions": [
{
"type": "permission",
"resource": "rpc",
"inheritAud": true,
"lxm": [
"app.bsky.video.uploadVideo",
"app.bsky.video.getJobStatus",
"app.bsky.video.getUploadLimits"
]
},
{
"type": "permission",
"resource": "repo",
"action": ["create"],
"collection": [
"app.bsky.feed.post",
"app.bsky.feed.postgate",
"app.bsky.feed.threadgate"
]
}
]
}
}
}
+27
View File
@@ -0,0 +1,27 @@
{
"lexicon": 1,
"id": "app.bsky.authDeleteContent",
"defs": {
"main": {
"type": "permission-set",
"title": "Delete Bluesky Content",
"title:lang": {},
"detail": "Clean up public account history: posts, reposts, and likes.",
"detail:lang": {},
"permissions": [
{
"type": "permission",
"resource": "repo",
"action": ["delete"],
"collection": [
"app.bsky.feed.like",
"app.bsky.feed.post",
"app.bsky.feed.postgate",
"app.bsky.feed.repost",
"app.bsky.feed.threadgate"
]
}
]
}
}
}
+136
View File
@@ -0,0 +1,136 @@
{
"lexicon": 1,
"id": "app.bsky.authFullApp",
"defs": {
"main": {
"type": "permission-set",
"title": "Full Bluesky Social App Permissions",
"title:lang": {},
"detail": "Manage all public content and interactions, private preferences and subscriptions, and other Bluesky-specific app features and data.",
"detail:lang": {},
"permissions": [
{
"type": "permission",
"resource": "rpc",
"inheritAud": true,
"lxm": [
"app.bsky.actor.getPreferences",
"app.bsky.actor.getProfile",
"app.bsky.actor.getProfiles",
"app.bsky.actor.getSuggestions",
"app.bsky.actor.putPreferences",
"app.bsky.actor.searchActors",
"app.bsky.actor.searchActorsTypeahead",
"app.bsky.bookmark.createBookmark",
"app.bsky.bookmark.deleteBookmark",
"app.bsky.bookmark.getBookmarks",
"app.bsky.contact.dismissMatch",
"app.bsky.contact.getMatches",
"app.bsky.contact.getSyncStatus",
"app.bsky.contact.importContacts",
"app.bsky.contact.removeData",
"app.bsky.contact.startPhoneVerification",
"app.bsky.contact.verifyPhone",
"app.bsky.feed.describeFeedGenerator",
"app.bsky.feed.getActorFeeds",
"app.bsky.feed.getActorLikes",
"app.bsky.feed.getAuthorFeed",
"app.bsky.feed.getFeed",
"app.bsky.feed.getFeedGenerator",
"app.bsky.feed.getFeedGenerators",
"app.bsky.feed.getFeedSkeleton",
"app.bsky.feed.getLikes",
"app.bsky.feed.getListFeed",
"app.bsky.feed.getPostThread",
"app.bsky.feed.getPosts",
"app.bsky.feed.getQuotes",
"app.bsky.feed.getRepostedBy",
"app.bsky.feed.getSuggestedFeeds",
"app.bsky.feed.getTimeline",
"app.bsky.feed.searchPosts",
"app.bsky.feed.sendInteractions",
"app.bsky.graph.getActorStarterPacks",
"app.bsky.graph.getBlocks",
"app.bsky.graph.getFollowers",
"app.bsky.graph.getFollows",
"app.bsky.graph.getKnownFollowers",
"app.bsky.graph.getList",
"app.bsky.graph.getListBlocks",
"app.bsky.graph.getListMutes",
"app.bsky.graph.getLists",
"app.bsky.graph.getListsWithMembership",
"app.bsky.graph.getMutes",
"app.bsky.graph.getRelationships",
"app.bsky.graph.getStarterPack",
"app.bsky.graph.getStarterPacks",
"app.bsky.graph.getStarterPacksWithMembership",
"app.bsky.graph.getSuggestedFollowsByActor",
"app.bsky.graph.muteActor",
"app.bsky.graph.muteActorList",
"app.bsky.graph.muteThread",
"app.bsky.graph.searchStarterPacks",
"app.bsky.graph.unmuteActor",
"app.bsky.graph.unmuteActorList",
"app.bsky.graph.unmuteThread",
"app.bsky.labeler.getServices",
"app.bsky.notification.getPreferences",
"app.bsky.notification.getUnreadCount",
"app.bsky.notification.listActivitySubscriptions",
"app.bsky.notification.listNotifications",
"app.bsky.notification.putActivitySubscription",
"app.bsky.notification.putPreferences",
"app.bsky.notification.putPreferencesV2",
"app.bsky.notification.registerPush",
"app.bsky.notification.unregisterPush",
"app.bsky.notification.updateSeen",
"app.bsky.unspecced.getAgeAssuranceState",
"app.bsky.unspecced.getConfig",
"app.bsky.unspecced.getOnboardingSuggestedStarterPacks",
"app.bsky.unspecced.getPopularFeedGenerators",
"app.bsky.unspecced.getPostThreadOtherV2",
"app.bsky.unspecced.getPostThreadV2",
"app.bsky.unspecced.getSuggestedFeeds",
"app.bsky.unspecced.getSuggestedFeedsSkeleton",
"app.bsky.unspecced.getSuggestedStarterPacks",
"app.bsky.unspecced.getSuggestedStarterPacksSkeleton",
"app.bsky.unspecced.getSuggestedUsers",
"app.bsky.unspecced.getSuggestedUsersSkeleton",
"app.bsky.unspecced.getSuggestionsSkeleton",
"app.bsky.unspecced.getTaggedSuggestions",
"app.bsky.unspecced.getTrendingTopics",
"app.bsky.unspecced.getTrends",
"app.bsky.unspecced.getTrendsSkeleton",
"app.bsky.unspecced.initAgeAssurance",
"app.bsky.unspecced.searchActorsSkeleton",
"app.bsky.unspecced.searchPostsSkeleton",
"app.bsky.unspecced.searchStarterPacksSkeleton",
"app.bsky.video.getJobStatus",
"app.bsky.video.getUploadLimits",
"app.bsky.video.uploadVideo"
]
},
{
"type": "permission",
"resource": "repo",
"action": ["create", "update", "delete"],
"collection": [
"app.bsky.actor.profile",
"app.bsky.actor.status",
"app.bsky.feed.like",
"app.bsky.feed.post",
"app.bsky.feed.postgate",
"app.bsky.feed.repost",
"app.bsky.feed.threadgate",
"app.bsky.graph.block",
"app.bsky.graph.follow",
"app.bsky.graph.list",
"app.bsky.graph.listblock",
"app.bsky.graph.listitem",
"app.bsky.graph.starterpack",
"app.bsky.notification.declaration"
]
}
]
}
}
}
@@ -0,0 +1,21 @@
{
"lexicon": 1,
"id": "app.bsky.authManageFeedDeclarations",
"defs": {
"main": {
"type": "permission-set",
"title": "Manage Hosted Feeds",
"title:lang": {},
"detail": "Configure feed generator declaration records.",
"detail:lang": {},
"permissions": [
{
"type": "permission",
"resource": "repo",
"action": ["create", "update", "delete"],
"collection": ["app.bsky.feed.generator"]
}
]
}
}
}
@@ -0,0 +1,21 @@
{
"lexicon": 1,
"id": "app.bsky.authManageLabelerService",
"defs": {
"main": {
"type": "permission-set",
"title": "Manage Hosted Labeling Service",
"title:lang": {},
"detail": "Configure labeler declaration records.",
"detail:lang": {},
"permissions": [
{
"type": "permission",
"resource": "repo",
"action": ["create", "update", "delete"],
"collection": ["app.bsky.labeler.service"]
}
]
}
}
}
@@ -0,0 +1,36 @@
{
"lexicon": 1,
"id": "app.bsky.authManageModeration",
"defs": {
"main": {
"type": "permission-set",
"title": "Manage Personal Moderation",
"title:lang": {},
"detail": "Control over blocks, mutes, mod lists, mod services, and preferences.",
"detail:lang": {},
"permissions": [
{
"type": "permission",
"resource": "rpc",
"inheritAud": true,
"lxm": [
"app.bsky.actor.getPreferences",
"app.bsky.actor.putPreferences",
"app.bsky.graph.muteActor",
"app.bsky.graph.muteActorList",
"app.bsky.graph.muteThread",
"app.bsky.graph.unmuteActor",
"app.bsky.graph.unmuteActorList",
"app.bsky.graph.unmuteThread"
]
},
{
"type": "permission",
"resource": "repo",
"action": ["create", "update", "delete"],
"collection": ["app.bsky.graph.block", "app.bsky.graph.listblock"]
}
]
}
}
}
@@ -0,0 +1,32 @@
{
"lexicon": 1,
"id": "app.bsky.authManageNotifications",
"defs": {
"main": {
"type": "permission-set",
"title": "Manage Bluesky Notifications",
"title:lang": {},
"detail": "View and configure notifications for the Bluesky app.",
"detail:lang": {},
"permissions": [
{
"type": "permission",
"resource": "rpc",
"inheritAud": true,
"lxm": [
"app.bsky.notification.getPreferences",
"app.bsky.notification.getUnreadCount",
"app.bsky.notification.listActivitySubscriptions",
"app.bsky.notification.listNotifications",
"app.bsky.notification.putActivitySubscription",
"app.bsky.notification.putPreferences",
"app.bsky.notification.putPreferencesV2",
"app.bsky.notification.registerPush",
"app.bsky.notification.unregisterPush",
"app.bsky.notification.updateSeen"
]
}
]
}
}
}
+25
View File
@@ -0,0 +1,25 @@
{
"lexicon": 1,
"id": "app.bsky.authManageProfile",
"defs": {
"main": {
"type": "permission-set",
"title": "Manage Bluesky Profile",
"title:lang": {},
"detail": "Update profile data, as well as status and public chat visibility.",
"detail:lang": {},
"permissions": [
{
"type": "permission",
"resource": "repo",
"action": ["create", "update", "delete"],
"collection": [
"app.bsky.actor.profile",
"app.bsky.actor.status",
"app.bsky.notification.declaration"
]
}
]
}
}
}
+89
View File
@@ -0,0 +1,89 @@
{
"lexicon": 1,
"id": "app.bsky.authViewAll",
"defs": {
"main": {
"type": "permission-set",
"title": "Read-only access to all content",
"title:lang": {},
"detail": "View Bluesky network content from account perspective, and read all notifications and preferences.",
"detail:lang": {},
"permissions": [
{
"type": "permission",
"resource": "rpc",
"inheritAud": true,
"lxm": [
"app.bsky.actor.getPreferences",
"app.bsky.actor.getProfile",
"app.bsky.actor.getProfiles",
"app.bsky.actor.getSuggestions",
"app.bsky.actor.searchActors",
"app.bsky.actor.searchActorsTypeahead",
"app.bsky.bookmark.getBookmarks",
"app.bsky.feed.describeFeedGenerator",
"app.bsky.feed.getActorFeeds",
"app.bsky.feed.getActorLikes",
"app.bsky.feed.getAuthorFeed",
"app.bsky.feed.getFeed",
"app.bsky.feed.getFeedGenerator",
"app.bsky.feed.getFeedGenerators",
"app.bsky.feed.getFeedSkeleton",
"app.bsky.feed.getLikes",
"app.bsky.feed.getListFeed",
"app.bsky.feed.getPostThread",
"app.bsky.feed.getPosts",
"app.bsky.feed.getQuotes",
"app.bsky.feed.getRepostedBy",
"app.bsky.feed.getSuggestedFeeds",
"app.bsky.feed.getTimeline",
"app.bsky.feed.searchPosts",
"app.bsky.graph.getActorStarterPacks",
"app.bsky.graph.getBlocks",
"app.bsky.graph.getFollowers",
"app.bsky.graph.getFollows",
"app.bsky.graph.getKnownFollowers",
"app.bsky.graph.getListBlocks",
"app.bsky.graph.getListMutes",
"app.bsky.graph.getLists",
"app.bsky.graph.getListsWithMembership",
"app.bsky.graph.getMutes",
"app.bsky.graph.getRelationships",
"app.bsky.graph.getStarterPack",
"app.bsky.graph.getStarterPacks",
"app.bsky.graph.getStarterPacksWithMembership",
"app.bsky.graph.getSuggestedFollowsByActor",
"app.bsky.graph.searchStarterPacks",
"app.bsky.labeler.getServices",
"app.bsky.notification.getPreferences",
"app.bsky.notification.getUnreadCount",
"app.bsky.notification.listActivitySubscriptions",
"app.bsky.notification.listNotifications",
"app.bsky.notification.updateSeen",
"app.bsky.unspecced.getAgeAssuranceState",
"app.bsky.unspecced.getConfig",
"app.bsky.unspecced.getOnboardingSuggestedStarterPacks",
"app.bsky.unspecced.getPopularFeedGenerators",
"app.bsky.unspecced.getPostThreadOtherV2",
"app.bsky.unspecced.getPostThreadV2",
"app.bsky.unspecced.getSuggestedFeeds",
"app.bsky.unspecced.getSuggestedFeedsSkeleton",
"app.bsky.unspecced.getSuggestedStarterPacks",
"app.bsky.unspecced.getSuggestedStarterPacksSkeleton",
"app.bsky.unspecced.getSuggestedUsers",
"app.bsky.unspecced.getSuggestedUsersSkeleton",
"app.bsky.unspecced.getSuggestionsSkeleton",
"app.bsky.unspecced.getTaggedSuggestions",
"app.bsky.unspecced.getTrendingTopics",
"app.bsky.unspecced.getTrends",
"app.bsky.unspecced.getTrendsSkeleton",
"app.bsky.unspecced.searchActorsSkeleton",
"app.bsky.unspecced.searchPostsSkeleton",
"app.bsky.unspecced.searchStarterPacksSkeleton",
"app.bsky.video.getUploadLimits"
]
}
]
}
}
}
+57
View File
@@ -0,0 +1,57 @@
{
"lexicon": 1,
"id": "app.bsky.contact.defs",
"defs": {
"matchAndContactIndex": {
"description": "Associates a profile with the positional index of the contact import input in the call to `app.bsky.contact.importContacts`, so clients can know which phone caused a particular match.",
"type": "object",
"required": ["match", "contactIndex"],
"properties": {
"match": {
"description": "Profile of the matched user.",
"type": "ref",
"ref": "app.bsky.actor.defs#profileView"
},
"contactIndex": {
"description": "The index of this match in the import contact input.",
"type": "integer",
"minimum": 0,
"maximum": 999
}
}
},
"syncStatus": {
"type": "object",
"required": ["syncedAt", "matchesCount"],
"properties": {
"syncedAt": {
"description": "Last date when contacts where imported.",
"type": "string",
"format": "datetime"
},
"matchesCount": {
"description": "Number of existing contact matches resulting of the user imports and of their imported contacts having imported the user. Matches stop being counted when the user either follows the matched contact or dismisses the match.",
"type": "integer",
"minimum": 0
}
}
},
"notification": {
"description": "A stash object to be sent via bsync representing a notification to be created.",
"type": "object",
"required": ["from", "to"],
"properties": {
"from": {
"description": "The DID of who this notification comes from.",
"type": "string",
"format": "did"
},
"to": {
"description": "The DID of who this notification should go to.",
"type": "string",
"format": "did"
}
}
}
}
}
@@ -0,0 +1,39 @@
{
"lexicon": 1,
"id": "app.bsky.contact.dismissMatch",
"defs": {
"main": {
"type": "procedure",
"description": "Removes a match that was found via contact import. It shouldn't appear again if the same contact is re-imported. Requires authentication.",
"input": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["subject"],
"properties": {
"subject": {
"description": "The subject's DID to dismiss the match with.",
"type": "string",
"format": "did"
}
}
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"properties": {}
}
},
"errors": [
{
"name": "InvalidDid"
},
{
"name": "InternalError"
}
]
}
}
}
+53
View File
@@ -0,0 +1,53 @@
{
"lexicon": 1,
"id": "app.bsky.contact.getMatches",
"defs": {
"main": {
"type": "query",
"description": "Returns the matched contacts (contacts that were mutually imported). Excludes dismissed matches. Requires authentication.",
"parameters": {
"type": "params",
"properties": {
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 50
},
"cursor": { "type": "string" }
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["matches"],
"properties": {
"cursor": { "type": "string" },
"matches": {
"type": "array",
"items": {
"type": "ref",
"ref": "app.bsky.actor.defs#profileView"
}
}
}
}
},
"errors": [
{
"name": "InvalidDid"
},
{
"name": "InvalidLimit"
},
{
"name": "InvalidCursor"
},
{
"name": "InternalError"
}
]
}
}
}
@@ -0,0 +1,35 @@
{
"lexicon": 1,
"id": "app.bsky.contact.getSyncStatus",
"defs": {
"main": {
"type": "query",
"description": "Gets the user's current contact import status. Requires authentication.",
"parameters": {
"type": "params",
"properties": {}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"properties": {
"syncStatus": {
"description": "If present, indicates the user has imported their contacts. If not present, indicates the user never used the feature or called `app.bsky.contact.removeData` and didn't import again since.",
"type": "ref",
"ref": "app.bsky.contact.defs#syncStatus"
}
}
}
},
"errors": [
{
"name": "InvalidDid"
},
{
"name": "InternalError"
}
]
}
}
}
@@ -0,0 +1,66 @@
{
"lexicon": 1,
"id": "app.bsky.contact.importContacts",
"defs": {
"main": {
"type": "procedure",
"description": "Import contacts for securely matching with other users. This follows the protocol explained in https://docs.bsky.app/blog/contact-import-rfc. Requires authentication.",
"input": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["token", "contacts"],
"properties": {
"token": {
"description": "JWT to authenticate the call. Use the JWT received as a response to the call to `app.bsky.contact.verifyPhone`.",
"type": "string"
},
"contacts": {
"description": "List of phone numbers in global E.164 format (e.g., '+12125550123'). Phone numbers that cannot be normalized into a valid phone number will be discarded. Should not repeat the 'phone' input used in `app.bsky.contact.verifyPhone`.",
"type": "array",
"items": {
"type": "string"
},
"minLength": 1,
"maxLength": 1000
}
}
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["matchesAndContactIndexes"],
"properties": {
"matchesAndContactIndexes": {
"description": "The users that matched during import and their indexes on the input contacts, so the client can correlate with its local list.",
"type": "array",
"items": {
"type": "ref",
"ref": "app.bsky.contact.defs#matchAndContactIndex"
}
}
}
}
},
"errors": [
{
"name": "InvalidDid"
},
{
"name": "InvalidContacts"
},
{
"name": "TooManyContacts"
},
{
"name": "InvalidToken"
},
{
"name": "InternalError"
}
]
}
}
}
+32
View File
@@ -0,0 +1,32 @@
{
"lexicon": 1,
"id": "app.bsky.contact.removeData",
"defs": {
"main": {
"type": "procedure",
"description": "Removes all stored hashes used for contact matching, existing matches, and sync status. Requires authentication.",
"input": {
"encoding": "application/json",
"schema": {
"type": "object",
"properties": {}
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"properties": {}
}
},
"errors": [
{
"name": "InvalidDid"
},
{
"name": "InternalError"
}
]
}
}
}
@@ -0,0 +1,36 @@
{
"lexicon": 1,
"id": "app.bsky.contact.sendNotification",
"defs": {
"main": {
"type": "procedure",
"description": "System endpoint to send notifications related to contact imports. Requires role authentication.",
"input": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["from", "to"],
"properties": {
"from": {
"description": "The DID of who this notification comes from.",
"type": "string",
"format": "did"
},
"to": {
"description": "The DID of who this notification should go to.",
"type": "string",
"format": "did"
}
}
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"properties": {}
}
}
}
}
}
@@ -0,0 +1,44 @@
{
"lexicon": 1,
"id": "app.bsky.contact.startPhoneVerification",
"defs": {
"main": {
"type": "procedure",
"description": "Starts a phone verification flow. The phone passed will receive a code via SMS that should be passed to `app.bsky.contact.verifyPhone`. Requires authentication.",
"input": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["phone"],
"properties": {
"phone": {
"description": "The phone number to receive the code via SMS.",
"type": "string"
}
}
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"properties": {}
}
},
"errors": [
{
"name": "RateLimitExceeded"
},
{
"name": "InvalidDid"
},
{
"name": "InvalidPhone"
},
{
"name": "InternalError"
}
]
}
}
}
@@ -0,0 +1,57 @@
{
"lexicon": 1,
"id": "app.bsky.contact.verifyPhone",
"defs": {
"main": {
"type": "procedure",
"description": "Verifies control over a phone number with a code received via SMS and starts a contact import session. Requires authentication.",
"input": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["phone", "code"],
"properties": {
"phone": {
"description": "The phone number to verify. Should be the same as the one passed to `app.bsky.contact.startPhoneVerification`.",
"type": "string"
},
"code": {
"description": "The code received via SMS as a result of the call to `app.bsky.contact.startPhoneVerification`.",
"type": "string"
}
}
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["token"],
"properties": {
"token": {
"description": "JWT to be used in a call to `app.bsky.contact.importContacts`. It is only valid for a single call.",
"type": "string"
}
}
}
},
"errors": [
{
"name": "RateLimitExceeded"
},
{
"name": "InvalidDid"
},
{
"name": "InvalidPhone"
},
{
"name": "InvalidCode"
},
{
"name": "InternalError"
}
]
}
}
}
+42
View File
@@ -0,0 +1,42 @@
{
"lexicon": 1,
"id": "app.bsky.draft.createDraft",
"defs": {
"main": {
"type": "procedure",
"description": "Inserts a draft using private storage (stash). An upper limit of drafts might be enforced. Requires authentication.",
"input": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["draft"],
"properties": {
"draft": {
"type": "ref",
"ref": "app.bsky.draft.defs#draft"
}
}
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["id"],
"properties": {
"id": {
"type": "string",
"description": "The ID of the created draft."
}
}
}
},
"errors": [
{
"name": "DraftLimitReached",
"description": "Trying to insert a new draft when the limit was already reached."
}
]
}
}
}
+221
View File
@@ -0,0 +1,221 @@
{
"lexicon": 1,
"id": "app.bsky.draft.defs",
"defs": {
"draftWithId": {
"description": "A draft with an identifier, used to store drafts in private storage (stash).",
"type": "object",
"required": ["id", "draft"],
"properties": {
"id": {
"description": "A TID to be used as a draft identifier.",
"type": "string",
"format": "tid"
},
"draft": {
"type": "ref",
"ref": "#draft"
}
}
},
"draft": {
"description": "A draft containing an array of draft posts.",
"type": "object",
"required": ["posts"],
"properties": {
"deviceId": {
"type": "string",
"description": "UUIDv4 identifier of the device that created this draft.",
"maxLength": 100
},
"deviceName": {
"type": "string",
"description": "The device and/or platform on which the draft was created.",
"maxLength": 100
},
"posts": {
"description": "Array of draft posts that compose this draft.",
"type": "array",
"minLength": 1,
"maxLength": 100,
"items": {
"type": "ref",
"ref": "#draftPost"
}
},
"langs": {
"type": "array",
"description": "Indicates human language of posts primary text content.",
"maxLength": 3,
"items": { "type": "string", "format": "language" }
},
"postgateEmbeddingRules": {
"description": "Embedding rules for the postgates to be created when this draft is published.",
"type": "array",
"maxLength": 5,
"items": {
"type": "union",
"refs": ["app.bsky.feed.postgate#disableRule"]
}
},
"threadgateAllow": {
"description": "Allow-rules for the threadgate to be created when this draft is published.",
"type": "array",
"maxLength": 5,
"items": {
"type": "union",
"refs": [
"app.bsky.feed.threadgate#mentionRule",
"app.bsky.feed.threadgate#followerRule",
"app.bsky.feed.threadgate#followingRule",
"app.bsky.feed.threadgate#listRule"
]
}
}
}
},
"draftPost": {
"description": "One of the posts that compose a draft.",
"type": "object",
"required": ["text"],
"properties": {
"text": {
"type": "string",
"maxLength": 10000,
"maxGraphemes": 1000,
"description": "The primary post content. It has a higher limit than post contents to allow storing a larger text that can later be refined into smaller posts."
},
"labels": {
"type": "union",
"description": "Self-label values for this post. Effectively content warnings.",
"refs": ["com.atproto.label.defs#selfLabels"]
},
"embedImages": {
"type": "array",
"items": { "type": "ref", "ref": "#draftEmbedImage" },
"maxLength": 4
},
"embedVideos": {
"type": "array",
"items": { "type": "ref", "ref": "#draftEmbedVideo" },
"maxLength": 1
},
"embedExternals": {
"type": "array",
"items": { "type": "ref", "ref": "#draftEmbedExternal" },
"maxLength": 1
},
"embedRecords": {
"type": "array",
"items": { "type": "ref", "ref": "#draftEmbedRecord" },
"maxLength": 1
}
}
},
"draftView": {
"description": "View to present drafts data to users.",
"type": "object",
"required": ["id", "draft", "createdAt", "updatedAt"],
"properties": {
"id": {
"description": "A TID to be used as a draft identifier.",
"type": "string",
"format": "tid"
},
"draft": {
"type": "ref",
"ref": "#draft"
},
"createdAt": {
"description": "The time the draft was created.",
"type": "string",
"format": "datetime"
},
"updatedAt": {
"description": "The time the draft was last updated.",
"type": "string",
"format": "datetime"
}
}
},
"draftEmbedLocalRef": {
"type": "object",
"required": ["path"],
"properties": {
"path": {
"type": "string",
"description": "Local, on-device ref to file to be embedded. Embeds are currently device-bound for drafts.",
"minLength": 1,
"maxLength": 1024
}
}
},
"draftEmbedCaption": {
"type": "object",
"required": ["lang", "content"],
"properties": {
"lang": {
"type": "string",
"format": "language"
},
"content": {
"type": "string",
"maxLength": 10000
}
}
},
"draftEmbedImage": {
"type": "object",
"required": ["localRef"],
"properties": {
"localRef": {
"type": "ref",
"ref": "#draftEmbedLocalRef"
},
"alt": {
"type": "string",
"maxGraphemes": 2000
}
}
},
"draftEmbedVideo": {
"type": "object",
"required": ["localRef"],
"properties": {
"localRef": {
"type": "ref",
"ref": "#draftEmbedLocalRef"
},
"alt": {
"type": "string",
"maxGraphemes": 2000
},
"captions": {
"type": "array",
"items": {
"type": "ref",
"ref": "#draftEmbedCaption"
},
"maxLength": 20
}
}
},
"draftEmbedExternal": {
"type": "object",
"required": ["uri"],
"properties": {
"uri": { "type": "string", "format": "uri" }
}
},
"draftEmbedRecord": {
"type": "object",
"required": ["record"],
"properties": {
"record": { "type": "ref", "ref": "com.atproto.repo.strongRef" }
}
}
}
}
+23
View File
@@ -0,0 +1,23 @@
{
"lexicon": 1,
"id": "app.bsky.draft.deleteDraft",
"defs": {
"main": {
"type": "procedure",
"description": "Deletes a draft by ID. Requires authentication.",
"input": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["id"],
"properties": {
"id": {
"type": "string",
"format": "tid"
}
}
}
}
}
}
}
+39
View File
@@ -0,0 +1,39 @@
{
"lexicon": 1,
"id": "app.bsky.draft.getDrafts",
"defs": {
"main": {
"type": "query",
"description": "Gets views of user drafts. Requires authentication.",
"parameters": {
"type": "params",
"properties": {
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 50
},
"cursor": { "type": "string" }
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["drafts"],
"properties": {
"cursor": { "type": "string" },
"drafts": {
"type": "array",
"items": {
"type": "ref",
"ref": "app.bsky.draft.defs#draftView"
}
}
}
}
}
}
}
}
+23
View File
@@ -0,0 +1,23 @@
{
"lexicon": 1,
"id": "app.bsky.draft.updateDraft",
"defs": {
"main": {
"type": "procedure",
"description": "Updates a draft using private storage (stash). If the draft ID points to a non-existing ID, the update will be silently ignored. This is done because updates don't enforce draft limit, so it accepts all writes, but will ignore invalid ones. Requires authentication.",
"input": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["draft"],
"properties": {
"draft": {
"type": "ref",
"ref": "app.bsky.draft.defs#draftWithId"
}
}
}
}
}
}
}
+2 -1
View File
@@ -20,8 +20,9 @@
"properties": {
"image": {
"type": "blob",
"description": "The raw image file. May be up to 2 MB, formerly limited to 1 MB.",
"accept": ["image/*"],
"maxSize": 1000000
"maxSize": 2000000
},
"alt": {
"type": "string",
+10
View File
@@ -27,6 +27,11 @@
"aspectRatio": {
"type": "ref",
"ref": "app.bsky.embed.defs#aspectRatio"
},
"presentation": {
"type": "string",
"description": "A hint to the client about how to present the video.",
"knownValues": ["default", "gif"]
}
}
},
@@ -60,6 +65,11 @@
"aspectRatio": {
"type": "ref",
"ref": "app.bsky.embed.defs#aspectRatio"
},
"presentation": {
"type": "string",
"description": "A hint to the client about how to present the video.",
"knownValues": ["default", "gif"]
}
}
}
+5 -1
View File
@@ -34,7 +34,11 @@
"type": "array",
"items": { "type": "ref", "ref": "com.atproto.label.defs#label" }
},
"threadgate": { "type": "ref", "ref": "#threadgateView" }
"threadgate": { "type": "ref", "ref": "#threadgateView" },
"debug": {
"type": "unknown",
"description": "Debug information for internal development"
}
}
},
"viewerState": {
@@ -11,6 +11,7 @@
"type": "object",
"required": ["interactions"],
"properties": {
"feed": { "type": "string", "format": "at-uri" },
"interactions": {
"type": "array",
"items": {
+1 -1
View File
@@ -32,7 +32,7 @@
"createdAt": { "type": "string", "format": "datetime" },
"hiddenReplies": {
"type": "array",
"maxLength": 50,
"maxLength": 300,
"items": {
"type": "string",
"format": "at-uri"
+20
View File
@@ -159,6 +159,26 @@
"type": "string",
"format": "at-uri",
"description": "if the actor is followed by this DID, contains the AT-URI of the follow record"
},
"blocking": {
"type": "string",
"format": "at-uri",
"description": "if the actor blocks this DID, this is the AT-URI of the block record"
},
"blockedBy": {
"type": "string",
"format": "at-uri",
"description": "if the actor is blocked by this DID, contains the AT-URI of the block record"
},
"blockingByList": {
"type": "string",
"format": "at-uri",
"description": "if the actor blocks this DID via a block list, this is the AT-URI of the listblock record"
},
"blockedByList": {
"type": "string",
"format": "at-uri",
"description": "if the actor is blocked by this DID via a block list, contains the AT-URI of the listblock record"
}
}
}
+2 -1
View File
@@ -11,7 +11,8 @@
"required": ["subject", "createdAt"],
"properties": {
"subject": { "type": "string", "format": "did" },
"createdAt": { "type": "string", "format": "datetime" }
"createdAt": { "type": "string", "format": "datetime" },
"via": { "type": "ref", "ref": "com.atproto.repo.strongRef" }
}
}
}
@@ -25,14 +25,18 @@
"ref": "app.bsky.actor.defs#profileView"
}
},
"recIdStr": {
"type": "string",
"description": "Snowflake for this recommendation, use when submitting recommendation events."
},
"isFallback": {
"type": "boolean",
"description": "If true, response has fallen-back to generic results, and is not scoped using relativeToDid",
"description": "DEPRECATED, unused. Previously: if true, response has fallen-back to generic results, and is not scoped using relativeToDid",
"default": false
},
"recId": {
"type": "integer",
"description": "Snowflake for this recommendation, use when submitting recommendation events."
"description": "DEPRECATED: use recIdStr instead."
}
}
}
@@ -74,7 +74,8 @@
"unverified",
"like-via-repost",
"repost-via-repost",
"subscribed-post"
"subscribed-post",
"contact-match"
]
},
"reasonSubject": { "type": "string", "format": "at-uri" },
@@ -0,0 +1,37 @@
{
"lexicon": 1,
"id": "app.bsky.unspecced.getOnboardingSuggestedStarterPacks",
"defs": {
"main": {
"type": "query",
"description": "Get a list of suggested starterpacks for onboarding",
"parameters": {
"type": "params",
"properties": {
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 10
}
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["starterPacks"],
"properties": {
"starterPacks": {
"type": "array",
"items": {
"type": "ref",
"ref": "app.bsky.graph.defs#starterPackView"
}
}
}
}
}
}
}
}
@@ -0,0 +1,42 @@
{
"lexicon": 1,
"id": "app.bsky.unspecced.getOnboardingSuggestedStarterPacksSkeleton",
"defs": {
"main": {
"type": "query",
"description": "Get a skeleton of suggested starterpacks for onboarding. Intended to be called and hydrated by app.bsky.unspecced.getOnboardingSuggestedStarterPacks",
"parameters": {
"type": "params",
"properties": {
"viewer": {
"type": "string",
"format": "did",
"description": "DID of the account making the request (not included for public/unauthenticated queries)."
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 25,
"default": 10
}
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["starterPacks"],
"properties": {
"starterPacks": {
"type": "array",
"items": {
"type": "string",
"format": "at-uri"
}
}
}
}
}
}
}
}
@@ -0,0 +1,54 @@
{
"lexicon": 1,
"id": "app.bsky.unspecced.getOnboardingSuggestedUsersSkeleton",
"defs": {
"main": {
"type": "query",
"description": "Get a skeleton of suggested users for onboarding. Intended to be called and hydrated by app.bsky.unspecced.getSuggestedOnboardingUsers",
"parameters": {
"type": "params",
"properties": {
"viewer": {
"type": "string",
"format": "did",
"description": "DID of the account making the request (not included for public/unauthenticated queries)."
},
"category": {
"type": "string",
"description": "Category of users to get suggestions for."
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 50,
"default": 25
}
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["dids"],
"properties": {
"dids": {
"type": "array",
"items": {
"type": "string",
"format": "did"
}
},
"recId": {
"type": "string",
"description": "DEPRECATED: use recIdStr instead."
},
"recIdStr": {
"type": "string",
"description": "Snowflake for this recommendation, use when submitting recommendation events."
}
}
}
}
}
}
}
@@ -13,11 +13,6 @@
"type": "string",
"format": "at-uri",
"description": "Reference (AT-URI) to post record. This is the anchor post."
},
"prioritizeFollowedUsers": {
"type": "boolean",
"description": "Whether to prioritize posts from followed users. It only has effect when the user is authenticated.",
"default": false
}
}
},
@@ -33,11 +33,6 @@
"minimum": 0,
"maximum": 100
},
"prioritizeFollowedUsers": {
"type": "boolean",
"description": "Whether to prioritize posts from followed users. It only has effect when the user is authenticated.",
"default": false
},
"sort": {
"type": "string",
"description": "Sorting for the thread replies.",
@@ -0,0 +1,49 @@
{
"lexicon": 1,
"id": "app.bsky.unspecced.getSuggestedOnboardingUsers",
"defs": {
"main": {
"type": "query",
"description": "Get a list of suggested users for onboarding",
"parameters": {
"type": "params",
"properties": {
"category": {
"type": "string",
"description": "Category of users to get suggestions for."
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 50,
"default": 25
}
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["actors"],
"properties": {
"actors": {
"type": "array",
"items": {
"type": "ref",
"ref": "app.bsky.actor.defs#profileView"
}
},
"recId": {
"type": "string",
"description": "DEPRECATED: use recIdStr instead."
},
"recIdStr": {
"type": "string",
"description": "Snowflake for this recommendation, use when submitting recommendation events."
}
}
}
}
}
}
}
@@ -32,6 +32,14 @@
"type": "ref",
"ref": "app.bsky.actor.defs#profileView"
}
},
"recId": {
"type": "string",
"description": "DEPRECATED: use recIdStr instead."
},
"recIdStr": {
"type": "string",
"description": "Snowflake for this recommendation, use when submitting recommendation events."
}
}
}
@@ -0,0 +1,41 @@
{
"lexicon": 1,
"id": "app.bsky.unspecced.getSuggestedUsersForDiscover",
"defs": {
"main": {
"type": "query",
"description": "Get a list of suggested users for the Discover page",
"parameters": {
"type": "params",
"properties": {
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 50,
"default": 25
}
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["actors"],
"properties": {
"actors": {
"type": "array",
"items": {
"type": "ref",
"ref": "app.bsky.actor.defs#profileView"
}
},
"recIdStr": {
"type": "string",
"description": "Snowflake for this recommendation, use when submitting recommendation events."
}
}
}
}
}
}
}
@@ -0,0 +1,46 @@
{
"lexicon": 1,
"id": "app.bsky.unspecced.getSuggestedUsersForDiscoverSkeleton",
"defs": {
"main": {
"type": "query",
"description": "Get a skeleton of suggested users for the Discover page. Intended to be called and hydrated by app.bsky.unspecced.getSuggestedUsersForDiscover",
"parameters": {
"type": "params",
"properties": {
"viewer": {
"type": "string",
"format": "did",
"description": "DID of the account making the request (not included for public/unauthenticated queries)."
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 50,
"default": 25
}
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["dids"],
"properties": {
"dids": {
"type": "array",
"items": {
"type": "string",
"format": "did"
}
},
"recIdStr": {
"type": "string",
"description": "Snowflake for this recommendation, use when submitting recommendation events."
}
}
}
}
}
}
}
@@ -0,0 +1,45 @@
{
"lexicon": 1,
"id": "app.bsky.unspecced.getSuggestedUsersForExplore",
"defs": {
"main": {
"type": "query",
"description": "Get a list of suggested users for the Explore page",
"parameters": {
"type": "params",
"properties": {
"category": {
"type": "string",
"description": "Category of users to get suggestions for."
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 50,
"default": 25
}
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["actors"],
"properties": {
"actors": {
"type": "array",
"items": {
"type": "ref",
"ref": "app.bsky.actor.defs#profileView"
}
},
"recIdStr": {
"type": "string",
"description": "Snowflake for this recommendation, use when submitting recommendation events."
}
}
}
}
}
}
}
@@ -0,0 +1,50 @@
{
"lexicon": 1,
"id": "app.bsky.unspecced.getSuggestedUsersForExploreSkeleton",
"defs": {
"main": {
"type": "query",
"description": "Get a skeleton of suggested users for the Explore page. Intended to be called and hydrated by app.bsky.unspecced.getSuggestedUsersForExplore",
"parameters": {
"type": "params",
"properties": {
"viewer": {
"type": "string",
"format": "did",
"description": "DID of the account making the request (not included for public/unauthenticated queries)."
},
"category": {
"type": "string",
"description": "Category of users to get suggestions for."
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 50,
"default": 25
}
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["dids"],
"properties": {
"dids": {
"type": "array",
"items": {
"type": "string",
"format": "did"
}
},
"recIdStr": {
"type": "string",
"description": "Snowflake for this recommendation, use when submitting recommendation events."
}
}
}
}
}
}
}
@@ -0,0 +1,45 @@
{
"lexicon": 1,
"id": "app.bsky.unspecced.getSuggestedUsersForSeeMore",
"defs": {
"main": {
"type": "query",
"description": "Get a list of suggested users for the See More page",
"parameters": {
"type": "params",
"properties": {
"category": {
"type": "string",
"description": "Category of users to get suggestions for."
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 50,
"default": 25
}
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["actors"],
"properties": {
"actors": {
"type": "array",
"items": {
"type": "ref",
"ref": "app.bsky.actor.defs#profileView"
}
},
"recIdStr": {
"type": "string",
"description": "Snowflake for this recommendation, use when submitting recommendation events."
}
}
}
}
}
}
}
@@ -0,0 +1,50 @@
{
"lexicon": 1,
"id": "app.bsky.unspecced.getSuggestedUsersForSeeMoreSkeleton",
"defs": {
"main": {
"type": "query",
"description": "Get a skeleton of suggested users for the See More page. Intended to be called and hydrated by app.bsky.unspecced.getSuggestedUsersForSeeMore",
"parameters": {
"type": "params",
"properties": {
"viewer": {
"type": "string",
"format": "did",
"description": "DID of the account making the request (not included for public/unauthenticated queries)."
},
"category": {
"type": "string",
"description": "Category of users to get suggestions for."
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 50,
"default": 25
}
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["dids"],
"properties": {
"dids": {
"type": "array",
"items": {
"type": "string",
"format": "did"
}
},
"recIdStr": {
"type": "string",
"description": "Snowflake for this recommendation, use when submitting recommendation events."
}
}
}
}
}
}
}
@@ -37,6 +37,14 @@
"type": "string",
"format": "did"
}
},
"recId": {
"type": "string",
"description": "DEPRECATED: use recIdStr instead."
},
"recIdStr": {
"type": "string",
"description": "Snowflake for this recommendation, use when submitting recommendation events."
}
}
}
@@ -48,6 +48,10 @@
},
"recId": {
"type": "integer",
"description": "DEPRECATED: use recIdStr instead."
},
"recIdStr": {
"type": "string",
"description": "Snowflake for this recommendation, use when submitting recommendation events."
}
}
@@ -13,6 +13,11 @@
"allowIncoming": {
"type": "string",
"knownValues": ["all", "none", "following"]
},
"allowGroupInvites": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. Declaration about group chat invitation preferences for the record owner.",
"type": "string",
"knownValues": ["all", "none", "following"]
}
}
}
+42
View File
@@ -2,6 +2,10 @@
"lexicon": 1,
"id": "chat.bsky.actor.defs",
"defs": {
"memberRole": {
"type": "string",
"knownValues": ["owner", "standard"]
},
"profileViewBasic": {
"type": "object",
"required": ["did", "handle"],
@@ -23,6 +27,7 @@
"type": "array",
"items": { "type": "ref", "ref": "com.atproto.label.defs#label" }
},
"createdAt": { "type": "string", "format": "datetime" },
"chatDisabled": {
"type": "boolean",
"description": "Set to true when the actor cannot actively participate in conversations"
@@ -30,8 +35,45 @@
"verification": {
"type": "ref",
"ref": "app.bsky.actor.defs#verificationState"
},
"kind": {
"description": "Union field that has data specific to different kinds of convos.",
"type": "union",
"refs": [
"#directConvoMember",
"#groupConvoMember",
"#pastGroupConvoMember"
]
}
}
},
"directConvoMember": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here].",
"type": "object",
"properties": {}
},
"groupConvoMember": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. A current group convo member.",
"type": "object",
"required": ["role"],
"properties": {
"addedBy": {
"description": "Who added this member. Only present if the member was added (instead of joining via link).",
"type": "ref",
"ref": "#profileViewBasic"
},
"role": {
"description": "The member's role within this conversation. Only present in group conversation member lists.",
"type": "ref",
"ref": "#memberRole"
}
}
},
"pastGroupConvoMember": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. A past group convo member.",
"type": "object",
"required": [],
"properties": {}
}
}
}
@@ -0,0 +1,49 @@
{
"lexicon": 1,
"id": "chat.bsky.authFullChatClient",
"defs": {
"main": {
"type": "permission-set",
"title": "Full Chat Client (All Conversations)",
"title:lang": {},
"detail": "Control of all chat conversations and configuration management.",
"detail:lang": {
"en": "All Chat Conversations"
},
"permissions": [
{
"type": "permission",
"resource": "rpc",
"inheritAud": true,
"lxm": [
"chat.bsky.actor.deleteAccount",
"chat.bsky.convo.acceptConvo",
"chat.bsky.convo.addReaction",
"chat.bsky.convo.deleteMessageForSelf",
"chat.bsky.convo.exportAccountData",
"chat.bsky.convo.getConvo",
"chat.bsky.convo.getConvoAvailability",
"chat.bsky.convo.getConvoForMembers",
"chat.bsky.convo.getLog",
"chat.bsky.convo.getMessages",
"chat.bsky.convo.leaveConvo",
"chat.bsky.convo.listConvos",
"chat.bsky.convo.muteConvo",
"chat.bsky.convo.removeReaction",
"chat.bsky.convo.sendMessage",
"chat.bsky.convo.sendMessageBatch",
"chat.bsky.convo.unmuteConvo",
"chat.bsky.convo.updateAllRead",
"chat.bsky.convo.updateRead"
]
},
{
"type": "permission",
"resource": "repo",
"action": ["create", "update", "delete"],
"collection": ["chat.bsky.actor.declaration"]
}
]
}
}
}
@@ -4,6 +4,8 @@
"defs": {
"main": {
"type": "procedure",
"description": "Marks a conversation as accepted, so it is shown in the list of accepted convos instead on the request convos.",
"errors": [{ "name": "InvalidConvo" }],
"input": {
"encoding": "application/json",
"schema": {
@@ -37,6 +37,11 @@
}
},
"errors": [
{ "name": "InvalidConvo" },
{
"name": "ReactionNotAllowed",
"description": "Indicates that reactions are not allowed on this message, e.g. because it is a system message."
},
{
"name": "ReactionMessageDeleted",
"description": "Indicates that the message has been deleted and reactions can no longer be added/removed."
+528 -5
View File
@@ -2,6 +2,18 @@
"lexicon": 1,
"id": "chat.bsky.convo.defs",
"defs": {
"convoKind": {
"type": "string",
"knownValues": ["direct", "group"]
},
"convoLockStatus": {
"type": "string",
"knownValues": ["unlocked", "locked", "locked-permanently"]
},
"convoStatus": {
"type": "string",
"knownValues": ["request", "accepted"]
},
"messageRef": {
"type": "object",
"required": ["did", "messageId", "convoId"],
@@ -60,6 +72,181 @@
"sentAt": { "type": "string", "format": "datetime" }
}
},
"systemMessageReferredUser": {
"type": "object",
"required": ["did"],
"properties": {
"did": { "type": "string", "format": "did" }
}
},
"systemMessageView": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here].",
"type": "object",
"required": ["id", "rev", "sentAt", "data"],
"properties": {
"id": { "type": "string" },
"rev": { "type": "string" },
"sentAt": { "type": "string", "format": "datetime" },
"data": {
"type": "union",
"refs": [
"#systemMessageDataAddMember",
"#systemMessageDataRemoveMember",
"#systemMessageDataMemberJoin",
"#systemMessageDataMemberLeave",
"#systemMessageDataLockConvo",
"#systemMessageDataUnlockConvo",
"#systemMessageDataLockConvoPermanently",
"#systemMessageDataEditGroup",
"#systemMessageDataCreateJoinLink",
"#systemMessageDataEditJoinLink",
"#systemMessageDataEnableJoinLink",
"#systemMessageDataDisableJoinLink"
]
}
}
},
"systemMessageDataAddMember": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. System message indicating a user was added to the group convo.",
"type": "object",
"required": ["member", "role", "addedBy"],
"properties": {
"member": {
"description": "Current view of the member who was added.",
"type": "ref",
"ref": "#systemMessageReferredUser"
},
"role": {
"description": "Role the user was added to the group with. The role from 'member' will reflect the current data, not historical.",
"type": "ref",
"ref": "chat.bsky.actor.defs#memberRole"
},
"addedBy": {
"type": "ref",
"ref": "#systemMessageReferredUser"
}
}
},
"systemMessageDataRemoveMember": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. System message indicating a user was removed from the group convo.",
"type": "object",
"required": ["member", "removedBy"],
"properties": {
"member": {
"description": "Current view of the member who was removed.",
"type": "ref",
"ref": "#systemMessageReferredUser"
},
"removedBy": {
"type": "ref",
"ref": "#systemMessageReferredUser"
}
}
},
"systemMessageDataMemberJoin": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. System message indicating a user joined the group convo via join link.",
"type": "object",
"required": ["member", "role"],
"properties": {
"member": {
"description": "Current view of the member who joined.",
"type": "ref",
"ref": "#systemMessageReferredUser"
},
"role": {
"description": "Role the user was added to the group with. The role from 'member' will reflect the current data, not historical.",
"type": "ref",
"ref": "chat.bsky.actor.defs#memberRole"
},
"approvedBy": {
"description": "If join link was configured to require approval, this will be set to who approved the request. Undefined if approval was not required.",
"type": "ref",
"ref": "#systemMessageReferredUser"
}
}
},
"systemMessageDataMemberLeave": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. System message indicating a user voluntarily left the group convo.",
"type": "object",
"required": ["member"],
"properties": {
"member": {
"description": "Current view of the member who left the group.",
"type": "ref",
"ref": "#systemMessageReferredUser"
}
}
},
"systemMessageDataLockConvo": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. System message indicating the group convo was locked.",
"type": "object",
"required": ["lockedBy"],
"properties": {
"lockedBy": {
"description": "Current view of the member who locked the group.",
"type": "ref",
"ref": "#systemMessageReferredUser"
}
}
},
"systemMessageDataUnlockConvo": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. System message indicating the group convo was unlocked.",
"type": "object",
"required": ["unlockedBy"],
"properties": {
"unlockedBy": {
"description": "Current view of the member who unlocked the group.",
"type": "ref",
"ref": "#systemMessageReferredUser"
}
}
},
"systemMessageDataLockConvoPermanently": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. System message indicating the group convo was locked permanently.",
"type": "object",
"required": ["lockedBy"],
"properties": {
"lockedBy": {
"description": "Current view of the member who locked the group.",
"type": "ref",
"ref": "#systemMessageReferredUser"
}
}
},
"systemMessageDataEditGroup": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. System message indicating the group info was edited.",
"type": "object",
"properties": {
"oldName": {
"description": "Group name that was replaced.",
"type": "string"
},
"newName": {
"description": "Group name that replaced the old.",
"type": "string"
}
}
},
"systemMessageDataCreateJoinLink": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. System message indicating the group join link was created.",
"type": "object",
"properties": {}
},
"systemMessageDataEditJoinLink": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. System message indicating the group join link was edited.",
"type": "object",
"properties": {}
},
"systemMessageDataEnableJoinLink": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. System message indicating the group join link was enabled.",
"type": "object",
"properties": {}
},
"systemMessageDataDisableJoinLink": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. System message indicating the group join link was disabled.",
"type": "object",
"properties": {}
},
"deletedMessageView": {
"type": "object",
"required": ["id", "rev", "sender", "sentAt"],
@@ -108,6 +295,7 @@
"id": { "type": "string" },
"rev": { "type": "string" },
"members": {
"description": "Members of this conversation. For direct convos, it will be an immutable list of the 2 members. For group convos, it will a list of important members (the first few members, the viewer, the member who invited the viewer, the member who sent the last message, the member who sent the last reaction), but will not contain the full list of members. Use chat.bsky.convo.getConvoMembers to list all members.",
"type": "array",
"items": {
"type": "ref",
@@ -116,7 +304,7 @@
},
"lastMessage": {
"type": "union",
"refs": ["#messageView", "#deletedMessageView"]
"refs": ["#messageView", "#deletedMessageView", "#systemMessageView"]
},
"lastReaction": {
"type": "union",
@@ -124,13 +312,51 @@
},
"muted": { "type": "boolean" },
"status": {
"type": "string",
"knownValues": ["request", "accepted"]
"description": "Convo status for the viewer member (not the convo itself).",
"type": "ref",
"ref": "#convoStatus"
},
"unreadCount": { "type": "integer" }
"unreadCount": { "type": "integer" },
"kind": {
"description": "Union field that has data specific to different kinds of convos.",
"type": "union",
"refs": ["#directConvo", "#groupConvo"]
}
}
},
"directConvo": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here].",
"type": "object",
"properties": {}
},
"groupConvo": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here].",
"type": "object",
"required": ["name", "lockStatus", "memberCount"],
"properties": {
"name": {
"type": "string",
"description": "The display name of the group conversation.",
"maxGraphemes": 128,
"maxLength": 1280
},
"memberCount": {
"type": "integer",
"description": "The total number of members in the group conversation."
},
"joinLink": {
"type": "ref",
"ref": "chat.bsky.group.defs#joinLinkView"
},
"lockStatus": {
"description": "The lock status of the conversation.",
"type": "ref",
"ref": "#convoLockStatus"
}
}
},
"logBeginConvo": {
"description": "Event indicating a convo containing the viewer was started. Can be direct or group. When a member is added to a group convo, they also get this event.",
"type": "object",
"required": ["rev", "convoId"],
"properties": {
@@ -139,6 +365,7 @@
}
},
"logAcceptConvo": {
"description": "Event indicating the viewer accepted a convo, and it can be moved out of the request inbox. Can be direct or group.",
"type": "object",
"required": ["rev", "convoId"],
"properties": {
@@ -147,6 +374,7 @@
}
},
"logLeaveConvo": {
"description": "Event indicating the viewer left a convo. Can be direct or group.",
"type": "object",
"required": ["rev", "convoId"],
"properties": {
@@ -155,6 +383,7 @@
}
},
"logMuteConvo": {
"description": "Event indicating the viewer muted a convo. Can be direct or group.",
"type": "object",
"required": ["rev", "convoId"],
"properties": {
@@ -163,6 +392,7 @@
}
},
"logUnmuteConvo": {
"description": "Event indicating the viewer unmuted a convo. Can be direct or group.",
"type": "object",
"required": ["rev", "convoId"],
"properties": {
@@ -171,6 +401,7 @@
}
},
"logCreateMessage": {
"description": "Event indicating a user-originated message was created. Is not emitted for system messages.",
"type": "object",
"required": ["rev", "convoId", "message"],
"properties": {
@@ -183,6 +414,7 @@
}
},
"logDeleteMessage": {
"description": "Event indicating a user-originated message was deleted. Is not emitted for system messages.",
"type": "object",
"required": ["rev", "convoId", "message"],
"properties": {
@@ -195,6 +427,7 @@
}
},
"logReadMessage": {
"description": "DEPRECATED: use logReadConvo instead. Event indicating a convo was read up to a certain message.",
"type": "object",
"required": ["rev", "convoId", "message"],
"properties": {
@@ -202,11 +435,12 @@
"convoId": { "type": "string" },
"message": {
"type": "union",
"refs": ["#messageView", "#deletedMessageView"]
"refs": ["#messageView", "#deletedMessageView", "#systemMessageView"]
}
}
},
"logAddReaction": {
"description": "Event indicating a reaction was added to a message.",
"type": "object",
"required": ["rev", "convoId", "message", "reaction"],
"properties": {
@@ -220,6 +454,7 @@
}
},
"logRemoveReaction": {
"description": "Event indicating a reaction was removed from a message.",
"type": "object",
"required": ["rev", "convoId", "message", "reaction"],
"properties": {
@@ -231,6 +466,294 @@
},
"reaction": { "type": "ref", "ref": "#reactionView" }
}
},
"logReadConvo": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. Event indicating a convo was read up to a certain message.",
"type": "object",
"required": ["rev", "convoId", "message"],
"properties": {
"rev": { "type": "string" },
"convoId": { "type": "string" },
"message": {
"type": "union",
"refs": ["#messageView", "#deletedMessageView", "#systemMessageView"]
}
}
},
"logAddMember": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. Event indicating a member was added to a group convo. The member who was added gets a logBeginConvo (to create the convo) but also a logAddMember (to show the system message as the first message the user sees).",
"type": "object",
"required": ["rev", "convoId", "message", "relatedProfiles"],
"properties": {
"rev": { "type": "string" },
"convoId": { "type": "string" },
"message": {
"description": "A system message with data of type #systemMessageDataAddMember",
"type": "ref",
"ref": "#systemMessageView"
},
"relatedProfiles": {
"description": "Profiles referred in the system message.",
"type": "array",
"items": {
"type": "ref",
"ref": "chat.bsky.actor.defs#profileViewBasic"
}
}
}
},
"logRemoveMember": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. Event indicating a member was removed from a group convo. The member who was removed gets a logLeaveConvo (to leave the convo) but not a logRemoveMember (because they already left, so can't see the system message).",
"type": "object",
"required": ["rev", "convoId", "message", "relatedProfiles"],
"properties": {
"rev": { "type": "string" },
"convoId": { "type": "string" },
"message": {
"description": "A system message with data of type #systemMessageDataRemoveMember",
"type": "ref",
"ref": "#systemMessageView"
},
"relatedProfiles": {
"description": "Profiles referred in the system message.",
"type": "array",
"items": {
"type": "ref",
"ref": "chat.bsky.actor.defs#profileViewBasic"
}
}
}
},
"logMemberJoin": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. Event indicating a member joined a group convo via join link. The member who was added gets a logBeginConvo (to create the convo) but also a logMemberJoin (to show the system message as the first message the user sees).",
"type": "object",
"required": ["rev", "convoId", "message", "relatedProfiles"],
"properties": {
"rev": { "type": "string" },
"convoId": { "type": "string" },
"message": {
"description": "A system message with data of type #systemMessageDataMemberJoin",
"type": "ref",
"ref": "#systemMessageView"
},
"relatedProfiles": {
"description": "Profiles referred in the system message.",
"type": "array",
"items": {
"type": "ref",
"ref": "chat.bsky.actor.defs#profileViewBasic"
}
}
}
},
"logMemberLeave": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. Event indicating a member voluntarily left a group convo. The member who was removed gets a logLeaveConvo (to leave the convo) but not a logMemberLeave (because they already left, so can't see the system message).",
"type": "object",
"required": ["rev", "convoId", "message", "relatedProfiles"],
"properties": {
"rev": { "type": "string" },
"convoId": { "type": "string" },
"message": {
"description": "A system message with data of type #systemMessageDataMemberLeave",
"type": "ref",
"ref": "#systemMessageView"
},
"relatedProfiles": {
"description": "Profiles referred in the system message.",
"type": "array",
"items": {
"type": "ref",
"ref": "chat.bsky.actor.defs#profileViewBasic"
}
}
}
},
"logLockConvo": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. Event indicating a group convo was locked.",
"type": "object",
"required": ["rev", "convoId", "message", "relatedProfiles"],
"properties": {
"rev": { "type": "string" },
"convoId": { "type": "string" },
"message": {
"description": "A system message with data of type #systemMessageDataLockConvo",
"type": "ref",
"ref": "#systemMessageView"
},
"relatedProfiles": {
"description": "Profiles referred in the system message.",
"type": "array",
"items": {
"type": "ref",
"ref": "chat.bsky.actor.defs#profileViewBasic"
}
}
}
},
"logUnlockConvo": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. Event indicating a group convo was unlocked.",
"type": "object",
"required": ["rev", "convoId", "message", "relatedProfiles"],
"properties": {
"rev": { "type": "string" },
"convoId": { "type": "string" },
"message": {
"description": "A system message with data of type #systemMessageDataUnlockConvo",
"type": "ref",
"ref": "#systemMessageView"
},
"relatedProfiles": {
"description": "Profiles referred in the system message.",
"type": "array",
"items": {
"type": "ref",
"ref": "chat.bsky.actor.defs#profileViewBasic"
}
}
}
},
"logLockConvoPermanently": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. Event indicating a group convo was locked permanently.",
"type": "object",
"required": ["rev", "convoId", "message", "relatedProfiles"],
"properties": {
"rev": { "type": "string" },
"convoId": { "type": "string" },
"message": {
"description": "A system message with data of type #systemMessageDataLockConvoPermanently",
"type": "ref",
"ref": "#systemMessageView"
},
"relatedProfiles": {
"description": "Profiles referred in the system message.",
"type": "array",
"items": {
"type": "ref",
"ref": "chat.bsky.actor.defs#profileViewBasic"
}
}
}
},
"logEditGroup": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. Event indicating info about group convo was edited.",
"type": "object",
"required": ["rev", "convoId", "message"],
"properties": {
"rev": { "type": "string" },
"convoId": { "type": "string" },
"message": {
"description": "A system message with data of type #systemMessageDataEditGroup",
"type": "ref",
"ref": "#systemMessageView"
}
}
},
"logCreateJoinLink": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. Event indicating a join link was created for a group convo.",
"type": "object",
"required": ["rev", "convoId", "message"],
"properties": {
"rev": { "type": "string" },
"convoId": { "type": "string" },
"message": {
"description": "A system message with data of type #systemMessageDataCreateJoinLink",
"type": "ref",
"ref": "#systemMessageView"
}
}
},
"logEditJoinLink": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. Event indicating a settings about a join link for a group convo were edited.",
"type": "object",
"required": ["rev", "convoId", "message"],
"properties": {
"rev": { "type": "string" },
"convoId": { "type": "string" },
"message": {
"description": "A system message with data of type #systemMessageDataEditJoinLink",
"type": "ref",
"ref": "#systemMessageView"
}
}
},
"logEnableJoinLink": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. Event indicating a join link was enabled for a group convo.",
"type": "object",
"required": ["rev", "convoId", "message"],
"properties": {
"rev": { "type": "string" },
"convoId": { "type": "string" },
"message": {
"description": "A system message with data of type #systemMessageDataEnableJoinLink",
"type": "ref",
"ref": "#systemMessageView"
}
}
},
"logDisableJoinLink": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. Event indicating a join link was disabled for a group convo.",
"type": "object",
"required": ["rev", "convoId", "message"],
"properties": {
"rev": { "type": "string" },
"convoId": { "type": "string" },
"message": {
"description": "A system message with data of type #systemMessageDataDisableJoinLink",
"type": "ref",
"ref": "#systemMessageView"
}
}
},
"logIncomingJoinRequest": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. Event indicating a join request was made to a group the viewer owns. Only the owner gets this.",
"type": "object",
"required": ["rev", "convoId", "member"],
"properties": {
"rev": { "type": "string" },
"convoId": { "type": "string" },
"member": {
"description": "Prospective member who requested to join.",
"type": "ref",
"ref": "chat.bsky.actor.defs#profileViewBasic"
}
}
},
"logApproveJoinRequest": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. Event indicating a join request was approved by the viewer. Only the owner gets this. The approved member gets a logBeginConvo.",
"type": "object",
"required": ["rev", "convoId", "member"],
"properties": {
"rev": { "type": "string" },
"convoId": { "type": "string" },
"member": {
"description": "Prospective member who requested to join.",
"type": "ref",
"ref": "chat.bsky.actor.defs#profileViewBasic"
}
}
},
"logRejectJoinRequest": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. Event indicating a join request was rejected by the viewer. Only the owner gets this.",
"type": "object",
"required": ["rev", "convoId", "member"],
"properties": {
"rev": { "type": "string" },
"convoId": { "type": "string" },
"member": {
"description": "Prospective member who requested to join.",
"type": "ref",
"ref": "chat.bsky.actor.defs#profileViewBasic"
}
}
},
"logOutgoingJoinRequest": {
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. Event indicating a join request was made by the viewer.",
"type": "object",
"required": ["rev", "convoId"],
"properties": {
"rev": { "type": "string" },
"convoId": { "type": "string" }
}
}
}
}
@@ -4,6 +4,14 @@
"defs": {
"main": {
"type": "procedure",
"description": "Marks a message as deleted for the viewer, so they won't see that message in future enumerations.",
"errors": [
{ "name": "InvalidConvo" },
{
"name": "MessageDeleteNotAllowed",
"description": "Indicates that this message cannot be deleted, e.g. because it is a system message."
}
],
"input": {
"encoding": "application/json",
"schema": {
+2
View File
@@ -4,6 +4,8 @@
"defs": {
"main": {
"type": "query",
"description": "Gets an existing conversation by its ID.",
"errors": [{ "name": "InvalidConvo" }],
"parameters": {
"type": "params",
"required": ["convoId"],
@@ -4,7 +4,7 @@
"defs": {
"main": {
"type": "query",
"description": "Get whether the requester and the other members can chat. If an existing convo is found for these members, it is returned.",
"description": "Check whether the requester and the other members can start a 1-1 chat. Only applicable to direct (non-group) conversations. If an existing convo is found for these members, it is returned. Does not create a new convo if it doesn't exist.",
"parameters": {
"type": "params",
"required": ["members"],
@@ -4,6 +4,14 @@
"defs": {
"main": {
"type": "query",
"description": "Get or create a 1-1 conversation for the given members. Always returns the same direct (non-group) conversation. To create a group conversation, use createGroup.",
"errors": [
{ "name": "AccountSuspended" },
{ "name": "BlockedActor" },
{ "name": "MessagesDisabled" },
{ "name": "NotFollowedBySender" },
{ "name": "RecipientNotFound" }
],
"parameters": {
"type": "params",
"required": ["members"],
@@ -0,0 +1,42 @@
{
"lexicon": 1,
"id": "chat.bsky.convo.getConvoMembers",
"defs": {
"main": {
"type": "query",
"description": "Returns a paginated list of members from a conversation.",
"errors": [{ "name": "InvalidConvo" }],
"parameters": {
"type": "params",
"required": ["convoId"],
"properties": {
"convoId": { "type": "string" },
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 50
},
"cursor": { "type": "string" }
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["members"],
"properties": {
"cursor": { "type": "string" },
"members": {
"type": "array",
"items": {
"type": "ref",
"ref": "chat.bsky.actor.defs#profileViewBasic"
}
}
}
}
}
}
}
}
+23 -1
View File
@@ -31,8 +31,30 @@
"chat.bsky.convo.defs#logCreateMessage",
"chat.bsky.convo.defs#logDeleteMessage",
"chat.bsky.convo.defs#logReadMessage",
"chat.bsky.convo.defs#logAddReaction",
"chat.bsky.convo.defs#logRemoveReaction"
"chat.bsky.convo.defs#logRemoveReaction",
"chat.bsky.convo.defs#logReadConvo",
"chat.bsky.convo.defs#logAddMember",
"chat.bsky.convo.defs#logRemoveMember",
"chat.bsky.convo.defs#logMemberJoin",
"chat.bsky.convo.defs#logMemberLeave",
"chat.bsky.convo.defs#logLockConvo",
"chat.bsky.convo.defs#logUnlockConvo",
"chat.bsky.convo.defs#logLockConvoPermanently",
"chat.bsky.convo.defs#logEditGroup",
"chat.bsky.convo.defs#logCreateJoinLink",
"chat.bsky.convo.defs#logEditJoinLink",
"chat.bsky.convo.defs#logEnableJoinLink",
"chat.bsky.convo.defs#logDisableJoinLink",
"chat.bsky.convo.defs#logIncomingJoinRequest",
"chat.bsky.convo.defs#logApproveJoinRequest",
"chat.bsky.convo.defs#logRejectJoinRequest",
"chat.bsky.convo.defs#logOutgoingJoinRequest"
]
}
}
+12 -1
View File
@@ -4,6 +4,8 @@
"defs": {
"main": {
"type": "query",
"description": "Returns a page of messages from a conversation.",
"errors": [{ "name": "InvalidConvo" }],
"parameters": {
"type": "params",
"required": ["convoId"],
@@ -31,9 +33,18 @@
"type": "union",
"refs": [
"chat.bsky.convo.defs#messageView",
"chat.bsky.convo.defs#deletedMessageView"
"chat.bsky.convo.defs#deletedMessageView",
"chat.bsky.convo.defs#systemMessageView"
]
}
},
"relatedProfiles": {
"description": "Set of all members who authored or reacted to the returned messages. Members referred to by system messages are also included.",
"type": "array",
"items": {
"type": "ref",
"ref": "chat.bsky.actor.defs#profileViewBasic"
}
}
}
}
+8
View File
@@ -4,6 +4,14 @@
"defs": {
"main": {
"type": "procedure",
"description": "Leaves a conversation (direct or group). For group, this effectively removes membership. For direct, membership is never removed, only changed to remove from enumerations by the user who left.",
"errors": [
{ "name": "InvalidConvo" },
{
"name": "OwnerCannotLeave",
"description": "The owner of a group conversation cannot leave before locking the group."
}
],
"input": {
"encoding": "application/json",
"schema": {
@@ -0,0 +1,42 @@
{
"lexicon": 1,
"id": "chat.bsky.convo.listConvoRequests",
"defs": {
"main": {
"type": "query",
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. Returns a page of incoming conversation requests for the user. Direct convo requests are returned as convoView; group join requests are returned as joinRequestView.",
"parameters": {
"type": "params",
"properties": {
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 50
},
"cursor": { "type": "string" }
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["requests"],
"properties": {
"cursor": { "type": "string" },
"requests": {
"type": "array",
"items": {
"type": "union",
"refs": [
"chat.bsky.convo.defs#convoView",
"chat.bsky.group.defs#joinRequestView"
]
}
}
}
}
}
}
}
}
+7
View File
@@ -4,6 +4,7 @@
"defs": {
"main": {
"type": "query",
"description": "Returns a page of conversations (direct or group) for the user.",
"parameters": {
"type": "params",
"properties": {
@@ -19,8 +20,14 @@
"knownValues": ["unread"]
},
"status": {
"description": "Filter convos by their status. It is discouraged to call with \"request\" and preferred to call chat.bsky.convo.listConvoRequests, which also includes group join requests made by the user.",
"type": "string",
"knownValues": ["request", "accepted"]
},
"kind": {
"type": "string",
"description": "Filter by conversation kind.",
"knownValues": ["direct", "group"]
}
}
},
+38
View File
@@ -0,0 +1,38 @@
{
"lexicon": 1,
"id": "chat.bsky.convo.lockConvo",
"defs": {
"main": {
"type": "procedure",
"description": "[NOTE: This is under active development and should be considered unstable while this note is here]. Locks a group convo so no more content (messages, reactions) can be added to it.",
"errors": [
{ "name": "ConvoLocked" },
{ "name": "InvalidConvo" },
{ "name": "InsufficientRole" }
],
"input": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["convoId"],
"properties": {
"convoId": { "type": "string" }
}
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["convo"],
"properties": {
"convo": {
"type": "ref",
"ref": "chat.bsky.convo.defs#convoView"
}
}
}
}
}
}
}
+2
View File
@@ -4,6 +4,8 @@
"defs": {
"main": {
"type": "procedure",
"description": "Mutes a conversation, preventing notifications related to it.",
"errors": [{ "name": "InvalidConvo" }],
"input": {
"encoding": "application/json",
"schema": {
@@ -37,6 +37,11 @@
}
},
"errors": [
{ "name": "InvalidConvo" },
{
"name": "ReactionNotAllowed",
"description": "Indicates that reactions are not allowed on this message, e.g. because it is a system message."
},
{
"name": "ReactionMessageDeleted",
"description": "Indicates that the message has been deleted and reactions can no longer be added/removed."
@@ -4,6 +4,8 @@
"defs": {
"main": {
"type": "procedure",
"description": "Sends a message to a conversation.",
"errors": [{ "name": "ConvoLocked" }, { "name": "InvalidConvo" }],
"input": {
"encoding": "application/json",
"schema": {

Some files were not shown because too many files have changed in this diff Show More