* 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
* 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
* Remove deprecated handling from getSuggestedFollowsByActor
* Remove very old test that is not testing existing functionality
* Import sort
* Softer off-ramp if suggestionsAgent
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.
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.
* Deprecate recId in favor of recIdStr
* Add changeset
* Revert "Add changeset"
This reverts commit 3241aec7202c1bcb2a8e28f7064def5024dcae73.
* Recreate changeset as patch version
* Lex SDK error handling improvements
* Add support for method defined through `@atproto/lex` in addition to `@atproto/lexicon` "codegen"
* review comments
* tidy
* docs: Updating app password based session example
Updating atproto app password based session example in README
* refactor: prettier lint + changeset
* 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>
* 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>
* 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
* 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>
* 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
* 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
* 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`
* 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
* 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`
* 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
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>
* 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>
* 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
* 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
* 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)
* 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
* 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__*/
* 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
* Remove ability to define `blob` permission in permission sets
* Disallow `rpc` permissions with specific `aud` in permission-sets
* Add `toScopes()` utility on `IncludeScope`
* tidy
* 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
* 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
* 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
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
* ✨ 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
* Integrate new reporting reasons
* Update bnn to BNR, prefix all with reason* to match previous
* Remap deprecations
* Update naming, add notes about Bluesky-only reasons
* Update reason
* Move new defs to tools.ozone namespace
* Add ozone lexicons to app view
* Copy known values to merge defs
* Update comments
* Add reasonAppeal to new ozone namespace defs
* Changeset
* ❇️ Support new reporting categories in ozone (#3974)
* ✨ Validate report reason using labeler service profile
* ✅ Rename test
* :rotating_lights: Fix lint issue
* ✨ Use both appeal reason type for materialized views
* ✨ Add old to new reason mapping for fallback
* ✅ Update test snapshot
* :rotating_lights: Fix lint issue
* 🧹 Cleanup
* :rotating_lights: Fix lint issue
* ✨ Adjust report reason tagging
* 📝 Additional comment for new migration
---------
Co-authored-by: Foysal Ahamed <foysal@blueskyweb.xyz>
* Allow unexpected error to go through when fetching permission sets
* Log `cid` as string after succesful lexicon resolution
* Log `cid` and `uri` as string on successful lexicon resolution
* Export constants and type assertion utilities
* Add permission set support to oauth provider
* improve permission set parsing
* Rename `PermissionSet` to `ScopePermissions`
* Improve performance of NSID validation
* Add support for `permission-set` in lexicon document
* Validate NSID syntax using `@atproto/syntax`
* Export all types used in public interfaces (from `lexicon-resolver`)
* Small performance improvement
* Rework scope parsing utilities to work with Lexicon defined permissions
* file rename
* fixup! Rework scope parsing utilities to work with Lexicon defined permissions
* removed outdated comment
* removed outdated comment
* fix comment typo
* Improve `SimpleStore` api
* permission-set NSID auth scopes
* Remove dev dependency on dev-env
* fix build script
* pnpm-lock
* Improve fetch-node unicast protection
* Explicitly set the `redirect: "follow"` `fetch()` option
* Add delay when building oauth-provider-ui in watch mode
* Remove external dependencies from auth-scopes
* Add customizable lexicon authority to pds (for dev purposes)
* fix pds migration
* update permission-set icon
* Add support for `include:` syntax in scopes
* tidy
* Renaming of "resource" concept to better reflect the fact that not all oauth scope values are about resources
* changeset
* ui improvmeents
* i18n
* ui imporvements
* add `AtprotoAudience` type
* Enforce proper formatting of audience (atproto supported did + fragment part)
* tidy
* tidy
* tidy
* fix ci ?
* ci fix ?
* tidy ?
* Apply consistent outline around focusable items
* Use `inheritAud: true` to control `aud` inheritance
* Update packages/oauth/oauth-provider/src/lexicon/lexicon-manager.ts
Co-authored-by: devin ivy <devinivy@gmail.com>
* Review comments
* Add `nsid` property to `LexiconResolutionError`
* improve nsid validation
* i18n
* Improve oauth scope parsing
* Simplify lex scope parsing
* tidy
* docs
* tidy
* ci
* Code simplification
* tidy
* improve type safety
* improve deps graph
* naming
* Improve tests and package structure
* Improve error when resolving a non permission-set
* improve nsid parsing perfs
* benchmark
* Refactor ozone and lexicon into using a common service profile mechanism
* improve perfs
* ci fix (?)
* tidy
* Allow storage of valid lexicons in lexicon store
* Improve handling of lexicon resolution failures
* review comment
* Test both regexp and non regexp based nsid validation
* properly detect presence of port number in https did:web
* Re-enable logging of `safeFetch` requests
* tidy
---------
Co-authored-by: devin ivy <devinivy@gmail.com>
* Support multiple redirect URIs for @atproto/oauth-client-browser
* For redirect_uri callback parameter type
* fix-type-error
* Do not fail if the client can't figure out which redirect uri was used (and only one is available)
---------
Co-authored-by: Emelia Smith <ThisIsMissEm@users.noreply.github.com>
* @atproto/lexicon: relax validation of lexicon documents to allow unknown fields
* changeset
* remove field from lexiconDoc validation, no longer necessary
* Remore requirement for JWK to define either `use` or `key_ops`
* Prevent inconsistent use of `use` and `key_ops` in JWK
* docs
* review comments
* comment
* Validate user status before initiating
* Format
* Add test, not working yet
* Fix test
* Import order
---------
Co-authored-by: rafael <rafael@blueskyweb.xyz>
* 🚧 WIP
* ✨ Make age assurance state queryable
* ✨ Split age assurance events into 2
* ✨ Implement admin and user state overrides
* ✨ Add blocked as a known value for age assurance state
* ✅ Update test snapshot
* ✅ Update test snapshot
* ✨ Cleanup
* xrpc-server: skip body parsing when input encoding is */*, fix json and text uploads
* changeset
* pds: add tests for text and json uploads
* tidy
* xrpc-server: only create body parser when it will be used
* ✨ Add userAgent tracking for events
* :rotating_lights: Fix lint issue
* ♻️ Refactor userAgent to modTool
* ✨ Rename extra to meta
* 📝 Add changeset
* ✨ Support modTools param in createReport
* ✨ Add support for mod tool in createReport
* Fix authorization error type name
* Refactor authorization error handling: replace AccessDeniedError with AuthorizationError and improve error reporting
* Improve OAuth Example app
* Improve style
* bsync: Accept NSID with fragment in operation ns (#3954)
* Add `match: MuteWordMatch` to `muted-word` mod decision `cause` (#2934)
* Return MuteWordMatch instead of simple boolean
* Return full mute word with match
* Add MuteWordMatch to decision cause, update a few tests
* Backwards compat
* Tighter types
* Return all mute word matches
* Clean up types
* Rename
* More cleanup of naming
* Remove unneeded changes
* Format
* Add predicate value to matches
* Better migration path
* Changeset
* Import sort
* Tighten up addMuteWord API
Co-authored-by: Matthieu Sieben <matthieusieben@users.noreply.github.com>
* Mute words: handle `Andor` and `and/or` case (#3948)
* Handle Andor case
* Remove useless escape
* Changeset
---------
Co-authored-by: Matthieu Sieben <matthieusieben@users.noreply.github.com>
* Version packages (#3947)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* Update README.md to add some missing details in examples (#3254)
Update README.md
Improve code examples (some OAuth implementation details are missing in these examples)
* Increase oauth session & refresh token lifetimes (#3883)
* Allow HTTPS `redirect_uris` from any origin (#3811)
* bump MST key length from 256 to 1024 chars (#3956)
* bump MST key length from 256 to 1024 chars
* update MST key test
* add a changeset
* Version packages (#3959)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* Rename `filter` -> `include` (#3966)
* rename filter -> include
* changeset
* fix tests
* Minor Fixes: Typo Correction and Comment Update (#3961)
* Update blob-resolver.ts
* Update index.ts
* Appview: sync up protos for notification prefs (#3970)
appview: sync up protos for notification prefs
* Version packages (#3969)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* Fix invalid use of `invalid_client` (#3967)
* Replace slice() with subarray() in car file parsing (#3971)
* Replace slice() with subarray() in car file parsing
* changeset
---------
Co-authored-by: Devin Ivy <devinivy@gmail.com>
* Re-export all types & utilities needed to instantiate an OAuth client (#3976)
* Re-export all types & utilities needed to instantiate an OAuth client
* Add `jwkPrivateSchema` to ensure a key is private
* Return object instead of array as result of `findPrivateKey`
* Allow override of default `handleResolver` and `runtimeImplementation` options for NodeOAuthClient
* changeset
* Allow `OAuthClient` to be instantiated with custom `didResolver` instance
* Version packages (#3975)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* Perform a bi-directional check when resolving identity from did (#3977)
* Perform a bi-directional check when resolving identity from did
* tidy
* Reject did documents containing invalid `alsoKnownAs` ATProto handles
* Use error classes
* tidy
* Improve identity resolution
* tidy
* Allow non-normalized handles in did document
* pnpm-lock
* Version packages (#3979)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* repo: MST should allow tilde in keys (#3981)
* repo: MST should allow tilde in keys
* add changeset
* fic ci
* tidy
* tidy
---------
Co-authored-by: rafael <rafael@blueskyweb.xyz>
Co-authored-by: Eric Bailey <git@esb.lol>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: James Futhey <kidGodzilla@users.noreply.github.com>
Co-authored-by: bnewbold <bnewbold@robocracy.org>
Co-authored-by: Samuel Newman <mozzius@protonmail.com>
Co-authored-by: leopardracer <136604165+leopardracer@users.noreply.github.com>
Co-authored-by: devin ivy <devinivy@gmail.com>
Co-authored-by: Paul Frazee <pfrazee@gmail.com>
* Re-export all types & utilities needed to instantiate an OAuth client
* Add `jwkPrivateSchema` to ensure a key is private
* Return object instead of array as result of `findPrivateKey`
* Allow override of default `handleResolver` and `runtimeImplementation` options for NodeOAuthClient
* changeset
* Allow `OAuthClient` to be instantiated with custom `didResolver` instance
* Return MuteWordMatch instead of simple boolean
* Return full mute word with match
* Add MuteWordMatch to decision cause, update a few tests
* Backwards compat
* Tighter types
* Return all mute word matches
* Clean up types
* Rename
* More cleanup of naming
* Remove unneeded changes
* Format
* Add predicate value to matches
* Better migration path
* Changeset
* Import sort
* Tighten up addMuteWord API
Co-authored-by: Matthieu Sieben <matthieusieben@users.noreply.github.com>
* Mute words: handle `Andor` and `and/or` case (#3948)
* Handle Andor case
* Remove useless escape
* Changeset
---------
Co-authored-by: Matthieu Sieben <matthieusieben@users.noreply.github.com>
* Ensure that the credentials used during a refresh correspond to those used to create the OAuth tokens.
* tidy
* Bind the OAuth session to the kid that was used to authenticate the client (private_key_jwt)
* Store the whole authentication method in the client session store rather than the kid only
* tidy
* Improve error reporting in case an invalid `token_endpoint_auth_method` is used in the client metadata document.
* tidy
* tidy
* Improve JAR checks
* tidy
* changeset
* tidy
* Remove schema's `.optional()` modifier when a `.default()` is defined
* tidy
* verify client auth during code exchange
* tidy
* Minor naming improvement
* tidy
* Update .changeset/quiet-pans-fix.md
Co-authored-by: devin ivy <devinivy@gmail.com>
* Update packages/oauth/oauth-client/src/oauth-client-auth.ts
* Use `private_key_jwt` instead of incorrect `client_secret_jwt` as authentication method for confidential clients
* style
* code split
* dead code removal
* Represent missing client auth with a `null` instead of "none" when storing request data.
* Allow storing `null` in authorization_request's `clientAuth` json column
* document
* tidy
* Remove non-standard behavior that allowed client to authenticate through JAR
* Improved error messages
* Parse JSON encoded Authorization Request Parameters
* Use `application/x-www-form-urlencoded` content instead of JSON for OAuth requests
Fixes: #3723
* tidy
* tidy
* tidy
* tidy
* code style
* remove un-necessary checks
* tidy
* Pre-process number too
* improved type checking
* add missing exports
* fix merge conflict
* tidy
* Remove invalid default for `code_challenge_method` authorization request parameter
* tidy
* Delete inaccurate changeset
* PR comment
* tidy
* Update OAuth client credentials factory to return headers and payload separately.
* tidy
* Renamed `clientAuthCheck` to `validateClientAuth`
* Validate presence of DPoP proofs sooner when processing token requests.
Fixes: #3859
* Protect against concurrent use of request code
* tidy
* tidy
* Update packages/oauth/oauth-provider/src/client/client.ts
Co-authored-by: devin ivy <devinivy@gmail.com>
* Review comments
* Add missing `exp` claim in client attestation JWT
* fixup! Review comments
* Review comments
* Refactor: explicit optionality of unsigned JAR issuer & audience
* Use client attestation's `exp` claim to determine the life time of JWT's `jti` nonce.
* Fix PDS: consumeRequestCode should delete request data
* tidy
* tidy
* Unused code removal
* Restore "Native clients must authenticate using "none" method" check
* tidy
* tidy
* cleanup
* comment
* Allow missing DPoP header during PAR request if `dpop_jkt` is provided
* tidy
---------
Co-authored-by: devin ivy <devinivy@gmail.com>
* Refactor route rate limiter builder
* Refactor RouteRateLimiter handle method to improve bypass logic and return type
* Use `redis` as rate limit db when available
* Return atproto handle in identity resolution result
* Use resolved handle or did instead of raw input as "login_hint"
* Normalize and validate `login_hint` in oauth request properties
* Properly validate JWK `htu` claim by enforcing URL without query or fragment
* type fix
* Return DPoP validation result from `authenticateRequest`
* Log clients using invalid "htu" claim in DPoP proof
* review comments
* fix lint
* tidy
* rename dpop result to dpop proof
Currently, the `lingui extract` command is being run as part of the `build` and `dev` commands. This causes very large diffs in PRs, even when no change are made to `.po` files.
With this change, only running `pnpm i18n` (from the root folder), or `pnpm i18n:extract` (from ui libs that support it) will cause the PO files to be re-computed.
* 🐛 Fetch record from pds if appview fails to find it for ozone
* ✨ Resolve and etch from pds without auth
* ♻️ Refactor and cleanup
* ✅ Fix tests
* ✅ Fix tests
* 🚨 Fix linter issue
* 🧹 Cleanup
* Allow proxying of dpop bound requests by using service auth instead, for the `getSession` endpoint.
* Show `getSession` data in example app
* Add scope
* strings
* cleanup
* tidy
* tidy
* Add transition:email scope to example app
* strings
* changeset
* pr comments
* Handle resolution improvements
On PDS, only perform resolution if appview is not configured.
On appview, never perform resolution, only rely on dataplane.
* changeset
* appview: add lookup_unidirectional to GetDidsByHandles
* appview: plumb lookup_unidirectional for handle lookup
* note
---------
Co-authored-by: Devin Ivy <devinivy@gmail.com>
* First vouch implementation
* Remove unneeded endpoints
* wip
* ✨ wip
* ✨ Process jetstream events through p-queue and add tests
* ✅ Add test for cursor update
* 🐛 Use utc time to update updatedAt
* 🧹 Cleanup
* 🔨 Fix pnpm versioning issues
* ✨ Replace jetstream lib with manual implementation
* ✨ Remove unnecessary 3p dep
* ✨ Add e2e test for jetstream
* 🚨 Fix import
* 🧹 Remove unnecessary property
* ✨ Fix dev-env and add profile to verification view in ozone
* ✅ Add profile type
* ✨ Add backpressure handling to jetstream listener
* ✨ Use WebSocketKeepAlive from xrpc-server and replace partysocket
* ✨ Add a new verifier role to ozone team meber roles
* 📝 Run codegen
* 🐛 Fix auth check
* 🐛 Fix test failure check
* 🚨 Fix json formatting
* 🐛 Fix team role check
* 🚧 Checking failing test
* ✅ Fix tests
* ✨ Address review comments
* ✨ Add xrpc-server to version
* 🚨 Fix linter issue
* 🚨 Fix linter issue
* ✨ Resolve race condition in cursor update
* ✅ Add verification check on profile
* 🐛 Fix missing cid in test and firehose cursor
* ✨ Fix test
* ✨ Add record validation for verification and separate xrpc-server version
* ✨ Return error object for failed revocations
* ✨ Add re-login on expired session case
* 📝 Fix typo
---------
Co-authored-by: rafael <rafael@blueskyweb.xyz>
* Allow `:` chars in url path parts
* Allow customizing contrast and hue colors
* Allow customizing contrast and hue colors
* Use white as primary contrast color
* Fix buttons alignment and labels in "My Devices" section
* Add a `<title>` tag to all pages
* Properly display the "lastSeenAt" date
* Improve display of clients & devices
* tidy
* code split
* Add definition for `ConventionalOAuthClientId`
* Remove hard coded `client_name` from loopback client metadata
* Code factorization
* Fix `<title>` of branding page
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.
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):
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()`
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):
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`:
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.
Otherwise, for plain data objects that don't require a `$type`, just use the namespace types for type annotations without changing the construction logic:
- 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
constbody=awaitctx.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)) {
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`:
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:
constsize=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
constresult1=schema.$safeParse(data)// strict: true by default
The `Client` class has a `strictResponseProcessing` option that controls the default strict mode for all XRPC calls:
```typescript
constclient=newClient(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)) { ... }
### `$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:
`$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.
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
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(errinstanceofXrpcFetchError)
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
awaitexpect(
someAsyncFn(),
).rejects.toSatisfy((err)=>{
assert(errinstanceofSomeError)
expect(err.cause).toBeInstanceOf(TypeError)
expect(err.message).toContain('failed')
returntrue// must return true
})
```
Always `return true` at the end of `toSatisfy` callbacks.
For simple "it throws" checks, `toThrow()` is fine:
Copyright (c) 2022-2025 Bluesky 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>).
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>).
The Authenticated Transfer Protocol ("ATP" or "atproto") is a decentralized social media protocol, developed by [Bluesky PBC](https://bsky.social). Learn more at:
The Authenticated Transfer Protocol ("ATP" or "atproto") is a decentralized social media protocol, developed by [Bluesky Social PBC](https://bsky.social). Learn more at:
- [Overview and Guides](https://atproto.com/guides/overview) 👈 Best starting point
- [Overview and Guides](https://atproto.com/guides/overview) 👈 Best starting point
- [Github Discussions](https://github.com/bluesky-social/atproto/discussions) 👈 Great place to ask questions
- [Github Discussions](https://github.com/bluesky-social/atproto/discussions) 👈 Great place to ask questions
@@ -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)
- 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.
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).
"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."
"description":"Show followed users at the top of all replies."
}
}
}
}
},
},
@@ -530,6 +614,34 @@
}
}
}
}
},
},
"verificationPrefs":{
"type":"object",
"description":"Preferences for how verified accounts appear in the app.",
"required":[],
"properties":{
"hideBadges":{
"description":"Hide the blue check badges for verified accounts and trusted verifiers.",
"type":"boolean",
"default":false
}
}
},
"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":{
"postInteractionSettingsPref":{
"type":"object",
"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.",
"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.",
@@ -559,6 +671,42 @@
}
}
}
}
}
}
},
"statusView":{
"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.",
"knownValues":["app.bsky.actor.status#live"]
},
"record":{"type":"unknown"},
"embed":{
"type":"union",
"description":"An optional embed associated with the status.",
"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.",
"description":"Returns server-computed Age Assurance state, if available, and any additional metadata needed to compute Age Assurance state client-side.",
"description":"Creates a private bookmark for the specified record. Currently, only `app.bsky.feed.post` records are supported. Requires authentication.",
"input":{
"encoding":"application/json",
"schema":{
"type":"object",
"required":["uri","cid"],
"properties":{
"uri":{"type":"string","format":"at-uri"},
"cid":{"type":"string","format":"cid"}
}
}
},
"errors":[
{
"name":"UnsupportedCollection",
"description":"The URI to be bookmarked is for an unsupported collection."
"description":"Deletes a private bookmark for the specified record. Currently, only `app.bsky.feed.post` records are supported. Requires authentication.",
"input":{
"encoding":"application/json",
"schema":{
"type":"object",
"required":["uri"],
"properties":{
"uri":{"type":"string","format":"at-uri"}
}
}
},
"errors":[
{
"name":"UnsupportedCollection",
"description":"The URI to be bookmarked is for an unsupported collection."
"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.",
"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.",
"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.",
"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.",
"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.",
"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.",
"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.",
"description":"Find posts matching search criteria, returning views of those posts.",
"description":"Find posts matching search criteria, returning views of those posts. Note that this API endpoint may require authentication (eg, not public) for some service providers and implementations.",
"description":"Enumerates the lists created by the session user, and includes membership information about `actor` in those lists. Only supports curation and moderation lists (no reference lists, used in starter packs). Requires auth.",
"parameters":{
"type":"params",
"required":["actor"],
"properties":{
"actor":{
"type":"string",
"format":"at-identifier",
"description":"The account (actor) to check for membership."
},
"limit":{
"type":"integer",
"minimum":1,
"maximum":100,
"default":50
},
"cursor":{"type":"string"},
"purposes":{
"type":"array",
"description":"Optional filter by list purpose. If not specified, all supported types are returned.",
"description":"Enumerates the starter packs created by the session user, and includes membership information about `actor` in those starter packs. Requires auth.",
"parameters":{
"type":"params",
"required":["actor"],
"properties":{
"actor":{
"type":"string",
"format":"at-identifier",
"description":"The account (actor) to check for membership."
"description":"The inverse of registerPush - inform a specified service that push notifications should no longer be sent to the given token for the requesting account. Requires auth.",
"description":"This post has more parents that were not present in the response. This is just a boolean, without the number of parents."
},
"moreReplies":{
"type":"integer",
"description":"This post has more replies that were not present in the response. This is a numeric value, which is best-effort and might not be accurate."
},
"opThread":{
"type":"boolean",
"description":"This post is part of a contiguous thread by the OP from the thread root. Many different OP threads can happen in the same thread."
},
"hiddenByThreadgate":{
"type":"boolean",
"description":"The threadgate created by the author indicates this post as a reply to be hidden for everyone consuming the thread."
},
"mutedByViewer":{
"type":"boolean",
"description":"This is by an account muted by the viewer requesting it."
"description":"Returns the current state of the age assurance process for an account. This is used to check if the user has completed age assurance or if further action is required.",
"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)."
"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."
"description":"(NOTE: this endpoint is under development and WILL change without notice. Don't use it until it is moved out of `unspecced` or your application WILL break) Get additional posts under a thread e.g. replies hidden by threadgate. Based on an anchor post at any depth of the tree, returns top-level replies below that anchor. It does not include ancestors nor the anchor itself. This should be called after exhausting `app.bsky.unspecced.getPostThreadV2`. Does not require auth, but additional metadata and filtering will be applied for authed requests.",
"parameters":{
"type":"params",
"required":["anchor"],
"properties":{
"anchor":{
"type":"string",
"format":"at-uri",
"description":"Reference (AT-URI) to post record. This is the anchor post."
}
}
},
"output":{
"encoding":"application/json",
"schema":{
"type":"object",
"required":["thread"],
"properties":{
"thread":{
"type":"array",
"description":"A flat list of other thread items. The depth of each item is indicated by the depth property inside the item.",
"items":{
"type":"ref",
"ref":"#threadItem"
}
}
}
}
}
},
"threadItem":{
"type":"object",
"required":["uri","depth","value"],
"properties":{
"uri":{
"type":"string",
"format":"at-uri"
},
"depth":{
"type":"integer",
"description":"The nesting level of this item in the thread. Depth 0 means the anchor item. Items above have negative depths, items below have positive depths."
"description":"(NOTE: this endpoint is under development and WILL change without notice. Don't use it until it is moved out of `unspecced` or your application WILL break) Get posts in a thread. It is based in an anchor post at any depth of the tree, and returns posts above it (recursively resolving the parent, without further branching to their replies) and below it (recursive replies, with branching to their replies). Does not require auth, but additional metadata and filtering will be applied for authed requests.",
"parameters":{
"type":"params",
"required":["anchor"],
"properties":{
"anchor":{
"type":"string",
"format":"at-uri",
"description":"Reference (AT-URI) to post record. This is the anchor post, and the thread will be built around it. It can be any post in the tree, not necessarily a root post."
},
"above":{
"type":"boolean",
"description":"Whether to include parents above the anchor.",
"default":true
},
"below":{
"type":"integer",
"description":"How many levels of replies to include below the anchor.",
"default":6,
"minimum":0,
"maximum":20
},
"branchingFactor":{
"type":"integer",
"description":"Maximum of replies to include at each level of the thread, except for the direct replies to the anchor, which are (NOTE: currently, during unspecced phase) all returned (NOTE: later they might be paginated).",
"default":10,
"minimum":0,
"maximum":100
},
"sort":{
"type":"string",
"description":"Sorting for the thread replies.",
"knownValues":["newest","oldest","top"],
"default":"oldest"
}
}
},
"output":{
"encoding":"application/json",
"schema":{
"type":"object",
"required":["thread","hasOtherReplies"],
"properties":{
"thread":{
"type":"array",
"description":"A flat list of thread items. The depth of each item is indicated by the depth property inside the item.",
"items":{
"type":"ref",
"ref":"#threadItem"
}
},
"threadgate":{
"type":"ref",
"ref":"app.bsky.feed.defs#threadgateView"
},
"hasOtherReplies":{
"type":"boolean",
"description":"Whether this thread has additional replies. If true, a call can be made to the `getPostThreadOtherV2` endpoint to retrieve them."
}
}
}
}
},
"threadItem":{
"type":"object",
"required":["uri","depth","value"],
"properties":{
"uri":{
"type":"string",
"format":"at-uri"
},
"depth":{
"type":"integer",
"description":"The nesting level of this item in the thread. Depth 0 means the anchor item. Items above have negative depths, items below have positive depths."
"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."
}
}
}
}
}
}
}
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.