pydantic model generator for atproto lexicons
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor: modular architecture with builtin lexicon support

- split lib.rs into parser, types, codegen, and builtin modules
- implement internal ref resolution (#localDef -> ClassName)
- implement external ref resolution (com.atproto.repo.strongRef -> ComAtprotoRepoStrongRef)
- bundle 81 com.atproto.* lexicons at compile time for automatic resolution
- add dynamic versioning from git tags in CI
- add tests for ref resolution

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

+3888 -217
+9 -1
.github/workflows/publish.yml
··· 16 16 with: 17 17 fetch-depth: 0 18 18 19 - - name: "Install uv" 19 + - name: Set version from git tag 20 + run: | 21 + VERSION="${GITHUB_REF_NAME#v}" 22 + echo "VERSION=$VERSION" >> $GITHUB_ENV 23 + sed -i "s/^version = .*/version = \"$VERSION\"/" Cargo.toml 24 + echo "Set version to $VERSION" 25 + grep "^version" Cargo.toml 26 + 27 + - name: Install uv 20 28 uses: astral-sh/setup-uv@v7 21 29 22 30 - name: Build
+21
Cargo.lock
··· 271 271 "pyo3", 272 272 "serde", 273 273 "serde_json", 274 + "thiserror", 274 275 ] 275 276 276 277 [[package]] ··· 486 487 version = "0.13.3" 487 488 source = "registry+https://github.com/rust-lang/crates.io-index" 488 489 checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" 490 + 491 + [[package]] 492 + name = "thiserror" 493 + version = "2.0.17" 494 + source = "registry+https://github.com/rust-lang/crates.io-index" 495 + checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" 496 + dependencies = [ 497 + "thiserror-impl", 498 + ] 499 + 500 + [[package]] 501 + name = "thiserror-impl" 502 + version = "2.0.17" 503 + source = "registry+https://github.com/rust-lang/crates.io-index" 504 + checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" 505 + dependencies = [ 506 + "proc-macro2", 507 + "quote", 508 + "syn", 509 + ] 489 510 490 511 [[package]] 491 512 name = "time"
+2 -1
Cargo.toml
··· 1 1 [package] 2 2 name = "pmgfal" 3 - version = "0.1.0" 3 + version = "0.0.0" # set dynamically from git tag in CI 4 4 edition = "2021" 5 5 license = "MIT" 6 6 description = "pydantic model generator for atproto lexicons" ··· 15 15 serde = { version = "1.0", features = ["derive"] } 16 16 serde_json = "1.0" 17 17 heck = "0.5" 18 + thiserror = "2.0" 18 19 19 20 [profile.release] 20 21 lto = true
+71
lexicons/com/atproto/admin/defs.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.admin.defs", 4 + "defs": { 5 + "statusAttr": { 6 + "type": "object", 7 + "required": ["applied"], 8 + "properties": { 9 + "applied": { "type": "boolean" }, 10 + "ref": { "type": "string" } 11 + } 12 + }, 13 + "accountView": { 14 + "type": "object", 15 + "required": ["did", "handle", "indexedAt"], 16 + "properties": { 17 + "did": { "type": "string", "format": "did" }, 18 + "handle": { "type": "string", "format": "handle" }, 19 + "email": { "type": "string" }, 20 + "relatedRecords": { "type": "array", "items": { "type": "unknown" } }, 21 + "indexedAt": { "type": "string", "format": "datetime" }, 22 + "invitedBy": { 23 + "type": "ref", 24 + "ref": "com.atproto.server.defs#inviteCode" 25 + }, 26 + "invites": { 27 + "type": "array", 28 + "items": { 29 + "type": "ref", 30 + "ref": "com.atproto.server.defs#inviteCode" 31 + } 32 + }, 33 + "invitesDisabled": { "type": "boolean" }, 34 + "emailConfirmedAt": { "type": "string", "format": "datetime" }, 35 + "inviteNote": { "type": "string" }, 36 + "deactivatedAt": { "type": "string", "format": "datetime" }, 37 + "threatSignatures": { 38 + "type": "array", 39 + "items": { 40 + "type": "ref", 41 + "ref": "#threatSignature" 42 + } 43 + } 44 + } 45 + }, 46 + "repoRef": { 47 + "type": "object", 48 + "required": ["did"], 49 + "properties": { 50 + "did": { "type": "string", "format": "did" } 51 + } 52 + }, 53 + "repoBlobRef": { 54 + "type": "object", 55 + "required": ["did", "cid"], 56 + "properties": { 57 + "did": { "type": "string", "format": "did" }, 58 + "cid": { "type": "string", "format": "cid" }, 59 + "recordUri": { "type": "string", "format": "at-uri" } 60 + } 61 + }, 62 + "threatSignature": { 63 + "type": "object", 64 + "required": ["property", "value"], 65 + "properties": { 66 + "property": { "type": "string" }, 67 + "value": { "type": "string" } 68 + } 69 + } 70 + } 71 + }
+20
lexicons/com/atproto/admin/deleteAccount.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.admin.deleteAccount", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Delete a user account as an administrator.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["did"], 13 + "properties": { 14 + "did": { "type": "string", "format": "did" } 15 + } 16 + } 17 + } 18 + } 19 + } 20 + }
+24
lexicons/com/atproto/admin/disableAccountInvites.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.admin.disableAccountInvites", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Disable an account from receiving new invite codes, but does not invalidate existing codes.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["account"], 13 + "properties": { 14 + "account": { "type": "string", "format": "did" }, 15 + "note": { 16 + "type": "string", 17 + "description": "Optional reason for disabled invites." 18 + } 19 + } 20 + } 21 + } 22 + } 23 + } 24 + }
+26
lexicons/com/atproto/admin/disableInviteCodes.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.admin.disableInviteCodes", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Disable some set of codes and/or all codes associated with a set of users.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "properties": { 13 + "codes": { 14 + "type": "array", 15 + "items": { "type": "string" } 16 + }, 17 + "accounts": { 18 + "type": "array", 19 + "items": { "type": "string" } 20 + } 21 + } 22 + } 23 + } 24 + } 25 + } 26 + }
+24
lexicons/com/atproto/admin/enableAccountInvites.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.admin.enableAccountInvites", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Re-enable an account's ability to receive invite codes.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["account"], 13 + "properties": { 14 + "account": { "type": "string", "format": "did" }, 15 + "note": { 16 + "type": "string", 17 + "description": "Optional reason for enabled invites." 18 + } 19 + } 20 + } 21 + } 22 + } 23 + } 24 + }
+24
lexicons/com/atproto/admin/getAccountInfo.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.admin.getAccountInfo", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get details about an account.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["did"], 11 + "properties": { 12 + "did": { "type": "string", "format": "did" } 13 + } 14 + }, 15 + "output": { 16 + "encoding": "application/json", 17 + "schema": { 18 + "type": "ref", 19 + "ref": "com.atproto.admin.defs#accountView" 20 + } 21 + } 22 + } 23 + } 24 + }
+36
lexicons/com/atproto/admin/getAccountInfos.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.admin.getAccountInfos", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get details about some accounts.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["dids"], 11 + "properties": { 12 + "dids": { 13 + "type": "array", 14 + "items": { "type": "string", "format": "did" } 15 + } 16 + } 17 + }, 18 + "output": { 19 + "encoding": "application/json", 20 + "schema": { 21 + "type": "object", 22 + "required": ["infos"], 23 + "properties": { 24 + "infos": { 25 + "type": "array", 26 + "items": { 27 + "type": "ref", 28 + "ref": "com.atproto.admin.defs#accountView" 29 + } 30 + } 31 + } 32 + } 33 + } 34 + } 35 + } 36 + }
+44
lexicons/com/atproto/admin/getInviteCodes.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.admin.getInviteCodes", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get an admin view of invite codes.", 8 + "parameters": { 9 + "type": "params", 10 + "properties": { 11 + "sort": { 12 + "type": "string", 13 + "knownValues": ["recent", "usage"], 14 + "default": "recent" 15 + }, 16 + "limit": { 17 + "type": "integer", 18 + "minimum": 1, 19 + "maximum": 500, 20 + "default": 100 21 + }, 22 + "cursor": { "type": "string" } 23 + } 24 + }, 25 + "output": { 26 + "encoding": "application/json", 27 + "schema": { 28 + "type": "object", 29 + "required": ["codes"], 30 + "properties": { 31 + "cursor": { "type": "string" }, 32 + "codes": { 33 + "type": "array", 34 + "items": { 35 + "type": "ref", 36 + "ref": "com.atproto.server.defs#inviteCode" 37 + } 38 + } 39 + } 40 + } 41 + } 42 + } 43 + } 44 + }
+43
lexicons/com/atproto/admin/getSubjectStatus.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.admin.getSubjectStatus", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get the service-specific admin status of a subject (account, record, or blob).", 8 + "parameters": { 9 + "type": "params", 10 + "properties": { 11 + "did": { "type": "string", "format": "did" }, 12 + "uri": { "type": "string", "format": "at-uri" }, 13 + "blob": { "type": "string", "format": "cid" } 14 + } 15 + }, 16 + "output": { 17 + "encoding": "application/json", 18 + "schema": { 19 + "type": "object", 20 + "required": ["subject"], 21 + "properties": { 22 + "subject": { 23 + "type": "union", 24 + "refs": [ 25 + "com.atproto.admin.defs#repoRef", 26 + "com.atproto.repo.strongRef", 27 + "com.atproto.admin.defs#repoBlobRef" 28 + ] 29 + }, 30 + "takedown": { 31 + "type": "ref", 32 + "ref": "com.atproto.admin.defs#statusAttr" 33 + }, 34 + "deactivated": { 35 + "type": "ref", 36 + "ref": "com.atproto.admin.defs#statusAttr" 37 + } 38 + } 39 + } 40 + } 41 + } 42 + } 43 + }
+40
lexicons/com/atproto/admin/searchAccounts.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.admin.searchAccounts", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get list of accounts that matches your search query.", 8 + "parameters": { 9 + "type": "params", 10 + "properties": { 11 + "email": { "type": "string" }, 12 + "cursor": { "type": "string" }, 13 + "limit": { 14 + "type": "integer", 15 + "minimum": 1, 16 + "maximum": 100, 17 + "default": 50 18 + } 19 + } 20 + }, 21 + "output": { 22 + "encoding": "application/json", 23 + "schema": { 24 + "type": "object", 25 + "required": ["accounts"], 26 + "properties": { 27 + "cursor": { "type": "string" }, 28 + "accounts": { 29 + "type": "array", 30 + "items": { 31 + "type": "ref", 32 + "ref": "com.atproto.admin.defs#accountView" 33 + } 34 + } 35 + } 36 + } 37 + } 38 + } 39 + } 40 + }
+37
lexicons/com/atproto/admin/sendEmail.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.admin.sendEmail", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Send email to a user's account email address.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["recipientDid", "content", "senderDid"], 13 + "properties": { 14 + "recipientDid": { "type": "string", "format": "did" }, 15 + "content": { "type": "string" }, 16 + "subject": { "type": "string" }, 17 + "senderDid": { "type": "string", "format": "did" }, 18 + "comment": { 19 + "type": "string", 20 + "description": "Additional comment by the sender that won't be used in the email itself but helpful to provide more context for moderators/reviewers" 21 + } 22 + } 23 + } 24 + }, 25 + "output": { 26 + "encoding": "application/json", 27 + "schema": { 28 + "type": "object", 29 + "required": ["sent"], 30 + "properties": { 31 + "sent": { "type": "boolean" } 32 + } 33 + } 34 + } 35 + } 36 + } 37 + }
+25
lexicons/com/atproto/admin/updateAccountEmail.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.admin.updateAccountEmail", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Administrative action to update an account's email.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["account", "email"], 13 + "properties": { 14 + "account": { 15 + "type": "string", 16 + "format": "at-identifier", 17 + "description": "The handle or DID of the repo." 18 + }, 19 + "email": { "type": "string" } 20 + } 21 + } 22 + } 23 + } 24 + } 25 + }
+21
lexicons/com/atproto/admin/updateAccountHandle.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.admin.updateAccountHandle", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Administrative action to update an account's handle.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["did", "handle"], 13 + "properties": { 14 + "did": { "type": "string", "format": "did" }, 15 + "handle": { "type": "string", "format": "handle" } 16 + } 17 + } 18 + } 19 + } 20 + } 21 + }
+21
lexicons/com/atproto/admin/updateAccountPassword.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.admin.updateAccountPassword", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Update the password for a user account as an administrator.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["did", "password"], 13 + "properties": { 14 + "did": { "type": "string", "format": "did" }, 15 + "password": { "type": "string" } 16 + } 17 + } 18 + } 19 + } 20 + } 21 + }
+56
lexicons/com/atproto/admin/updateSubjectStatus.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.admin.updateSubjectStatus", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Update the service-specific admin status of a subject (account, record, or blob).", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["subject"], 13 + "properties": { 14 + "subject": { 15 + "type": "union", 16 + "refs": [ 17 + "com.atproto.admin.defs#repoRef", 18 + "com.atproto.repo.strongRef", 19 + "com.atproto.admin.defs#repoBlobRef" 20 + ] 21 + }, 22 + "takedown": { 23 + "type": "ref", 24 + "ref": "com.atproto.admin.defs#statusAttr" 25 + }, 26 + "deactivated": { 27 + "type": "ref", 28 + "ref": "com.atproto.admin.defs#statusAttr" 29 + } 30 + } 31 + } 32 + }, 33 + "output": { 34 + "encoding": "application/json", 35 + "schema": { 36 + "type": "object", 37 + "required": ["subject"], 38 + "properties": { 39 + "subject": { 40 + "type": "union", 41 + "refs": [ 42 + "com.atproto.admin.defs#repoRef", 43 + "com.atproto.repo.strongRef", 44 + "com.atproto.admin.defs#repoBlobRef" 45 + ] 46 + }, 47 + "takedown": { 48 + "type": "ref", 49 + "ref": "com.atproto.admin.defs#statusAttr" 50 + } 51 + } 52 + } 53 + } 54 + } 55 + } 56 + }
+29
lexicons/com/atproto/identity/getRecommendedDidCredentials.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.identity.getRecommendedDidCredentials", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Describe the credentials that should be included in the DID doc of an account that is migrating to this service.", 8 + "output": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "properties": { 13 + "rotationKeys": { 14 + "description": "Recommended rotation keys for PLC dids. Should be undefined (or ignored) for did:webs.", 15 + "type": "array", 16 + "items": { "type": "string" } 17 + }, 18 + "alsoKnownAs": { 19 + "type": "array", 20 + "items": { "type": "string" } 21 + }, 22 + "verificationMethods": { "type": "unknown" }, 23 + "services": { "type": "unknown" } 24 + } 25 + } 26 + } 27 + } 28 + } 29 + }
+10
lexicons/com/atproto/identity/requestPlcOperationSignature.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.identity.requestPlcOperationSignature", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Request an email with a code to in order to request a signed PLC operation. Requires Auth." 8 + } 9 + } 10 + }
+31
lexicons/com/atproto/identity/resolveHandle.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.identity.resolveHandle", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Resolves a handle (domain name) to a DID.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["handle"], 11 + "properties": { 12 + "handle": { 13 + "type": "string", 14 + "format": "handle", 15 + "description": "The handle to resolve." 16 + } 17 + } 18 + }, 19 + "output": { 20 + "encoding": "application/json", 21 + "schema": { 22 + "type": "object", 23 + "required": ["did"], 24 + "properties": { 25 + "did": { "type": "string", "format": "did" } 26 + } 27 + } 28 + } 29 + } 30 + } 31 + }
+45
lexicons/com/atproto/identity/signPlcOperation.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.identity.signPlcOperation", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Signs a PLC operation to update some value(s) in the requesting DID's document.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "properties": { 13 + "token": { 14 + "description": "A token received through com.atproto.identity.requestPlcOperationSignature", 15 + "type": "string" 16 + }, 17 + "rotationKeys": { 18 + "type": "array", 19 + "items": { "type": "string" } 20 + }, 21 + "alsoKnownAs": { 22 + "type": "array", 23 + "items": { "type": "string" } 24 + }, 25 + "verificationMethods": { "type": "unknown" }, 26 + "services": { "type": "unknown" } 27 + } 28 + } 29 + }, 30 + "output": { 31 + "encoding": "application/json", 32 + "schema": { 33 + "type": "object", 34 + "required": ["operation"], 35 + "properties": { 36 + "operation": { 37 + "type": "unknown", 38 + "description": "A signed DID PLC operation." 39 + } 40 + } 41 + } 42 + } 43 + } 44 + } 45 + }
+20
lexicons/com/atproto/identity/submitPlcOperation.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.identity.submitPlcOperation", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Validates a PLC operation to ensure that it doesn't violate a service's constraints or get the identity into a bad state, then submits it to the PLC registry", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["operation"], 13 + "properties": { 14 + "operation": { "type": "unknown" } 15 + } 16 + } 17 + } 18 + } 19 + } 20 + }
+24
lexicons/com/atproto/identity/updateHandle.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.identity.updateHandle", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Updates the current account's handle. Verifies handle validity, and updates did:plc document if necessary. Implemented by PDS, and requires auth.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["handle"], 13 + "properties": { 14 + "handle": { 15 + "type": "string", 16 + "format": "handle", 17 + "description": "The new handle." 18 + } 19 + } 20 + } 21 + } 22 + } 23 + } 24 + }
+156
lexicons/com/atproto/label/defs.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.label.defs", 4 + "defs": { 5 + "label": { 6 + "type": "object", 7 + "description": "Metadata tag on an atproto resource (eg, repo or record).", 8 + "required": ["src", "uri", "val", "cts"], 9 + "properties": { 10 + "ver": { 11 + "type": "integer", 12 + "description": "The AT Protocol version of the label object." 13 + }, 14 + "src": { 15 + "type": "string", 16 + "format": "did", 17 + "description": "DID of the actor who created this label." 18 + }, 19 + "uri": { 20 + "type": "string", 21 + "format": "uri", 22 + "description": "AT URI of the record, repository (account), or other resource that this label applies to." 23 + }, 24 + "cid": { 25 + "type": "string", 26 + "format": "cid", 27 + "description": "Optionally, CID specifying the specific version of 'uri' resource this label applies to." 28 + }, 29 + "val": { 30 + "type": "string", 31 + "maxLength": 128, 32 + "description": "The short string name of the value or type of this label." 33 + }, 34 + "neg": { 35 + "type": "boolean", 36 + "description": "If true, this is a negation label, overwriting a previous label." 37 + }, 38 + "cts": { 39 + "type": "string", 40 + "format": "datetime", 41 + "description": "Timestamp when this label was created." 42 + }, 43 + "exp": { 44 + "type": "string", 45 + "format": "datetime", 46 + "description": "Timestamp at which this label expires (no longer applies)." 47 + }, 48 + "sig": { 49 + "type": "bytes", 50 + "description": "Signature of dag-cbor encoded label." 51 + } 52 + } 53 + }, 54 + "selfLabels": { 55 + "type": "object", 56 + "description": "Metadata tags on an atproto record, published by the author within the record.", 57 + "required": ["values"], 58 + "properties": { 59 + "values": { 60 + "type": "array", 61 + "items": { "type": "ref", "ref": "#selfLabel" }, 62 + "maxLength": 10 63 + } 64 + } 65 + }, 66 + "selfLabel": { 67 + "type": "object", 68 + "description": "Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel.", 69 + "required": ["val"], 70 + "properties": { 71 + "val": { 72 + "type": "string", 73 + "maxLength": 128, 74 + "description": "The short string name of the value or type of this label." 75 + } 76 + } 77 + }, 78 + "labelValueDefinition": { 79 + "type": "object", 80 + "description": "Declares a label value and its expected interpretations and behaviors.", 81 + "required": ["identifier", "severity", "blurs", "locales"], 82 + "properties": { 83 + "identifier": { 84 + "type": "string", 85 + "description": "The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).", 86 + "maxLength": 100, 87 + "maxGraphemes": 100 88 + }, 89 + "severity": { 90 + "type": "string", 91 + "description": "How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.", 92 + "knownValues": ["inform", "alert", "none"] 93 + }, 94 + "blurs": { 95 + "type": "string", 96 + "description": "What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing.", 97 + "knownValues": ["content", "media", "none"] 98 + }, 99 + "defaultSetting": { 100 + "type": "string", 101 + "description": "The default setting for this label.", 102 + "knownValues": ["ignore", "warn", "hide"], 103 + "default": "warn" 104 + }, 105 + "adultOnly": { 106 + "type": "boolean", 107 + "description": "Does the user need to have adult content enabled in order to configure this label?" 108 + }, 109 + "locales": { 110 + "type": "array", 111 + "items": { "type": "ref", "ref": "#labelValueDefinitionStrings" } 112 + } 113 + } 114 + }, 115 + "labelValueDefinitionStrings": { 116 + "type": "object", 117 + "description": "Strings which describe the label in the UI, localized into a specific language.", 118 + "required": ["lang", "name", "description"], 119 + "properties": { 120 + "lang": { 121 + "type": "string", 122 + "description": "The code of the language these strings are written in.", 123 + "format": "language" 124 + }, 125 + "name": { 126 + "type": "string", 127 + "description": "A short human-readable name for the label.", 128 + "maxGraphemes": 64, 129 + "maxLength": 640 130 + }, 131 + "description": { 132 + "type": "string", 133 + "description": "A longer description of what the label means and why it might be applied.", 134 + "maxGraphemes": 10000, 135 + "maxLength": 100000 136 + } 137 + } 138 + }, 139 + "labelValue": { 140 + "type": "string", 141 + "knownValues": [ 142 + "!hide", 143 + "!no-promote", 144 + "!warn", 145 + "!no-unauthenticated", 146 + "dmca-violation", 147 + "doxxing", 148 + "porn", 149 + "sexual", 150 + "nudity", 151 + "nsfl", 152 + "gore" 153 + ] 154 + } 155 + } 156 + }
+47
lexicons/com/atproto/label/queryLabels.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.label.queryLabels", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Find labels relevant to the provided AT-URI patterns. Public endpoint for moderation services, though may return different or additional results with auth.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["uriPatterns"], 11 + "properties": { 12 + "uriPatterns": { 13 + "type": "array", 14 + "items": { "type": "string" }, 15 + "description": "List of AT URI patterns to match (boolean 'OR'). Each may be a prefix (ending with '*'; will match inclusive of the string leading to '*'), or a full URI." 16 + }, 17 + "sources": { 18 + "type": "array", 19 + "items": { "type": "string", "format": "did" }, 20 + "description": "Optional list of label sources (DIDs) to filter on." 21 + }, 22 + "limit": { 23 + "type": "integer", 24 + "minimum": 1, 25 + "maximum": 250, 26 + "default": 50 27 + }, 28 + "cursor": { "type": "string" } 29 + } 30 + }, 31 + "output": { 32 + "encoding": "application/json", 33 + "schema": { 34 + "type": "object", 35 + "required": ["labels"], 36 + "properties": { 37 + "cursor": { "type": "string" }, 38 + "labels": { 39 + "type": "array", 40 + "items": { "type": "ref", "ref": "com.atproto.label.defs#label" } 41 + } 42 + } 43 + } 44 + } 45 + } 46 + } 47 + }
+50
lexicons/com/atproto/label/subscribeLabels.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.label.subscribeLabels", 4 + "defs": { 5 + "main": { 6 + "type": "subscription", 7 + "description": "Subscribe to stream of labels (and negations). Public endpoint implemented by mod services. Uses same sequencing scheme as repo event stream.", 8 + "parameters": { 9 + "type": "params", 10 + "properties": { 11 + "cursor": { 12 + "type": "integer", 13 + "description": "The last known event seq number to backfill from." 14 + } 15 + } 16 + }, 17 + "message": { 18 + "schema": { 19 + "type": "union", 20 + "refs": ["#labels", "#info"] 21 + } 22 + }, 23 + "errors": [{ "name": "FutureCursor" }] 24 + }, 25 + "labels": { 26 + "type": "object", 27 + "required": ["seq", "labels"], 28 + "properties": { 29 + "seq": { "type": "integer" }, 30 + "labels": { 31 + "type": "array", 32 + "items": { "type": "ref", "ref": "com.atproto.label.defs#label" } 33 + } 34 + } 35 + }, 36 + "info": { 37 + "type": "object", 38 + "required": ["name"], 39 + "properties": { 40 + "name": { 41 + "type": "string", 42 + "knownValues": ["OutdatedCursor"] 43 + }, 44 + "message": { 45 + "type": "string" 46 + } 47 + } 48 + } 49 + } 50 + }
+71
lexicons/com/atproto/moderation/createReport.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.moderation.createReport", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Submit a moderation report regarding an atproto account or record. Implemented by moderation services (with PDS proxying), and requires auth.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["reasonType", "subject"], 13 + "properties": { 14 + "reasonType": { 15 + "type": "ref", 16 + "description": "Indicates the broad category of violation the report is for.", 17 + "ref": "com.atproto.moderation.defs#reasonType" 18 + }, 19 + "reason": { 20 + "type": "string", 21 + "maxGraphemes": 2000, 22 + "maxLength": 20000, 23 + "description": "Additional context about the content and violation." 24 + }, 25 + "subject": { 26 + "type": "union", 27 + "refs": [ 28 + "com.atproto.admin.defs#repoRef", 29 + "com.atproto.repo.strongRef" 30 + ] 31 + } 32 + } 33 + } 34 + }, 35 + "output": { 36 + "encoding": "application/json", 37 + "schema": { 38 + "type": "object", 39 + "required": [ 40 + "id", 41 + "reasonType", 42 + "subject", 43 + "reportedBy", 44 + "createdAt" 45 + ], 46 + "properties": { 47 + "id": { "type": "integer" }, 48 + "reasonType": { 49 + "type": "ref", 50 + "ref": "com.atproto.moderation.defs#reasonType" 51 + }, 52 + "reason": { 53 + "type": "string", 54 + "maxGraphemes": 2000, 55 + "maxLength": 20000 56 + }, 57 + "subject": { 58 + "type": "union", 59 + "refs": [ 60 + "com.atproto.admin.defs#repoRef", 61 + "com.atproto.repo.strongRef" 62 + ] 63 + }, 64 + "reportedBy": { "type": "string", "format": "did" }, 65 + "createdAt": { "type": "string", "format": "datetime" } 66 + } 67 + } 68 + } 69 + } 70 + } 71 + }
+46
lexicons/com/atproto/moderation/defs.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.moderation.defs", 4 + "defs": { 5 + "reasonType": { 6 + "type": "string", 7 + "knownValues": [ 8 + "com.atproto.moderation.defs#reasonSpam", 9 + "com.atproto.moderation.defs#reasonViolation", 10 + "com.atproto.moderation.defs#reasonMisleading", 11 + "com.atproto.moderation.defs#reasonSexual", 12 + "com.atproto.moderation.defs#reasonRude", 13 + "com.atproto.moderation.defs#reasonOther", 14 + "com.atproto.moderation.defs#reasonAppeal" 15 + ] 16 + }, 17 + "reasonSpam": { 18 + "type": "token", 19 + "description": "Spam: frequent unwanted promotion, replies, mentions" 20 + }, 21 + "reasonViolation": { 22 + "type": "token", 23 + "description": "Direct violation of server rules, laws, terms of service" 24 + }, 25 + "reasonMisleading": { 26 + "type": "token", 27 + "description": "Misleading identity, affiliation, or content" 28 + }, 29 + "reasonSexual": { 30 + "type": "token", 31 + "description": "Unwanted or mislabeled sexual content" 32 + }, 33 + "reasonRude": { 34 + "type": "token", 35 + "description": "Rude, harassing, explicit, or otherwise unwelcoming behavior" 36 + }, 37 + "reasonOther": { 38 + "type": "token", 39 + "description": "Other: reports not falling under another report category" 40 + }, 41 + "reasonAppeal": { 42 + "type": "token", 43 + "description": "Appeal: appeal a previously taken moderation action" 44 + } 45 + } 46 + }
+126
lexicons/com/atproto/repo/applyWrites.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.applyWrites", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Apply a batch transaction of repository creates, updates, and deletes. Requires auth, implemented by PDS.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["repo", "writes"], 13 + "properties": { 14 + "repo": { 15 + "type": "string", 16 + "format": "at-identifier", 17 + "description": "The handle or DID of the repo (aka, current account)." 18 + }, 19 + "validate": { 20 + "type": "boolean", 21 + "description": "Can be set to 'false' to skip Lexicon schema validation of record data across all operations, 'true' to require it, or leave unset to validate only for known Lexicons." 22 + }, 23 + "writes": { 24 + "type": "array", 25 + "items": { 26 + "type": "union", 27 + "refs": ["#create", "#update", "#delete"], 28 + "closed": true 29 + } 30 + }, 31 + "swapCommit": { 32 + "type": "string", 33 + "description": "If provided, the entire operation will fail if the current repo commit CID does not match this value. Used to prevent conflicting repo mutations.", 34 + "format": "cid" 35 + } 36 + } 37 + } 38 + }, 39 + "output": { 40 + "encoding": "application/json", 41 + "schema": { 42 + "type": "object", 43 + "required": [], 44 + "properties": { 45 + "commit": { 46 + "type": "ref", 47 + "ref": "com.atproto.repo.defs#commitMeta" 48 + }, 49 + "results": { 50 + "type": "array", 51 + "items": { 52 + "type": "union", 53 + "refs": ["#createResult", "#updateResult", "#deleteResult"], 54 + "closed": true 55 + } 56 + } 57 + } 58 + } 59 + }, 60 + "errors": [ 61 + { 62 + "name": "InvalidSwap", 63 + "description": "Indicates that the 'swapCommit' parameter did not match current commit." 64 + } 65 + ] 66 + }, 67 + "create": { 68 + "type": "object", 69 + "description": "Operation which creates a new record.", 70 + "required": ["collection", "value"], 71 + "properties": { 72 + "collection": { "type": "string", "format": "nsid" }, 73 + "rkey": { "type": "string", "maxLength": 512 }, 74 + "value": { "type": "unknown" } 75 + } 76 + }, 77 + "update": { 78 + "type": "object", 79 + "description": "Operation which updates an existing record.", 80 + "required": ["collection", "rkey", "value"], 81 + "properties": { 82 + "collection": { "type": "string", "format": "nsid" }, 83 + "rkey": { "type": "string" }, 84 + "value": { "type": "unknown" } 85 + } 86 + }, 87 + "delete": { 88 + "type": "object", 89 + "description": "Operation which deletes an existing record.", 90 + "required": ["collection", "rkey"], 91 + "properties": { 92 + "collection": { "type": "string", "format": "nsid" }, 93 + "rkey": { "type": "string" } 94 + } 95 + }, 96 + "createResult": { 97 + "type": "object", 98 + "required": ["uri", "cid"], 99 + "properties": { 100 + "uri": { "type": "string", "format": "at-uri" }, 101 + "cid": { "type": "string", "format": "cid" }, 102 + "validationStatus": { 103 + "type": "string", 104 + "knownValues": ["valid", "unknown"] 105 + } 106 + } 107 + }, 108 + "updateResult": { 109 + "type": "object", 110 + "required": ["uri", "cid"], 111 + "properties": { 112 + "uri": { "type": "string", "format": "at-uri" }, 113 + "cid": { "type": "string", "format": "cid" }, 114 + "validationStatus": { 115 + "type": "string", 116 + "knownValues": ["valid", "unknown"] 117 + } 118 + } 119 + }, 120 + "deleteResult": { 121 + "type": "object", 122 + "required": [], 123 + "properties": {} 124 + } 125 + } 126 + }
+72
lexicons/com/atproto/repo/createRecord.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.createRecord", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Create a single new repository record. Requires auth, implemented by PDS.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["repo", "collection", "record"], 13 + "properties": { 14 + "repo": { 15 + "type": "string", 16 + "format": "at-identifier", 17 + "description": "The handle or DID of the repo (aka, current account)." 18 + }, 19 + "collection": { 20 + "type": "string", 21 + "format": "nsid", 22 + "description": "The NSID of the record collection." 23 + }, 24 + "rkey": { 25 + "type": "string", 26 + "description": "The Record Key.", 27 + "maxLength": 512 28 + }, 29 + "validate": { 30 + "type": "boolean", 31 + "description": "Can be set to 'false' to skip Lexicon schema validation of record data, 'true' to require it, or leave unset to validate only for known Lexicons." 32 + }, 33 + "record": { 34 + "type": "unknown", 35 + "description": "The record itself. Must contain a $type field." 36 + }, 37 + "swapCommit": { 38 + "type": "string", 39 + "format": "cid", 40 + "description": "Compare and swap with the previous commit by CID." 41 + } 42 + } 43 + } 44 + }, 45 + "output": { 46 + "encoding": "application/json", 47 + "schema": { 48 + "type": "object", 49 + "required": ["uri", "cid"], 50 + "properties": { 51 + "uri": { "type": "string", "format": "at-uri" }, 52 + "cid": { "type": "string", "format": "cid" }, 53 + "commit": { 54 + "type": "ref", 55 + "ref": "com.atproto.repo.defs#commitMeta" 56 + }, 57 + "validationStatus": { 58 + "type": "string", 59 + "knownValues": ["valid", "unknown"] 60 + } 61 + } 62 + } 63 + }, 64 + "errors": [ 65 + { 66 + "name": "InvalidSwap", 67 + "description": "Indicates that 'swapCommit' didn't match current repo commit." 68 + } 69 + ] 70 + } 71 + } 72 + }
+14
lexicons/com/atproto/repo/defs.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.defs", 4 + "defs": { 5 + "commitMeta": { 6 + "type": "object", 7 + "required": ["cid", "rev"], 8 + "properties": { 9 + "cid": { "type": "string", "format": "cid" }, 10 + "rev": { "type": "string" } 11 + } 12 + } 13 + } 14 + }
+56
lexicons/com/atproto/repo/deleteRecord.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.deleteRecord", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Delete a repository record, or ensure it doesn't exist. Requires auth, implemented by PDS.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["repo", "collection", "rkey"], 13 + "properties": { 14 + "repo": { 15 + "type": "string", 16 + "format": "at-identifier", 17 + "description": "The handle or DID of the repo (aka, current account)." 18 + }, 19 + "collection": { 20 + "type": "string", 21 + "format": "nsid", 22 + "description": "The NSID of the record collection." 23 + }, 24 + "rkey": { 25 + "type": "string", 26 + "description": "The Record Key." 27 + }, 28 + "swapRecord": { 29 + "type": "string", 30 + "format": "cid", 31 + "description": "Compare and swap with the previous record by CID." 32 + }, 33 + "swapCommit": { 34 + "type": "string", 35 + "format": "cid", 36 + "description": "Compare and swap with the previous commit by CID." 37 + } 38 + } 39 + } 40 + }, 41 + "output": { 42 + "encoding": "application/json", 43 + "schema": { 44 + "type": "object", 45 + "properties": { 46 + "commit": { 47 + "type": "ref", 48 + "ref": "com.atproto.repo.defs#commitMeta" 49 + } 50 + } 51 + } 52 + }, 53 + "errors": [{ "name": "InvalidSwap" }] 54 + } 55 + } 56 + }
+51
lexicons/com/atproto/repo/describeRepo.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.describeRepo", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get information about an account and repository, including the list of collections. Does not require auth.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["repo"], 11 + "properties": { 12 + "repo": { 13 + "type": "string", 14 + "format": "at-identifier", 15 + "description": "The handle or DID of the repo." 16 + } 17 + } 18 + }, 19 + "output": { 20 + "encoding": "application/json", 21 + "schema": { 22 + "type": "object", 23 + "required": [ 24 + "handle", 25 + "did", 26 + "didDoc", 27 + "collections", 28 + "handleIsCorrect" 29 + ], 30 + "properties": { 31 + "handle": { "type": "string", "format": "handle" }, 32 + "did": { "type": "string", "format": "did" }, 33 + "didDoc": { 34 + "type": "unknown", 35 + "description": "The complete DID document for this account." 36 + }, 37 + "collections": { 38 + "type": "array", 39 + "description": "List of all the collections (NSIDs) for which this repo contains at least one record.", 40 + "items": { "type": "string", "format": "nsid" } 41 + }, 42 + "handleIsCorrect": { 43 + "type": "boolean", 44 + "description": "Indicates if handle is currently valid (resolves bi-directionally)" 45 + } 46 + } 47 + } 48 + } 49 + } 50 + } 51 + }
+45
lexicons/com/atproto/repo/getRecord.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.getRecord", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get a single record from a repository. Does not require auth.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["repo", "collection", "rkey"], 11 + "properties": { 12 + "repo": { 13 + "type": "string", 14 + "format": "at-identifier", 15 + "description": "The handle or DID of the repo." 16 + }, 17 + "collection": { 18 + "type": "string", 19 + "format": "nsid", 20 + "description": "The NSID of the record collection." 21 + }, 22 + "rkey": { "type": "string", "description": "The Record Key." }, 23 + "cid": { 24 + "type": "string", 25 + "format": "cid", 26 + "description": "The CID of the version of the record. If not specified, then return the most recent version." 27 + } 28 + } 29 + }, 30 + "output": { 31 + "encoding": "application/json", 32 + "schema": { 33 + "type": "object", 34 + "required": ["uri", "value"], 35 + "properties": { 36 + "uri": { "type": "string", "format": "at-uri" }, 37 + "cid": { "type": "string", "format": "cid" }, 38 + "value": { "type": "unknown" } 39 + } 40 + } 41 + }, 42 + "errors": [{ "name": "RecordNotFound" }] 43 + } 44 + } 45 + }
+13
lexicons/com/atproto/repo/importRepo.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.importRepo", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Import a repo in the form of a CAR file. Requires Content-Length HTTP header to be set.", 8 + "input": { 9 + "encoding": "application/vnd.ipld.car" 10 + } 11 + } 12 + } 13 + }
+44
lexicons/com/atproto/repo/listMissingBlobs.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.listMissingBlobs", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Returns a list of missing blobs for the requesting account. Intended to be used in the account migration flow.", 8 + "parameters": { 9 + "type": "params", 10 + "properties": { 11 + "limit": { 12 + "type": "integer", 13 + "minimum": 1, 14 + "maximum": 1000, 15 + "default": 500 16 + }, 17 + "cursor": { "type": "string" } 18 + } 19 + }, 20 + "output": { 21 + "encoding": "application/json", 22 + "schema": { 23 + "type": "object", 24 + "required": ["blobs"], 25 + "properties": { 26 + "cursor": { "type": "string" }, 27 + "blobs": { 28 + "type": "array", 29 + "items": { "type": "ref", "ref": "#recordBlob" } 30 + } 31 + } 32 + } 33 + } 34 + }, 35 + "recordBlob": { 36 + "type": "object", 37 + "required": ["cid", "recordUri"], 38 + "properties": { 39 + "cid": { "type": "string", "format": "cid" }, 40 + "recordUri": { "type": "string", "format": "at-uri" } 41 + } 42 + } 43 + } 44 + }
+69
lexicons/com/atproto/repo/listRecords.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.listRecords", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "List a range of records in a repository, matching a specific collection. Does not require auth.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["repo", "collection"], 11 + "properties": { 12 + "repo": { 13 + "type": "string", 14 + "format": "at-identifier", 15 + "description": "The handle or DID of the repo." 16 + }, 17 + "collection": { 18 + "type": "string", 19 + "format": "nsid", 20 + "description": "The NSID of the record type." 21 + }, 22 + "limit": { 23 + "type": "integer", 24 + "minimum": 1, 25 + "maximum": 100, 26 + "default": 50, 27 + "description": "The number of records to return." 28 + }, 29 + "cursor": { "type": "string" }, 30 + "rkeyStart": { 31 + "type": "string", 32 + "description": "DEPRECATED: The lowest sort-ordered rkey to start from (exclusive)" 33 + }, 34 + "rkeyEnd": { 35 + "type": "string", 36 + "description": "DEPRECATED: The highest sort-ordered rkey to stop at (exclusive)" 37 + }, 38 + "reverse": { 39 + "type": "boolean", 40 + "description": "Flag to reverse the order of the returned records." 41 + } 42 + } 43 + }, 44 + "output": { 45 + "encoding": "application/json", 46 + "schema": { 47 + "type": "object", 48 + "required": ["records"], 49 + "properties": { 50 + "cursor": { "type": "string" }, 51 + "records": { 52 + "type": "array", 53 + "items": { "type": "ref", "ref": "#record" } 54 + } 55 + } 56 + } 57 + } 58 + }, 59 + "record": { 60 + "type": "object", 61 + "required": ["uri", "cid", "value"], 62 + "properties": { 63 + "uri": { "type": "string", "format": "at-uri" }, 64 + "cid": { "type": "string", "format": "cid" }, 65 + "value": { "type": "unknown" } 66 + } 67 + } 68 + } 69 + }
+73
lexicons/com/atproto/repo/putRecord.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.putRecord", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Write a repository record, creating or updating it as needed. Requires auth, implemented by PDS.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["repo", "collection", "rkey", "record"], 13 + "nullable": ["swapRecord"], 14 + "properties": { 15 + "repo": { 16 + "type": "string", 17 + "format": "at-identifier", 18 + "description": "The handle or DID of the repo (aka, current account)." 19 + }, 20 + "collection": { 21 + "type": "string", 22 + "format": "nsid", 23 + "description": "The NSID of the record collection." 24 + }, 25 + "rkey": { 26 + "type": "string", 27 + "description": "The Record Key.", 28 + "maxLength": 512 29 + }, 30 + "validate": { 31 + "type": "boolean", 32 + "description": "Can be set to 'false' to skip Lexicon schema validation of record data, 'true' to require it, or leave unset to validate only for known Lexicons." 33 + }, 34 + "record": { 35 + "type": "unknown", 36 + "description": "The record to write." 37 + }, 38 + "swapRecord": { 39 + "type": "string", 40 + "format": "cid", 41 + "description": "Compare and swap with the previous record by CID. WARNING: nullable and optional field; may cause problems with golang implementation" 42 + }, 43 + "swapCommit": { 44 + "type": "string", 45 + "format": "cid", 46 + "description": "Compare and swap with the previous commit by CID." 47 + } 48 + } 49 + } 50 + }, 51 + "output": { 52 + "encoding": "application/json", 53 + "schema": { 54 + "type": "object", 55 + "required": ["uri", "cid"], 56 + "properties": { 57 + "uri": { "type": "string", "format": "at-uri" }, 58 + "cid": { "type": "string", "format": "cid" }, 59 + "commit": { 60 + "type": "ref", 61 + "ref": "com.atproto.repo.defs#commitMeta" 62 + }, 63 + "validationStatus": { 64 + "type": "string", 65 + "knownValues": ["valid", "unknown"] 66 + } 67 + } 68 + } 69 + }, 70 + "errors": [{ "name": "InvalidSwap" }] 71 + } 72 + } 73 + }
+15
lexicons/com/atproto/repo/strongRef.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.strongRef", 4 + "description": "A URI with a content-hash fingerprint.", 5 + "defs": { 6 + "main": { 7 + "type": "object", 8 + "required": ["uri", "cid"], 9 + "properties": { 10 + "uri": { "type": "string", "format": "at-uri" }, 11 + "cid": { "type": "string", "format": "cid" } 12 + } 13 + } 14 + } 15 + }
+23
lexicons/com/atproto/repo/uploadBlob.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.uploadBlob", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Upload a new blob, to be referenced from a repository record. The blob will be deleted if it is not referenced within a time window (eg, minutes). Blob restrictions (mimetype, size, etc) are enforced when the reference is created. Requires auth, implemented by PDS.", 8 + "input": { 9 + "encoding": "*/*" 10 + }, 11 + "output": { 12 + "encoding": "application/json", 13 + "schema": { 14 + "type": "object", 15 + "required": ["blob"], 16 + "properties": { 17 + "blob": { "type": "blob" } 18 + } 19 + } 20 + } 21 + } 22 + } 23 + }
+10
lexicons/com/atproto/server/activateAccount.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.server.activateAccount", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Activates a currently deactivated account. Used to finalize account migration after the account's repo is imported and identity is setup." 8 + } 9 + } 10 + }
+38
lexicons/com/atproto/server/checkAccountStatus.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.server.checkAccountStatus", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Returns the status of an account, especially as pertaining to import or recovery. Can be called many times over the course of an account migration. Requires auth and can only be called pertaining to oneself.", 8 + "output": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": [ 13 + "activated", 14 + "validDid", 15 + "repoCommit", 16 + "repoRev", 17 + "repoBlocks", 18 + "indexedRecords", 19 + "privateStateValues", 20 + "expectedBlobs", 21 + "importedBlobs" 22 + ], 23 + "properties": { 24 + "activated": { "type": "boolean" }, 25 + "validDid": { "type": "boolean" }, 26 + "repoCommit": { "type": "string", "format": "cid" }, 27 + "repoRev": { "type": "string" }, 28 + "repoBlocks": { "type": "integer" }, 29 + "indexedRecords": { "type": "integer" }, 30 + "privateStateValues": { "type": "integer" }, 31 + "expectedBlobs": { "type": "integer" }, 32 + "importedBlobs": { "type": "integer" } 33 + } 34 + } 35 + } 36 + } 37 + } 38 + }
+27
lexicons/com/atproto/server/confirmEmail.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.server.confirmEmail", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Confirm an email using a token from com.atproto.server.requestEmailConfirmation.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["email", "token"], 13 + "properties": { 14 + "email": { "type": "string" }, 15 + "token": { "type": "string" } 16 + } 17 + } 18 + }, 19 + "errors": [ 20 + { "name": "AccountNotFound" }, 21 + { "name": "ExpiredToken" }, 22 + { "name": "InvalidToken" }, 23 + { "name": "InvalidEmail" } 24 + ] 25 + } 26 + } 27 + }
+76
lexicons/com/atproto/server/createAccount.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.server.createAccount", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Create an account. Implemented by PDS.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["handle"], 13 + "properties": { 14 + "email": { "type": "string" }, 15 + "handle": { 16 + "type": "string", 17 + "format": "handle", 18 + "description": "Requested handle for the account." 19 + }, 20 + "did": { 21 + "type": "string", 22 + "format": "did", 23 + "description": "Pre-existing atproto DID, being imported to a new account." 24 + }, 25 + "inviteCode": { "type": "string" }, 26 + "verificationCode": { "type": "string" }, 27 + "verificationPhone": { "type": "string" }, 28 + "password": { 29 + "type": "string", 30 + "description": "Initial account password. May need to meet instance-specific password strength requirements." 31 + }, 32 + "recoveryKey": { 33 + "type": "string", 34 + "description": "DID PLC rotation key (aka, recovery key) to be included in PLC creation operation." 35 + }, 36 + "plcOp": { 37 + "type": "unknown", 38 + "description": "A signed DID PLC operation to be submitted as part of importing an existing account to this instance. NOTE: this optional field may be updated when full account migration is implemented." 39 + } 40 + } 41 + } 42 + }, 43 + "output": { 44 + "encoding": "application/json", 45 + "schema": { 46 + "type": "object", 47 + "description": "Account login session returned on successful account creation.", 48 + "required": ["accessJwt", "refreshJwt", "handle", "did"], 49 + "properties": { 50 + "accessJwt": { "type": "string" }, 51 + "refreshJwt": { "type": "string" }, 52 + "handle": { "type": "string", "format": "handle" }, 53 + "did": { 54 + "type": "string", 55 + "format": "did", 56 + "description": "The DID of the new account." 57 + }, 58 + "didDoc": { 59 + "type": "unknown", 60 + "description": "Complete DID document." 61 + } 62 + } 63 + } 64 + }, 65 + "errors": [ 66 + { "name": "InvalidHandle" }, 67 + { "name": "InvalidPassword" }, 68 + { "name": "InvalidInviteCode" }, 69 + { "name": "HandleNotAvailable" }, 70 + { "name": "UnsupportedDomain" }, 71 + { "name": "UnresolvableDid" }, 72 + { "name": "IncompatibleDidDoc" } 73 + ] 74 + } 75 + } 76 + }
+45
lexicons/com/atproto/server/createAppPassword.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.server.createAppPassword", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Create an App Password.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["name"], 13 + "properties": { 14 + "name": { 15 + "type": "string", 16 + "description": "A short name for the App Password, to help distinguish them." 17 + }, 18 + "privileged": { 19 + "type": "boolean", 20 + "description": "If an app password has 'privileged' access to possibly sensitive account state. Meant for use with trusted clients." 21 + } 22 + } 23 + } 24 + }, 25 + "output": { 26 + "encoding": "application/json", 27 + "schema": { 28 + "type": "ref", 29 + "ref": "#appPassword" 30 + } 31 + }, 32 + "errors": [{ "name": "AccountTakedown" }] 33 + }, 34 + "appPassword": { 35 + "type": "object", 36 + "required": ["name", "password", "createdAt"], 37 + "properties": { 38 + "name": { "type": "string" }, 39 + "password": { "type": "string" }, 40 + "createdAt": { "type": "string", "format": "datetime" }, 41 + "privileged": { "type": "boolean" } 42 + } 43 + } 44 + } 45 + }
+31
lexicons/com/atproto/server/createInviteCode.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.server.createInviteCode", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Create an invite code.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["useCount"], 13 + "properties": { 14 + "useCount": { "type": "integer" }, 15 + "forAccount": { "type": "string", "format": "did" } 16 + } 17 + } 18 + }, 19 + "output": { 20 + "encoding": "application/json", 21 + "schema": { 22 + "type": "object", 23 + "required": ["code"], 24 + "properties": { 25 + "code": { "type": "string" } 26 + } 27 + } 28 + } 29 + } 30 + } 31 + }
+49
lexicons/com/atproto/server/createInviteCodes.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.server.createInviteCodes", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Create invite codes.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["codeCount", "useCount"], 13 + "properties": { 14 + "codeCount": { "type": "integer", "default": 1 }, 15 + "useCount": { "type": "integer" }, 16 + "forAccounts": { 17 + "type": "array", 18 + "items": { "type": "string", "format": "did" } 19 + } 20 + } 21 + } 22 + }, 23 + "output": { 24 + "encoding": "application/json", 25 + "schema": { 26 + "type": "object", 27 + "required": ["codes"], 28 + "properties": { 29 + "codes": { 30 + "type": "array", 31 + "items": { "type": "ref", "ref": "#accountCodes" } 32 + } 33 + } 34 + } 35 + } 36 + }, 37 + "accountCodes": { 38 + "type": "object", 39 + "required": ["account", "codes"], 40 + "properties": { 41 + "account": { "type": "string" }, 42 + "codes": { 43 + "type": "array", 44 + "items": { "type": "string" } 45 + } 46 + } 47 + } 48 + } 49 + }
+52
lexicons/com/atproto/server/createSession.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.server.createSession", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Create an authentication session.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["identifier", "password"], 13 + "properties": { 14 + "identifier": { 15 + "type": "string", 16 + "description": "Handle or other identifier supported by the server for the authenticating user." 17 + }, 18 + "password": { "type": "string" }, 19 + "authFactorToken": { "type": "string" } 20 + } 21 + } 22 + }, 23 + "output": { 24 + "encoding": "application/json", 25 + "schema": { 26 + "type": "object", 27 + "required": ["accessJwt", "refreshJwt", "handle", "did"], 28 + "properties": { 29 + "accessJwt": { "type": "string" }, 30 + "refreshJwt": { "type": "string" }, 31 + "handle": { "type": "string", "format": "handle" }, 32 + "did": { "type": "string", "format": "did" }, 33 + "didDoc": { "type": "unknown" }, 34 + "email": { "type": "string" }, 35 + "emailConfirmed": { "type": "boolean" }, 36 + "emailAuthFactor": { "type": "boolean" }, 37 + "active": { "type": "boolean" }, 38 + "status": { 39 + "type": "string", 40 + "description": "If active=false, this optional field indicates a possible reason for why the account is not active. If active=false and no status is supplied, then the host makes no claim for why the repository is no longer being hosted.", 41 + "knownValues": ["takendown", "suspended", "deactivated"] 42 + } 43 + } 44 + } 45 + }, 46 + "errors": [ 47 + { "name": "AccountTakedown" }, 48 + { "name": "AuthFactorTokenRequired" } 49 + ] 50 + } 51 + } 52 + }
+23
lexicons/com/atproto/server/deactivateAccount.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.server.deactivateAccount", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Deactivates a currently active account. Stops serving of repo, and future writes to repo until reactivated. Used to finalize account migration with the old host after the account has been activated on the new host.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "properties": { 13 + "deleteAfter": { 14 + "type": "string", 15 + "format": "datetime", 16 + "description": "A recommendation to server as to how long they should hold onto the deactivated account before deleting." 17 + } 18 + } 19 + } 20 + } 21 + } 22 + } 23 + }
+38
lexicons/com/atproto/server/defs.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.server.defs", 4 + "defs": { 5 + "inviteCode": { 6 + "type": "object", 7 + "required": [ 8 + "code", 9 + "available", 10 + "disabled", 11 + "forAccount", 12 + "createdBy", 13 + "createdAt", 14 + "uses" 15 + ], 16 + "properties": { 17 + "code": { "type": "string" }, 18 + "available": { "type": "integer" }, 19 + "disabled": { "type": "boolean" }, 20 + "forAccount": { "type": "string" }, 21 + "createdBy": { "type": "string" }, 22 + "createdAt": { "type": "string", "format": "datetime" }, 23 + "uses": { 24 + "type": "array", 25 + "items": { "type": "ref", "ref": "#inviteCodeUse" } 26 + } 27 + } 28 + }, 29 + "inviteCodeUse": { 30 + "type": "object", 31 + "required": ["usedBy", "usedAt"], 32 + "properties": { 33 + "usedBy": { "type": "string", "format": "did" }, 34 + "usedAt": { "type": "string", "format": "datetime" } 35 + } 36 + } 37 + } 38 + }
+23
lexicons/com/atproto/server/deleteAccount.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.server.deleteAccount", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Delete an actor's account with a token and password. Can only be called after requesting a deletion token. Requires auth.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["did", "password", "token"], 13 + "properties": { 14 + "did": { "type": "string", "format": "did" }, 15 + "password": { "type": "string" }, 16 + "token": { "type": "string" } 17 + } 18 + } 19 + }, 20 + "errors": [{ "name": "ExpiredToken" }, { "name": "InvalidToken" }] 21 + } 22 + } 23 + }
+10
lexicons/com/atproto/server/deleteSession.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.server.deleteSession", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Delete the current session. Requires auth." 8 + } 9 + } 10 + }
+59
lexicons/com/atproto/server/describeServer.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.server.describeServer", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Describes the server's account creation requirements and capabilities. Implemented by PDS.", 8 + "output": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["did", "availableUserDomains"], 13 + "properties": { 14 + "inviteCodeRequired": { 15 + "type": "boolean", 16 + "description": "If true, an invite code must be supplied to create an account on this instance." 17 + }, 18 + "phoneVerificationRequired": { 19 + "type": "boolean", 20 + "description": "If true, a phone verification token must be supplied to create an account on this instance." 21 + }, 22 + "availableUserDomains": { 23 + "type": "array", 24 + "description": "List of domain suffixes that can be used in account handles.", 25 + "items": { "type": "string" } 26 + }, 27 + "links": { 28 + "type": "ref", 29 + "description": "URLs of service policy documents.", 30 + "ref": "#links" 31 + }, 32 + "contact": { 33 + "type": "ref", 34 + "description": "Contact information", 35 + "ref": "#contact" 36 + }, 37 + "did": { 38 + "type": "string", 39 + "format": "did" 40 + } 41 + } 42 + } 43 + } 44 + }, 45 + "links": { 46 + "type": "object", 47 + "properties": { 48 + "privacyPolicy": { "type": "string", "format": "uri" }, 49 + "termsOfService": { "type": "string", "format": "uri" } 50 + } 51 + }, 52 + "contact": { 53 + "type": "object", 54 + "properties": { 55 + "email": { "type": "string" } 56 + } 57 + } 58 + } 59 + }
+38
lexicons/com/atproto/server/getAccountInviteCodes.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.server.getAccountInviteCodes", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get all invite codes for the current account. Requires auth.", 8 + "parameters": { 9 + "type": "params", 10 + "properties": { 11 + "includeUsed": { "type": "boolean", "default": true }, 12 + "createAvailable": { 13 + "type": "boolean", 14 + "default": true, 15 + "description": "Controls whether any new 'earned' but not 'created' invites should be created." 16 + } 17 + } 18 + }, 19 + "output": { 20 + "encoding": "application/json", 21 + "schema": { 22 + "type": "object", 23 + "required": ["codes"], 24 + "properties": { 25 + "codes": { 26 + "type": "array", 27 + "items": { 28 + "type": "ref", 29 + "ref": "com.atproto.server.defs#inviteCode" 30 + } 31 + } 32 + } 33 + } 34 + }, 35 + "errors": [{ "name": "DuplicateCreate" }] 36 + } 37 + } 38 + }
+48
lexicons/com/atproto/server/getServiceAuth.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.server.getServiceAuth", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get a signed token on behalf of the requesting DID for the requested service.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["aud"], 11 + "properties": { 12 + "aud": { 13 + "type": "string", 14 + "format": "did", 15 + "description": "The DID of the service that the token will be used to authenticate with" 16 + }, 17 + "exp": { 18 + "type": "integer", 19 + "description": "The time in Unix Epoch seconds that the JWT expires. Defaults to 60 seconds in the future. The service may enforce certain time bounds on tokens depending on the requested scope." 20 + }, 21 + "lxm": { 22 + "type": "string", 23 + "format": "nsid", 24 + "description": "Lexicon (XRPC) method to bind the requested token to" 25 + } 26 + } 27 + }, 28 + "output": { 29 + "encoding": "application/json", 30 + "schema": { 31 + "type": "object", 32 + "required": ["token"], 33 + "properties": { 34 + "token": { 35 + "type": "string" 36 + } 37 + } 38 + } 39 + }, 40 + "errors": [ 41 + { 42 + "name": "BadExpiration", 43 + "description": "Indicates that the requested expiration date is not a valid. May be in the past or may be reliant on the requested scopes." 44 + } 45 + ] 46 + } 47 + } 48 + }
+31
lexicons/com/atproto/server/getSession.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.server.getSession", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get information about the current auth session. Requires auth.", 8 + "output": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["handle", "did"], 13 + "properties": { 14 + "handle": { "type": "string", "format": "handle" }, 15 + "did": { "type": "string", "format": "did" }, 16 + "email": { "type": "string" }, 17 + "emailConfirmed": { "type": "boolean" }, 18 + "emailAuthFactor": { "type": "boolean" }, 19 + "didDoc": { "type": "unknown" }, 20 + "active": { "type": "boolean" }, 21 + "status": { 22 + "type": "string", 23 + "description": "If active=false, this optional field indicates a possible reason for why the account is not active. If active=false and no status is supplied, then the host makes no claim for why the repository is no longer being hosted.", 24 + "knownValues": ["takendown", "suspended", "deactivated"] 25 + } 26 + } 27 + } 28 + } 29 + } 30 + } 31 + }
+33
lexicons/com/atproto/server/listAppPasswords.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.server.listAppPasswords", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "List all App Passwords.", 8 + "output": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["passwords"], 13 + "properties": { 14 + "passwords": { 15 + "type": "array", 16 + "items": { "type": "ref", "ref": "#appPassword" } 17 + } 18 + } 19 + } 20 + }, 21 + "errors": [{ "name": "AccountTakedown" }] 22 + }, 23 + "appPassword": { 24 + "type": "object", 25 + "required": ["name", "createdAt"], 26 + "properties": { 27 + "name": { "type": "string" }, 28 + "createdAt": { "type": "string", "format": "datetime" }, 29 + "privileged": { "type": "boolean" } 30 + } 31 + } 32 + } 33 + }
+31
lexicons/com/atproto/server/refreshSession.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.server.refreshSession", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Refresh an authentication session. Requires auth using the 'refreshJwt' (not the 'accessJwt').", 8 + "output": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["accessJwt", "refreshJwt", "handle", "did"], 13 + "properties": { 14 + "accessJwt": { "type": "string" }, 15 + "refreshJwt": { "type": "string" }, 16 + "handle": { "type": "string", "format": "handle" }, 17 + "did": { "type": "string", "format": "did" }, 18 + "didDoc": { "type": "unknown" }, 19 + "active": { "type": "boolean" }, 20 + "status": { 21 + "type": "string", 22 + "description": "Hosting status of the account. If not specified, then assume 'active'.", 23 + "knownValues": ["takendown", "suspended", "deactivated"] 24 + } 25 + } 26 + } 27 + }, 28 + "errors": [{ "name": "AccountTakedown" }] 29 + } 30 + } 31 + }
+10
lexicons/com/atproto/server/requestAccountDelete.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.server.requestAccountDelete", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Initiate a user account deletion via email." 8 + } 9 + } 10 + }
+10
lexicons/com/atproto/server/requestEmailConfirmation.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.server.requestEmailConfirmation", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Request an email with a code to confirm ownership of email." 8 + } 9 + } 10 + }
+20
lexicons/com/atproto/server/requestEmailUpdate.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.server.requestEmailUpdate", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Request a token in order to update email.", 8 + "output": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["tokenRequired"], 13 + "properties": { 14 + "tokenRequired": { "type": "boolean" } 15 + } 16 + } 17 + } 18 + } 19 + } 20 + }
+20
lexicons/com/atproto/server/requestPasswordReset.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.server.requestPasswordReset", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Initiate a user account password reset via email.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["email"], 13 + "properties": { 14 + "email": { "type": "string" } 15 + } 16 + } 17 + } 18 + } 19 + } 20 + }
+36
lexicons/com/atproto/server/reserveSigningKey.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.server.reserveSigningKey", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Reserve a repo signing key, for use with account creation. Necessary so that a DID PLC update operation can be constructed during an account migraiton. Public and does not require auth; implemented by PDS. NOTE: this endpoint may change when full account migration is implemented.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "properties": { 13 + "did": { 14 + "type": "string", 15 + "format": "did", 16 + "description": "The DID to reserve a key for." 17 + } 18 + } 19 + } 20 + }, 21 + "output": { 22 + "encoding": "application/json", 23 + "schema": { 24 + "type": "object", 25 + "required": ["signingKey"], 26 + "properties": { 27 + "signingKey": { 28 + "type": "string", 29 + "description": "The public key for the reserved signing key, in did:key serialization." 30 + } 31 + } 32 + } 33 + } 34 + } 35 + } 36 + }
+22
lexicons/com/atproto/server/resetPassword.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.server.resetPassword", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Reset a user account password using a token.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["token", "password"], 13 + "properties": { 14 + "token": { "type": "string" }, 15 + "password": { "type": "string" } 16 + } 17 + } 18 + }, 19 + "errors": [{ "name": "ExpiredToken" }, { "name": "InvalidToken" }] 20 + } 21 + } 22 + }
+20
lexicons/com/atproto/server/revokeAppPassword.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.server.revokeAppPassword", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Revoke an App Password by name.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["name"], 13 + "properties": { 14 + "name": { "type": "string" } 15 + } 16 + } 17 + } 18 + } 19 + } 20 + }
+30
lexicons/com/atproto/server/updateEmail.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.server.updateEmail", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Update an account's email.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["email"], 13 + "properties": { 14 + "email": { "type": "string" }, 15 + "emailAuthFactor": { "type": "boolean" }, 16 + "token": { 17 + "type": "string", 18 + "description": "Requires a token from com.atproto.sever.requestEmailUpdate if the account's email has been confirmed." 19 + } 20 + } 21 + } 22 + }, 23 + "errors": [ 24 + { "name": "ExpiredToken" }, 25 + { "name": "InvalidToken" }, 26 + { "name": "TokenRequired" } 27 + ] 28 + } 29 + } 30 + }
+36
lexicons/com/atproto/sync/getBlob.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.sync.getBlob", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get a blob associated with a given account. Returns the full blob as originally uploaded. Does not require auth; implemented by PDS.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["did", "cid"], 11 + "properties": { 12 + "did": { 13 + "type": "string", 14 + "format": "did", 15 + "description": "The DID of the account." 16 + }, 17 + "cid": { 18 + "type": "string", 19 + "format": "cid", 20 + "description": "The CID of the blob to fetch" 21 + } 22 + } 23 + }, 24 + "output": { 25 + "encoding": "*/*" 26 + }, 27 + "errors": [ 28 + { "name": "BlobNotFound" }, 29 + { "name": "RepoNotFound" }, 30 + { "name": "RepoTakendown" }, 31 + { "name": "RepoSuspended" }, 32 + { "name": "RepoDeactivated" } 33 + ] 34 + } 35 + } 36 + }
+35
lexicons/com/atproto/sync/getBlocks.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.sync.getBlocks", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get data blocks from a given repo, by CID. For example, intermediate MST nodes, or records. Does not require auth; implemented by PDS.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["did", "cids"], 11 + "properties": { 12 + "did": { 13 + "type": "string", 14 + "format": "did", 15 + "description": "The DID of the repo." 16 + }, 17 + "cids": { 18 + "type": "array", 19 + "items": { "type": "string", "format": "cid" } 20 + } 21 + } 22 + }, 23 + "output": { 24 + "encoding": "application/vnd.ipld.car" 25 + }, 26 + "errors": [ 27 + { "name": "BlockNotFound" }, 28 + { "name": "RepoNotFound" }, 29 + { "name": "RepoTakendown" }, 30 + { "name": "RepoSuspended" }, 31 + { "name": "RepoDeactivated" } 32 + ] 33 + } 34 + } 35 + }
+24
lexicons/com/atproto/sync/getCheckout.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.sync.getCheckout", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "DEPRECATED - please use com.atproto.sync.getRepo instead", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["did"], 11 + "properties": { 12 + "did": { 13 + "type": "string", 14 + "format": "did", 15 + "description": "The DID of the repo." 16 + } 17 + } 18 + }, 19 + "output": { 20 + "encoding": "application/vnd.ipld.car" 21 + } 22 + } 23 + } 24 + }
+32
lexicons/com/atproto/sync/getHead.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.sync.getHead", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "DEPRECATED - please use com.atproto.sync.getLatestCommit instead", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["did"], 11 + "properties": { 12 + "did": { 13 + "type": "string", 14 + "format": "did", 15 + "description": "The DID of the repo." 16 + } 17 + } 18 + }, 19 + "output": { 20 + "encoding": "application/json", 21 + "schema": { 22 + "type": "object", 23 + "required": ["root"], 24 + "properties": { 25 + "root": { "type": "string", "format": "cid" } 26 + } 27 + } 28 + }, 29 + "errors": [{ "name": "HeadNotFound" }] 30 + } 31 + } 32 + }
+38
lexicons/com/atproto/sync/getLatestCommit.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.sync.getLatestCommit", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get the current commit CID & revision of the specified repo. Does not require auth.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["did"], 11 + "properties": { 12 + "did": { 13 + "type": "string", 14 + "format": "did", 15 + "description": "The DID of the repo." 16 + } 17 + } 18 + }, 19 + "output": { 20 + "encoding": "application/json", 21 + "schema": { 22 + "type": "object", 23 + "required": ["cid", "rev"], 24 + "properties": { 25 + "cid": { "type": "string", "format": "cid" }, 26 + "rev": { "type": "string" } 27 + } 28 + } 29 + }, 30 + "errors": [ 31 + { "name": "RepoNotFound" }, 32 + { "name": "RepoTakendown" }, 33 + { "name": "RepoSuspended" }, 34 + { "name": "RepoDeactivated" } 35 + ] 36 + } 37 + } 38 + }
+38
lexicons/com/atproto/sync/getRecord.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.sync.getRecord", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get data blocks needed to prove the existence or non-existence of record in the current version of repo. Does not require auth.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["did", "collection", "rkey"], 11 + "properties": { 12 + "did": { 13 + "type": "string", 14 + "format": "did", 15 + "description": "The DID of the repo." 16 + }, 17 + "collection": { "type": "string", "format": "nsid" }, 18 + "rkey": { "type": "string", "description": "Record Key" }, 19 + "commit": { 20 + "type": "string", 21 + "format": "cid", 22 + "description": "DEPRECATED: referenced a repo commit by CID, and retrieved record as of that commit" 23 + } 24 + } 25 + }, 26 + "output": { 27 + "encoding": "application/vnd.ipld.car" 28 + }, 29 + "errors": [ 30 + { "name": "RecordNotFound" }, 31 + { "name": "RepoNotFound" }, 32 + { "name": "RepoTakendown" }, 33 + { "name": "RepoSuspended" }, 34 + { "name": "RepoDeactivated" } 35 + ] 36 + } 37 + } 38 + }
+34
lexicons/com/atproto/sync/getRepo.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.sync.getRepo", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Download a repository export as CAR file. Optionally only a 'diff' since a previous revision. Does not require auth; implemented by PDS.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["did"], 11 + "properties": { 12 + "did": { 13 + "type": "string", 14 + "format": "did", 15 + "description": "The DID of the repo." 16 + }, 17 + "since": { 18 + "type": "string", 19 + "description": "The revision ('rev') of the repo to create a diff from." 20 + } 21 + } 22 + }, 23 + "output": { 24 + "encoding": "application/vnd.ipld.car" 25 + }, 26 + "errors": [ 27 + { "name": "RepoNotFound" }, 28 + { "name": "RepoTakendown" }, 29 + { "name": "RepoSuspended" }, 30 + { "name": "RepoDeactivated" } 31 + ] 32 + } 33 + } 34 + }
+42
lexicons/com/atproto/sync/getRepoStatus.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.sync.getRepoStatus", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get the hosting status for a repository, on this server. Expected to be implemented by PDS and Relay.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["did"], 11 + "properties": { 12 + "did": { 13 + "type": "string", 14 + "format": "did", 15 + "description": "The DID of the repo." 16 + } 17 + } 18 + }, 19 + "output": { 20 + "encoding": "application/json", 21 + "schema": { 22 + "type": "object", 23 + "required": ["did", "active"], 24 + "properties": { 25 + "did": { "type": "string", "format": "did" }, 26 + "active": { "type": "boolean" }, 27 + "status": { 28 + "type": "string", 29 + "description": "If active=false, this optional field indicates a possible reason for why the account is not active. If active=false and no status is supplied, then the host makes no claim for why the repository is no longer being hosted.", 30 + "knownValues": ["takendown", "suspended", "deactivated"] 31 + }, 32 + "rev": { 33 + "type": "string", 34 + "description": "Optional field, the current rev of the repo, if active=true" 35 + } 36 + } 37 + } 38 + }, 39 + "errors": [{ "name": "RepoNotFound" }] 40 + } 41 + } 42 + }
+52
lexicons/com/atproto/sync/listBlobs.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.sync.listBlobs", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "List blob CIDs for an account, since some repo revision. Does not require auth; implemented by PDS.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["did"], 11 + "properties": { 12 + "did": { 13 + "type": "string", 14 + "format": "did", 15 + "description": "The DID of the repo." 16 + }, 17 + "since": { 18 + "type": "string", 19 + "description": "Optional revision of the repo to list blobs since." 20 + }, 21 + "limit": { 22 + "type": "integer", 23 + "minimum": 1, 24 + "maximum": 1000, 25 + "default": 500 26 + }, 27 + "cursor": { "type": "string" } 28 + } 29 + }, 30 + "output": { 31 + "encoding": "application/json", 32 + "schema": { 33 + "type": "object", 34 + "required": ["cids"], 35 + "properties": { 36 + "cursor": { "type": "string" }, 37 + "cids": { 38 + "type": "array", 39 + "items": { "type": "string", "format": "cid" } 40 + } 41 + } 42 + } 43 + }, 44 + "errors": [ 45 + { "name": "RepoNotFound" }, 46 + { "name": "RepoTakendown" }, 47 + { "name": "RepoSuspended" }, 48 + { "name": "RepoDeactivated" } 49 + ] 50 + } 51 + } 52 + }
+55
lexicons/com/atproto/sync/listRepos.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.sync.listRepos", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Enumerates all the DID, rev, and commit CID for all repos hosted by this service. Does not require auth; implemented by PDS and Relay.", 8 + "parameters": { 9 + "type": "params", 10 + "properties": { 11 + "limit": { 12 + "type": "integer", 13 + "minimum": 1, 14 + "maximum": 1000, 15 + "default": 500 16 + }, 17 + "cursor": { "type": "string" } 18 + } 19 + }, 20 + "output": { 21 + "encoding": "application/json", 22 + "schema": { 23 + "type": "object", 24 + "required": ["repos"], 25 + "properties": { 26 + "cursor": { "type": "string" }, 27 + "repos": { 28 + "type": "array", 29 + "items": { "type": "ref", "ref": "#repo" } 30 + } 31 + } 32 + } 33 + } 34 + }, 35 + "repo": { 36 + "type": "object", 37 + "required": ["did", "head", "rev"], 38 + "properties": { 39 + "did": { "type": "string", "format": "did" }, 40 + "head": { 41 + "type": "string", 42 + "format": "cid", 43 + "description": "Current repo commit CID" 44 + }, 45 + "rev": { "type": "string" }, 46 + "active": { "type": "boolean" }, 47 + "status": { 48 + "type": "string", 49 + "description": "If active=false, this optional field indicates a possible reason for why the account is not active. If active=false and no status is supplied, then the host makes no claim for why the repository is no longer being hosted.", 50 + "knownValues": ["takendown", "suspended", "deactivated"] 51 + } 52 + } 53 + } 54 + } 55 + }
+23
lexicons/com/atproto/sync/notifyOfUpdate.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.sync.notifyOfUpdate", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Notify a crawling service of a recent update, and that crawling should resume. Intended use is after a gap between repo stream events caused the crawling service to disconnect. Does not require auth; implemented by Relay.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["hostname"], 13 + "properties": { 14 + "hostname": { 15 + "type": "string", 16 + "description": "Hostname of the current service (usually a PDS) that is notifying of update." 17 + } 18 + } 19 + } 20 + } 21 + } 22 + } 23 + }
+23
lexicons/com/atproto/sync/requestCrawl.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.sync.requestCrawl", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Request a service to persistently crawl hosted repos. Expected use is new PDS instances declaring their existence to Relays. Does not require auth.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["hostname"], 13 + "properties": { 14 + "hostname": { 15 + "type": "string", 16 + "description": "Hostname of the current service (eg, PDS) that is requesting to be crawled." 17 + } 18 + } 19 + } 20 + } 21 + } 22 + } 23 + }
+213
lexicons/com/atproto/sync/subscribeRepos.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.sync.subscribeRepos", 4 + "defs": { 5 + "main": { 6 + "type": "subscription", 7 + "description": "Repository event stream, aka Firehose endpoint. Outputs repo commits with diff data, and identity update events, for all repositories on the current server. See the atproto specifications for details around stream sequencing, repo versioning, CAR diff format, and more. Public and does not require auth; implemented by PDS and Relay.", 8 + "parameters": { 9 + "type": "params", 10 + "properties": { 11 + "cursor": { 12 + "type": "integer", 13 + "description": "The last known event seq number to backfill from." 14 + } 15 + } 16 + }, 17 + "message": { 18 + "schema": { 19 + "type": "union", 20 + "refs": [ 21 + "#commit", 22 + "#identity", 23 + "#account", 24 + "#handle", 25 + "#migrate", 26 + "#tombstone", 27 + "#info" 28 + ] 29 + } 30 + }, 31 + "errors": [ 32 + { "name": "FutureCursor" }, 33 + { 34 + "name": "ConsumerTooSlow", 35 + "description": "If the consumer of the stream can not keep up with events, and a backlog gets too large, the server will drop the connection." 36 + } 37 + ] 38 + }, 39 + "commit": { 40 + "type": "object", 41 + "description": "Represents an update of repository state. Note that empty commits are allowed, which include no repo data changes, but an update to rev and signature.", 42 + "required": [ 43 + "seq", 44 + "rebase", 45 + "tooBig", 46 + "repo", 47 + "commit", 48 + "rev", 49 + "since", 50 + "blocks", 51 + "ops", 52 + "blobs", 53 + "time" 54 + ], 55 + "nullable": ["prev", "since"], 56 + "properties": { 57 + "seq": { 58 + "type": "integer", 59 + "description": "The stream sequence number of this message." 60 + }, 61 + "rebase": { "type": "boolean", "description": "DEPRECATED -- unused" }, 62 + "tooBig": { 63 + "type": "boolean", 64 + "description": "Indicates that this commit contained too many ops, or data size was too large. Consumers will need to make a separate request to get missing data." 65 + }, 66 + "repo": { 67 + "type": "string", 68 + "format": "did", 69 + "description": "The repo this event comes from." 70 + }, 71 + "commit": { 72 + "type": "cid-link", 73 + "description": "Repo commit object CID." 74 + }, 75 + "prev": { 76 + "type": "cid-link", 77 + "description": "DEPRECATED -- unused. WARNING -- nullable and optional; stick with optional to ensure golang interoperability." 78 + }, 79 + "rev": { 80 + "type": "string", 81 + "description": "The rev of the emitted commit. Note that this information is also in the commit object included in blocks, unless this is a tooBig event." 82 + }, 83 + "since": { 84 + "type": "string", 85 + "description": "The rev of the last emitted commit from this repo (if any)." 86 + }, 87 + "blocks": { 88 + "type": "bytes", 89 + "description": "CAR file containing relevant blocks, as a diff since the previous repo state.", 90 + "maxLength": 1000000 91 + }, 92 + "ops": { 93 + "type": "array", 94 + "items": { 95 + "type": "ref", 96 + "ref": "#repoOp", 97 + "description": "List of repo mutation operations in this commit (eg, records created, updated, or deleted)." 98 + }, 99 + "maxLength": 200 100 + }, 101 + "blobs": { 102 + "type": "array", 103 + "items": { 104 + "type": "cid-link", 105 + "description": "List of new blobs (by CID) referenced by records in this commit." 106 + } 107 + }, 108 + "time": { 109 + "type": "string", 110 + "format": "datetime", 111 + "description": "Timestamp of when this message was originally broadcast." 112 + } 113 + } 114 + }, 115 + "identity": { 116 + "type": "object", 117 + "description": "Represents a change to an account's identity. Could be an updated handle, signing key, or pds hosting endpoint. Serves as a prod to all downstream services to refresh their identity cache.", 118 + "required": ["seq", "did", "time"], 119 + "properties": { 120 + "seq": { "type": "integer" }, 121 + "did": { "type": "string", "format": "did" }, 122 + "time": { "type": "string", "format": "datetime" }, 123 + "handle": { 124 + "type": "string", 125 + "format": "handle", 126 + "description": "The current handle for the account, or 'handle.invalid' if validation fails. This field is optional, might have been validated or passed-through from an upstream source. Semantics and behaviors for PDS vs Relay may evolve in the future; see atproto specs for more details." 127 + } 128 + } 129 + }, 130 + "account": { 131 + "type": "object", 132 + "description": "Represents a change to an account's status on a host (eg, PDS or Relay). The semantics of this event are that the status is at the host which emitted the event, not necessarily that at the currently active PDS. Eg, a Relay takedown would emit a takedown with active=false, even if the PDS is still active.", 133 + "required": ["seq", "did", "time", "active"], 134 + "properties": { 135 + "seq": { "type": "integer" }, 136 + "did": { "type": "string", "format": "did" }, 137 + "time": { "type": "string", "format": "datetime" }, 138 + "active": { 139 + "type": "boolean", 140 + "description": "Indicates that the account has a repository which can be fetched from the host that emitted this event." 141 + }, 142 + "status": { 143 + "type": "string", 144 + "description": "If active=false, this optional field indicates a reason for why the account is not active.", 145 + "knownValues": ["takendown", "suspended", "deleted", "deactivated"] 146 + } 147 + } 148 + }, 149 + "handle": { 150 + "type": "object", 151 + "description": "DEPRECATED -- Use #identity event instead", 152 + "required": ["seq", "did", "handle", "time"], 153 + "properties": { 154 + "seq": { "type": "integer" }, 155 + "did": { "type": "string", "format": "did" }, 156 + "handle": { "type": "string", "format": "handle" }, 157 + "time": { "type": "string", "format": "datetime" } 158 + } 159 + }, 160 + "migrate": { 161 + "type": "object", 162 + "description": "DEPRECATED -- Use #account event instead", 163 + "required": ["seq", "did", "migrateTo", "time"], 164 + "nullable": ["migrateTo"], 165 + "properties": { 166 + "seq": { "type": "integer" }, 167 + "did": { "type": "string", "format": "did" }, 168 + "migrateTo": { "type": "string" }, 169 + "time": { "type": "string", "format": "datetime" } 170 + } 171 + }, 172 + "tombstone": { 173 + "type": "object", 174 + "description": "DEPRECATED -- Use #account event instead", 175 + "required": ["seq", "did", "time"], 176 + "properties": { 177 + "seq": { "type": "integer" }, 178 + "did": { "type": "string", "format": "did" }, 179 + "time": { "type": "string", "format": "datetime" } 180 + } 181 + }, 182 + "info": { 183 + "type": "object", 184 + "required": ["name"], 185 + "properties": { 186 + "name": { 187 + "type": "string", 188 + "knownValues": ["OutdatedCursor"] 189 + }, 190 + "message": { 191 + "type": "string" 192 + } 193 + } 194 + }, 195 + "repoOp": { 196 + "type": "object", 197 + "description": "A repo operation, ie a mutation of a single record.", 198 + "required": ["action", "path", "cid"], 199 + "nullable": ["cid"], 200 + "properties": { 201 + "action": { 202 + "type": "string", 203 + "knownValues": ["create", "update", "delete"] 204 + }, 205 + "path": { "type": "string" }, 206 + "cid": { 207 + "type": "cid-link", 208 + "description": "For creates and updates, the new record CID. For deletions, null." 209 + } 210 + } 211 + } 212 + } 213 + }
+27
lexicons/com/atproto/temp/addReservedHandle.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.temp.addReservedHandle", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Add a handle to the set of reserved handles.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["handle"], 13 + "properties": { 14 + "handle": { "type": "string" } 15 + } 16 + } 17 + }, 18 + "output": { 19 + "encoding": "application/json", 20 + "schema": { 21 + "type": "object", 22 + "properties": {} 23 + } 24 + } 25 + } 26 + } 27 + }
+22
lexicons/com/atproto/temp/checkSignupQueue.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.temp.checkSignupQueue", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Check accounts location in signup queue.", 8 + "output": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["activated"], 13 + "properties": { 14 + "activated": { "type": "boolean" }, 15 + "placeInQueue": { "type": "integer" }, 16 + "estimatedTimeMs": { "type": "integer" } 17 + } 18 + } 19 + } 20 + } 21 + } 22 + }
+35
lexicons/com/atproto/temp/fetchLabels.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.temp.fetchLabels", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "DEPRECATED: use queryLabels or subscribeLabels instead -- Fetch all labels from a labeler created after a certain date.", 8 + "parameters": { 9 + "type": "params", 10 + "properties": { 11 + "since": { "type": "integer" }, 12 + "limit": { 13 + "type": "integer", 14 + "minimum": 1, 15 + "maximum": 250, 16 + "default": 50 17 + } 18 + } 19 + }, 20 + "output": { 21 + "encoding": "application/json", 22 + "schema": { 23 + "type": "object", 24 + "required": ["labels"], 25 + "properties": { 26 + "labels": { 27 + "type": "array", 28 + "items": { "type": "ref", "ref": "com.atproto.label.defs#label" } 29 + } 30 + } 31 + } 32 + } 33 + } 34 + } 35 + }
+20
lexicons/com/atproto/temp/requestPhoneVerification.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.temp.requestPhoneVerification", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Request a verification code to be sent to the supplied phone number", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["phoneNumber"], 13 + "properties": { 14 + "phoneNumber": { "type": "string" } 15 + } 16 + } 17 + } 18 + } 19 + } 20 + }
+107
src/builtin.rs
··· 1 + //! built-in atproto lexicons for resolving external refs 2 + 3 + use atrium_lex::LexiconDoc; 4 + use std::sync::LazyLock; 5 + 6 + /// all bundled com.atproto.* lexicons 7 + static LEXICONS: LazyLock<Vec<LexiconDoc>> = LazyLock::new(|| { 8 + let mut docs = Vec::new(); 9 + 10 + for json in LEXICON_JSON { 11 + if let Ok(doc) = serde_json::from_str::<LexiconDoc>(json) { 12 + docs.push(doc); 13 + } 14 + } 15 + 16 + docs.sort_by(|a, b| a.id.cmp(&b.id)); 17 + docs 18 + }); 19 + 20 + /// get all built-in lexicon documents 21 + pub fn builtin_lexicons() -> &'static [LexiconDoc] { 22 + &LEXICONS 23 + } 24 + 25 + const LEXICON_JSON: &[&str] = &[ 26 + include_str!("../lexicons/com/atproto/admin/defs.json"), 27 + include_str!("../lexicons/com/atproto/admin/deleteAccount.json"), 28 + include_str!("../lexicons/com/atproto/admin/disableAccountInvites.json"), 29 + include_str!("../lexicons/com/atproto/admin/disableInviteCodes.json"), 30 + include_str!("../lexicons/com/atproto/admin/enableAccountInvites.json"), 31 + include_str!("../lexicons/com/atproto/admin/getAccountInfo.json"), 32 + include_str!("../lexicons/com/atproto/admin/getAccountInfos.json"), 33 + include_str!("../lexicons/com/atproto/admin/getInviteCodes.json"), 34 + include_str!("../lexicons/com/atproto/admin/getSubjectStatus.json"), 35 + include_str!("../lexicons/com/atproto/admin/searchAccounts.json"), 36 + include_str!("../lexicons/com/atproto/admin/sendEmail.json"), 37 + include_str!("../lexicons/com/atproto/admin/updateAccountEmail.json"), 38 + include_str!("../lexicons/com/atproto/admin/updateAccountHandle.json"), 39 + include_str!("../lexicons/com/atproto/admin/updateAccountPassword.json"), 40 + include_str!("../lexicons/com/atproto/admin/updateSubjectStatus.json"), 41 + include_str!("../lexicons/com/atproto/identity/getRecommendedDidCredentials.json"), 42 + include_str!("../lexicons/com/atproto/identity/requestPlcOperationSignature.json"), 43 + include_str!("../lexicons/com/atproto/identity/resolveHandle.json"), 44 + include_str!("../lexicons/com/atproto/identity/signPlcOperation.json"), 45 + include_str!("../lexicons/com/atproto/identity/submitPlcOperation.json"), 46 + include_str!("../lexicons/com/atproto/identity/updateHandle.json"), 47 + include_str!("../lexicons/com/atproto/label/defs.json"), 48 + include_str!("../lexicons/com/atproto/label/queryLabels.json"), 49 + include_str!("../lexicons/com/atproto/label/subscribeLabels.json"), 50 + include_str!("../lexicons/com/atproto/moderation/createReport.json"), 51 + include_str!("../lexicons/com/atproto/moderation/defs.json"), 52 + include_str!("../lexicons/com/atproto/repo/applyWrites.json"), 53 + include_str!("../lexicons/com/atproto/repo/createRecord.json"), 54 + include_str!("../lexicons/com/atproto/repo/defs.json"), 55 + include_str!("../lexicons/com/atproto/repo/deleteRecord.json"), 56 + include_str!("../lexicons/com/atproto/repo/describeRepo.json"), 57 + include_str!("../lexicons/com/atproto/repo/getRecord.json"), 58 + include_str!("../lexicons/com/atproto/repo/importRepo.json"), 59 + include_str!("../lexicons/com/atproto/repo/listMissingBlobs.json"), 60 + include_str!("../lexicons/com/atproto/repo/listRecords.json"), 61 + include_str!("../lexicons/com/atproto/repo/putRecord.json"), 62 + include_str!("../lexicons/com/atproto/repo/strongRef.json"), 63 + include_str!("../lexicons/com/atproto/repo/uploadBlob.json"), 64 + include_str!("../lexicons/com/atproto/server/activateAccount.json"), 65 + include_str!("../lexicons/com/atproto/server/checkAccountStatus.json"), 66 + include_str!("../lexicons/com/atproto/server/confirmEmail.json"), 67 + include_str!("../lexicons/com/atproto/server/createAccount.json"), 68 + include_str!("../lexicons/com/atproto/server/createAppPassword.json"), 69 + include_str!("../lexicons/com/atproto/server/createInviteCode.json"), 70 + include_str!("../lexicons/com/atproto/server/createInviteCodes.json"), 71 + include_str!("../lexicons/com/atproto/server/createSession.json"), 72 + include_str!("../lexicons/com/atproto/server/deactivateAccount.json"), 73 + include_str!("../lexicons/com/atproto/server/defs.json"), 74 + include_str!("../lexicons/com/atproto/server/deleteAccount.json"), 75 + include_str!("../lexicons/com/atproto/server/deleteSession.json"), 76 + include_str!("../lexicons/com/atproto/server/describeServer.json"), 77 + include_str!("../lexicons/com/atproto/server/getAccountInviteCodes.json"), 78 + include_str!("../lexicons/com/atproto/server/getServiceAuth.json"), 79 + include_str!("../lexicons/com/atproto/server/getSession.json"), 80 + include_str!("../lexicons/com/atproto/server/listAppPasswords.json"), 81 + include_str!("../lexicons/com/atproto/server/refreshSession.json"), 82 + include_str!("../lexicons/com/atproto/server/requestAccountDelete.json"), 83 + include_str!("../lexicons/com/atproto/server/requestEmailConfirmation.json"), 84 + include_str!("../lexicons/com/atproto/server/requestEmailUpdate.json"), 85 + include_str!("../lexicons/com/atproto/server/requestPasswordReset.json"), 86 + include_str!("../lexicons/com/atproto/server/reserveSigningKey.json"), 87 + include_str!("../lexicons/com/atproto/server/resetPassword.json"), 88 + include_str!("../lexicons/com/atproto/server/revokeAppPassword.json"), 89 + include_str!("../lexicons/com/atproto/server/updateEmail.json"), 90 + include_str!("../lexicons/com/atproto/sync/getBlob.json"), 91 + include_str!("../lexicons/com/atproto/sync/getBlocks.json"), 92 + include_str!("../lexicons/com/atproto/sync/getCheckout.json"), 93 + include_str!("../lexicons/com/atproto/sync/getHead.json"), 94 + include_str!("../lexicons/com/atproto/sync/getLatestCommit.json"), 95 + include_str!("../lexicons/com/atproto/sync/getRecord.json"), 96 + include_str!("../lexicons/com/atproto/sync/getRepo.json"), 97 + include_str!("../lexicons/com/atproto/sync/getRepoStatus.json"), 98 + include_str!("../lexicons/com/atproto/sync/listBlobs.json"), 99 + include_str!("../lexicons/com/atproto/sync/listRepos.json"), 100 + include_str!("../lexicons/com/atproto/sync/notifyOfUpdate.json"), 101 + include_str!("../lexicons/com/atproto/sync/requestCrawl.json"), 102 + include_str!("../lexicons/com/atproto/sync/subscribeRepos.json"), 103 + include_str!("../lexicons/com/atproto/temp/addReservedHandle.json"), 104 + include_str!("../lexicons/com/atproto/temp/checkSignupQueue.json"), 105 + include_str!("../lexicons/com/atproto/temp/fetchLabels.json"), 106 + include_str!("../lexicons/com/atproto/temp/requestPhoneVerification.json"), 107 + ];
+211
src/codegen.rs
··· 1 + //! python code generation from lexicon documents 2 + 3 + use std::collections::{HashMap, HashSet}; 4 + use std::fs; 5 + use std::io; 6 + use std::path::Path; 7 + 8 + use atrium_lex::lexicon::{LexObject, LexRecord, LexUserType}; 9 + use atrium_lex::LexiconDoc; 10 + use heck::ToSnakeCase; 11 + 12 + use crate::builtin::builtin_lexicons; 13 + use crate::types::{collect_external_refs, property_to_python, to_class_name, RefContext}; 14 + 15 + const HEADER: &str = r#"# auto-generated by pmgfal - do not edit 16 + 17 + from __future__ import annotations 18 + 19 + from typing import Any 20 + 21 + from pydantic import BaseModel, Field 22 + "#; 23 + 24 + /// python keywords that need escaping as field names 25 + const PYTHON_KEYWORDS: &[&str] = &[ 26 + "type", "class", "import", "from", "global", "lambda", "def", "return", "yield", "raise", 27 + "try", "except", "finally", "with", "as", "if", "elif", "else", "for", "while", "break", 28 + "continue", "pass", "and", "or", "not", "in", "is", "None", "True", "False", "async", "await", 29 + ]; 30 + 31 + /// generate pydantic models for all documents 32 + pub fn generate_models( 33 + docs: &[LexiconDoc], 34 + output_dir: &Path, 35 + namespace_prefix: Option<&str>, 36 + ) -> Result<Vec<String>, io::Error> { 37 + let filtered: Vec<_> = docs 38 + .iter() 39 + .filter(|doc| { 40 + namespace_prefix 41 + .map(|p| doc.id.starts_with(p)) 42 + .unwrap_or(true) 43 + }) 44 + .collect(); 45 + 46 + if filtered.is_empty() { 47 + return Ok(vec![]); 48 + } 49 + 50 + // build lookup of all available lexicons (user + builtin) 51 + let mut all_docs: HashMap<&str, &LexiconDoc> = HashMap::new(); 52 + for doc in docs { 53 + all_docs.insert(&doc.id, doc); 54 + } 55 + for doc in builtin_lexicons() { 56 + all_docs.entry(&doc.id).or_insert(doc); 57 + } 58 + 59 + // collect external refs from user documents 60 + let mut external_refs: HashSet<String> = HashSet::new(); 61 + for doc in &filtered { 62 + external_refs.extend(collect_external_refs(doc)); 63 + } 64 + 65 + // find which external refs we can resolve from builtins 66 + let mut resolved_externals: Vec<&LexiconDoc> = Vec::new(); 67 + for ref_nsid in &external_refs { 68 + if let Some(doc) = all_docs.get(ref_nsid.as_str()) { 69 + // only include if not already in user docs 70 + if !filtered.iter().any(|d| d.id == *ref_nsid) { 71 + resolved_externals.push(doc); 72 + } 73 + } 74 + } 75 + resolved_externals.sort_by(|a, b| a.id.cmp(&b.id)); 76 + 77 + fs::create_dir_all(output_dir)?; 78 + 79 + let mut output = String::from(HEADER); 80 + output.push('\n'); 81 + 82 + // generate external deps first (so they're defined before use) 83 + for doc in &resolved_externals { 84 + output.push_str(&format!("\n# {} (builtin)\n", doc.id)); 85 + output.push_str(&generate_document(doc)); 86 + } 87 + 88 + // generate user documents 89 + for doc in &filtered { 90 + output.push_str(&format!("\n# {}\n", doc.id)); 91 + output.push_str(&generate_document(doc)); 92 + } 93 + 94 + let output_file = match namespace_prefix { 95 + Some(prefix) => output_dir.join(format!("{}.py", prefix.replace('.', "_"))), 96 + None => output_dir.join("models.py"), 97 + }; 98 + 99 + fs::write(&output_file, &output)?; 100 + 101 + Ok(vec![output_file.to_string_lossy().to_string()]) 102 + } 103 + 104 + /// generate python code for a single lexicon document 105 + fn generate_document(doc: &LexiconDoc) -> String { 106 + let ctx = RefContext::new(&doc.id); 107 + let mut output = String::new(); 108 + 109 + for (def_name, def) in &doc.defs { 110 + let class_name = to_class_name(&doc.id, def_name); 111 + 112 + match def { 113 + LexUserType::Record(LexRecord { 114 + record, 115 + description, 116 + .. 117 + }) => { 118 + let atrium_lex::lexicon::LexRecordRecord::Object(obj) = record; 119 + let desc = description.as_deref().unwrap_or(&doc.id); 120 + output.push_str(&generate_class(&class_name, obj, Some(desc), &ctx)); 121 + output.push_str("\n\n"); 122 + } 123 + LexUserType::Object(obj) => { 124 + output.push_str(&generate_class( 125 + &class_name, 126 + obj, 127 + obj.description.as_deref(), 128 + &ctx, 129 + )); 130 + output.push_str("\n\n"); 131 + } 132 + LexUserType::Token(_) => { 133 + output.push_str(&format!( 134 + "# token: {}\n{} = \"{}#{}\"\n\n", 135 + class_name, 136 + class_name.to_uppercase(), 137 + doc.id, 138 + def_name 139 + )); 140 + } 141 + _ => {} 142 + } 143 + } 144 + 145 + output 146 + } 147 + 148 + /// generate a pydantic model class 149 + fn generate_class( 150 + class_name: &str, 151 + obj: &LexObject, 152 + description: Option<&str>, 153 + ctx: &RefContext, 154 + ) -> String { 155 + let mut lines = vec![format!("class {class_name}(BaseModel):")]; 156 + 157 + if let Some(desc) = description { 158 + lines.push(format!(" \"\"\"{desc}\"\"\"")); 159 + } 160 + 161 + if obj.properties.is_empty() { 162 + lines.push(" pass".into()); 163 + return lines.join("\n"); 164 + } 165 + 166 + let required: HashSet<_> = obj 167 + .required 168 + .as_ref() 169 + .map(|r| r.iter().map(String::as_str).collect()) 170 + .unwrap_or_default(); 171 + 172 + // generate required fields first, then optional 173 + let mut fields: Vec<_> = obj.properties.iter().collect(); 174 + fields.sort_by_key(|(name, _)| !required.contains(name.as_str())); 175 + 176 + for (name, prop) in fields { 177 + let field_name = to_field_name(name); 178 + let is_required = required.contains(name.as_str()); 179 + 180 + let mut py_type = property_to_python(prop, ctx); 181 + if !is_required { 182 + py_type = format!("{py_type} | None"); 183 + } 184 + 185 + let needs_alias = field_name != *name; 186 + let needs_default = !is_required; 187 + 188 + let field_def = match (needs_alias, needs_default) { 189 + (false, false) => format!(" {field_name}: {py_type}"), 190 + (true, false) => format!(" {field_name}: {py_type} = Field(alias=\"{name}\")"), 191 + (false, true) => format!(" {field_name}: {py_type} = Field(default=None)"), 192 + (true, true) => { 193 + format!(" {field_name}: {py_type} = Field(default=None, alias=\"{name}\")") 194 + } 195 + }; 196 + 197 + lines.push(field_def); 198 + } 199 + 200 + lines.join("\n") 201 + } 202 + 203 + /// convert property name to valid python field name 204 + fn to_field_name(name: &str) -> String { 205 + let snake = name.to_snake_case(); 206 + if PYTHON_KEYWORDS.contains(&snake.as_str()) { 207 + format!("{snake}_") 208 + } else { 209 + snake 210 + } 211 + }
+11 -215
src/lib.rs
··· 1 - use std::fs; 2 - use std::path::Path; 3 - 4 - use atrium_lex::lexicon::{ 5 - LexObject, LexObjectProperty, LexRecord, LexUserType, 6 - }; 7 - use atrium_lex::LexiconDoc; 8 - use heck::{ToPascalCase, ToSnakeCase}; 9 - use pyo3::prelude::*; 10 - 11 - const HEADER: &str = r#"# auto-generated by pmgfal - do not edit 12 - 13 - from __future__ import annotations 14 - 15 - from typing import Any 16 - 17 - from pydantic import BaseModel, Field 18 - "#; 19 - 20 - /// convert lexicon type to python type annotation 21 - fn type_to_python(prop: &LexObjectProperty) -> String { 22 - match prop { 23 - LexObjectProperty::Boolean(_) => "bool".to_string(), 24 - LexObjectProperty::Integer(_) => "int".to_string(), 25 - LexObjectProperty::String(_) => "str".to_string(), 26 - LexObjectProperty::Bytes(_) => "bytes".to_string(), 27 - LexObjectProperty::CidLink(_) => "str".to_string(), 28 - LexObjectProperty::Blob(_) => "dict[str, Any]".to_string(), 29 - LexObjectProperty::Array(arr) => { 30 - let item_type = match &arr.items { 31 - atrium_lex::lexicon::LexArrayItem::Boolean(_) => "bool", 32 - atrium_lex::lexicon::LexArrayItem::Integer(_) => "int", 33 - atrium_lex::lexicon::LexArrayItem::String(_) => "str", 34 - atrium_lex::lexicon::LexArrayItem::Bytes(_) => "bytes", 35 - atrium_lex::lexicon::LexArrayItem::CidLink(_) => "str", 36 - atrium_lex::lexicon::LexArrayItem::Blob(_) => "dict[str, Any]", 37 - atrium_lex::lexicon::LexArrayItem::Ref(_) => "dict[str, Any]", 38 - atrium_lex::lexicon::LexArrayItem::Union(_) => "dict[str, Any]", 39 - atrium_lex::lexicon::LexArrayItem::Unknown(_) => "Any", 40 - }; 41 - format!("list[{}]", item_type) 42 - } 43 - LexObjectProperty::Ref(_) => "dict[str, Any]".to_string(), 44 - LexObjectProperty::Union(_) => "dict[str, Any]".to_string(), 45 - LexObjectProperty::Unknown(_) => "Any".to_string(), 46 - } 47 - } 48 - 49 - /// generate class name from nsid and def name 50 - fn to_class_name(nsid: &str, def_name: &str) -> String { 51 - let mut parts: Vec<&str> = nsid.split('.').collect(); 52 - if def_name != "main" { 53 - parts.push(def_name); 54 - } 55 - parts.iter().map(|p| p.to_pascal_case()).collect() 56 - } 57 - 58 - /// generate python field name 59 - fn to_field_name(name: &str) -> String { 60 - let snake = name.to_snake_case(); 61 - // handle python keywords 62 - match snake.as_str() { 63 - "type" | "class" | "import" | "from" | "global" | "lambda" => format!("{}_", snake), 64 - _ => snake, 65 - } 66 - } 67 - 68 - /// generate a pydantic model class from an object definition 69 - fn generate_object_class( 70 - class_name: &str, 71 - obj: &LexObject, 72 - description: Option<&str>, 73 - ) -> String { 74 - let mut lines = vec![format!("class {}(BaseModel):", class_name)]; 75 - 76 - if let Some(desc) = description { 77 - lines.push(format!(" \"\"\"{}\"\"\"", desc)); 78 - } 79 - 80 - if obj.properties.is_empty() { 81 - lines.push(" pass".to_string()); 82 - return lines.join("\n"); 83 - } 84 - 85 - let required: std::collections::HashSet<_> = obj 86 - .required 87 - .as_ref() 88 - .map(|r| r.iter().collect()) 89 - .unwrap_or_default(); 90 - 91 - for (name, prop) in &obj.properties { 92 - let field_name = to_field_name(name); 93 - let mut py_type = type_to_python(prop); 94 - 95 - let is_required = required.contains(name); 96 - if !is_required { 97 - py_type = format!("{} | None", py_type); 98 - } 99 - 100 - let needs_alias = field_name != *name; 101 - let needs_default = !is_required; 102 - 103 - if needs_alias || needs_default { 104 - let mut field_args = vec![]; 105 - if needs_default { 106 - field_args.push("default=None".to_string()); 107 - } 108 - if needs_alias { 109 - field_args.push(format!("alias=\"{}\"", name)); 110 - } 111 - lines.push(format!( 112 - " {}: {} = Field({})", 113 - field_name, 114 - py_type, 115 - field_args.join(", ") 116 - )); 117 - } else { 118 - lines.push(format!(" {}: {}", field_name, py_type)); 119 - } 120 - } 1 + //! pmgfal - pydantic model generator for atproto lexicons 121 2 122 - lines.join("\n") 123 - } 3 + mod builtin; 4 + mod codegen; 5 + mod parser; 6 + mod types; 124 7 125 - /// parse lexicon files from a directory 126 - fn parse_lexicons(dir: &Path) -> Result<Vec<LexiconDoc>, String> { 127 - let mut docs = vec![]; 8 + use std::path::Path; 128 9 129 - fn visit_dir(dir: &Path, docs: &mut Vec<LexiconDoc>) -> Result<(), String> { 130 - if !dir.is_dir() { 131 - return Err(format!("not a directory: {}", dir.display())); 132 - } 133 - 134 - for entry in fs::read_dir(dir).map_err(|e| e.to_string())? { 135 - let entry = entry.map_err(|e| e.to_string())?; 136 - let path = entry.path(); 137 - 138 - if path.is_dir() { 139 - visit_dir(&path, docs)?; 140 - } else if path.extension().map(|e| e == "json").unwrap_or(false) { 141 - let content = fs::read_to_string(&path).map_err(|e| e.to_string())?; 142 - match serde_json::from_str::<LexiconDoc>(&content) { 143 - Ok(doc) => docs.push(doc), 144 - Err(_) => continue, // skip non-lexicon json 145 - } 146 - } 147 - } 148 - Ok(()) 149 - } 150 - 151 - visit_dir(dir, &mut docs)?; 152 - docs.sort_by(|a, b| a.id.cmp(&b.id)); 153 - Ok(docs) 154 - } 10 + use pyo3::prelude::*; 155 11 156 12 /// generate pydantic models from lexicon files 157 13 #[pyfunction] ··· 164 20 let lexicon_path = Path::new(lexicon_dir); 165 21 let output_path = Path::new(output_dir); 166 22 167 - let docs = parse_lexicons(lexicon_path) 168 - .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e))?; 23 + let docs = parser::parse_lexicons(lexicon_path) 24 + .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?; 169 25 170 - let filtered: Vec<_> = docs 171 - .iter() 172 - .filter(|doc| { 173 - namespace_prefix 174 - .map(|p| doc.id.starts_with(p)) 175 - .unwrap_or(true) 176 - }) 177 - .collect(); 178 - 179 - if filtered.is_empty() { 180 - return Ok(vec![]); 181 - } 182 - 183 - fs::create_dir_all(output_path) 26 + let files = codegen::generate_models(&docs, output_path, namespace_prefix) 184 27 .map_err(|e| PyErr::new::<pyo3::exceptions::PyIOError, _>(e.to_string()))?; 185 28 186 - let mut output = String::from(HEADER); 187 - output.push('\n'); 188 - 189 - for doc in &filtered { 190 - output.push_str(&format!("\n# {}\n", doc.id)); 191 - 192 - for (def_name, def) in &doc.defs { 193 - let class_name = to_class_name(&doc.id, def_name); 194 - 195 - match def { 196 - LexUserType::Record(LexRecord { record, description, .. }) => { 197 - let atrium_lex::lexicon::LexRecordRecord::Object(obj) = record; 198 - let desc = description.as_deref().unwrap_or(&doc.id); 199 - output.push_str(&generate_object_class(&class_name, obj, Some(desc))); 200 - output.push_str("\n\n"); 201 - } 202 - LexUserType::Object(obj) => { 203 - output.push_str(&generate_object_class( 204 - &class_name, 205 - obj, 206 - obj.description.as_deref(), 207 - )); 208 - output.push_str("\n\n"); 209 - } 210 - LexUserType::Token(_) => { 211 - output.push_str(&format!( 212 - "# token: {}\n{} = \"{}#{}\"\n\n", 213 - class_name, 214 - class_name.to_uppercase(), 215 - doc.id, 216 - def_name 217 - )); 218 - } 219 - _ => {} 220 - } 221 - } 222 - } 223 - 224 - let output_file = if let Some(prefix) = namespace_prefix { 225 - output_path.join(format!("{}.py", prefix.replace('.', "_"))) 226 - } else { 227 - output_path.join("models.py") 228 - }; 229 - 230 - fs::write(&output_file, &output) 231 - .map_err(|e| PyErr::new::<pyo3::exceptions::PyIOError, _>(e.to_string()))?; 232 - 233 - Ok(vec![output_file.to_string_lossy().to_string()]) 29 + Ok(files) 234 30 } 235 31 236 32 #[pymodule]
+48
src/parser.rs
··· 1 + //! lexicon file parsing 2 + 3 + use std::fs; 4 + use std::io; 5 + use std::path::Path; 6 + 7 + use atrium_lex::LexiconDoc; 8 + use thiserror::Error; 9 + 10 + #[derive(Error, Debug)] 11 + pub enum ParseError { 12 + #[error("not a directory: {0}")] 13 + NotADirectory(String), 14 + 15 + #[error("io error: {0}")] 16 + Io(#[from] io::Error), 17 + 18 + } 19 + 20 + /// parse all lexicon files from a directory recursively 21 + pub fn parse_lexicons(dir: &Path) -> Result<Vec<LexiconDoc>, ParseError> { 22 + if !dir.is_dir() { 23 + return Err(ParseError::NotADirectory(dir.display().to_string())); 24 + } 25 + 26 + let mut docs = Vec::new(); 27 + visit_dir(dir, &mut docs)?; 28 + docs.sort_by(|a, b| a.id.cmp(&b.id)); 29 + Ok(docs) 30 + } 31 + 32 + fn visit_dir(dir: &Path, docs: &mut Vec<LexiconDoc>) -> Result<(), ParseError> { 33 + for entry in fs::read_dir(dir)? { 34 + let path = entry?.path(); 35 + 36 + if path.is_dir() { 37 + visit_dir(&path, docs)?; 38 + } else if path.extension().is_some_and(|e| e == "json") { 39 + let content = fs::read_to_string(&path)?; 40 + 41 + // skip non-lexicon json files silently 42 + if let Ok(doc) = serde_json::from_str::<LexiconDoc>(&content) { 43 + docs.push(doc); 44 + } 45 + } 46 + } 47 + Ok(()) 48 + }
+166
src/types.rs
··· 1 + //! type conversion from lexicon types to python type annotations 2 + 3 + use std::collections::HashSet; 4 + 5 + use atrium_lex::lexicon::{ 6 + LexArrayItem, LexObject, LexObjectProperty, LexRecord, LexRef, LexRefUnion, LexUserType, 7 + }; 8 + use atrium_lex::LexiconDoc; 9 + use heck::ToPascalCase; 10 + 11 + /// context for resolving refs within a document 12 + pub struct RefContext<'a> { 13 + /// nsid of the current document (e.g., "fm.plyr.track") 14 + pub nsid: &'a str, 15 + } 16 + 17 + impl<'a> RefContext<'a> { 18 + pub fn new(nsid: &'a str) -> Self { 19 + Self { nsid } 20 + } 21 + 22 + /// resolve a ref string to a python class name 23 + /// 24 + /// - `#localDef` -> class in same document 25 + /// - `com.example.foo` -> external nsid main def 26 + /// - `com.example.foo#bar` -> external nsid specific def 27 + pub fn resolve_ref(&self, ref_str: &str) -> String { 28 + if let Some(local_name) = ref_str.strip_prefix('#') { 29 + // local ref within same document 30 + to_class_name(self.nsid, local_name) 31 + } else if let Some((nsid, def_name)) = ref_str.split_once('#') { 32 + // external ref with specific def 33 + to_class_name(nsid, def_name) 34 + } else { 35 + // external ref to main def 36 + to_class_name(ref_str, "main") 37 + } 38 + } 39 + } 40 + 41 + /// convert lexicon property to python type annotation 42 + pub fn property_to_python(prop: &LexObjectProperty, ctx: &RefContext) -> String { 43 + match prop { 44 + LexObjectProperty::Boolean(_) => "bool".into(), 45 + LexObjectProperty::Integer(_) => "int".into(), 46 + LexObjectProperty::String(_) => "str".into(), 47 + LexObjectProperty::Bytes(_) => "bytes".into(), 48 + LexObjectProperty::CidLink(_) => "str".into(), 49 + LexObjectProperty::Blob(_) => "dict[str, Any]".into(), 50 + LexObjectProperty::Unknown(_) => "Any".into(), 51 + LexObjectProperty::Ref(r) => ref_to_python(r, ctx), 52 + LexObjectProperty::Union(u) => union_to_python(u, ctx), 53 + LexObjectProperty::Array(arr) => { 54 + let item_type = array_item_to_python(&arr.items, ctx); 55 + format!("list[{item_type}]") 56 + } 57 + } 58 + } 59 + 60 + /// convert a ref to python type 61 + fn ref_to_python(r: &LexRef, ctx: &RefContext) -> String { 62 + ctx.resolve_ref(&r.r#ref) 63 + } 64 + 65 + /// convert a union to python type 66 + fn union_to_python(u: &LexRefUnion, ctx: &RefContext) -> String { 67 + if u.refs.is_empty() { 68 + return "Any".into(); 69 + } 70 + 71 + let types: Vec<String> = u.refs.iter().map(|r| ctx.resolve_ref(r)).collect(); 72 + 73 + if types.len() == 1 { 74 + types.into_iter().next().unwrap() 75 + } else { 76 + types.join(" | ") 77 + } 78 + } 79 + 80 + /// convert array item type to python 81 + fn array_item_to_python(item: &LexArrayItem, ctx: &RefContext) -> String { 82 + match item { 83 + LexArrayItem::Boolean(_) => "bool".into(), 84 + LexArrayItem::Integer(_) => "int".into(), 85 + LexArrayItem::String(_) => "str".into(), 86 + LexArrayItem::Bytes(_) => "bytes".into(), 87 + LexArrayItem::CidLink(_) => "str".into(), 88 + LexArrayItem::Blob(_) => "dict[str, Any]".into(), 89 + LexArrayItem::Unknown(_) => "Any".into(), 90 + LexArrayItem::Ref(r) => ref_to_python(r, ctx), 91 + LexArrayItem::Union(u) => union_to_python(u, ctx), 92 + } 93 + } 94 + 95 + /// generate python class name from nsid and def name 96 + pub fn to_class_name(nsid: &str, def_name: &str) -> String { 97 + let mut parts: Vec<&str> = nsid.split('.').collect(); 98 + if def_name != "main" { 99 + parts.push(def_name); 100 + } 101 + parts.iter().map(|p| p.to_pascal_case()).collect() 102 + } 103 + 104 + /// collect all external ref nsids from a document 105 + pub fn collect_external_refs(doc: &LexiconDoc) -> HashSet<String> { 106 + let mut refs = HashSet::new(); 107 + 108 + for def in doc.defs.values() { 109 + match def { 110 + LexUserType::Record(LexRecord { record, .. }) => { 111 + let atrium_lex::lexicon::LexRecordRecord::Object(obj) = record; 112 + collect_refs_from_object(obj, &mut refs); 113 + } 114 + LexUserType::Object(obj) => { 115 + collect_refs_from_object(obj, &mut refs); 116 + } 117 + _ => {} 118 + } 119 + } 120 + 121 + // filter to only external refs (not starting with #) 122 + refs.into_iter() 123 + .filter(|r| !r.starts_with('#')) 124 + .map(|r| { 125 + // extract nsid from ref (strip #defName if present) 126 + r.split_once('#').map(|(nsid, _)| nsid.to_string()).unwrap_or(r) 127 + }) 128 + .collect() 129 + } 130 + 131 + fn collect_refs_from_object(obj: &LexObject, refs: &mut HashSet<String>) { 132 + for prop in obj.properties.values() { 133 + collect_refs_from_property(prop, refs); 134 + } 135 + } 136 + 137 + fn collect_refs_from_property(prop: &LexObjectProperty, refs: &mut HashSet<String>) { 138 + match prop { 139 + LexObjectProperty::Ref(r) => { 140 + refs.insert(r.r#ref.clone()); 141 + } 142 + LexObjectProperty::Union(u) => { 143 + for r in &u.refs { 144 + refs.insert(r.clone()); 145 + } 146 + } 147 + LexObjectProperty::Array(arr) => { 148 + collect_refs_from_array_item(&arr.items, refs); 149 + } 150 + _ => {} 151 + } 152 + } 153 + 154 + fn collect_refs_from_array_item(item: &LexArrayItem, refs: &mut HashSet<String>) { 155 + match item { 156 + LexArrayItem::Ref(r) => { 157 + refs.insert(r.r#ref.clone()); 158 + } 159 + LexArrayItem::Union(u) => { 160 + for r in &u.refs { 161 + refs.insert(r.clone()); 162 + } 163 + } 164 + _ => {} 165 + } 166 + }
+92
tests/test_generate.py
··· 159 159 # verify optional field default 160 160 instance2 = TestExample(name="test") 161 161 assert instance2.count is None 162 + 163 + def test_internal_ref_resolution(self): 164 + """internal refs like #subDef should resolve to class names.""" 165 + from pmgfal import generate 166 + 167 + lexicon = { 168 + "lexicon": 1, 169 + "id": "fm.plyr.track", 170 + "defs": { 171 + "main": { 172 + "type": "record", 173 + "record": { 174 + "type": "object", 175 + "properties": { 176 + "title": {"type": "string"}, 177 + "features": { 178 + "type": "array", 179 + "items": {"type": "ref", "ref": "#featuredArtist"}, 180 + }, 181 + }, 182 + "required": ["title"], 183 + }, 184 + }, 185 + "featuredArtist": { 186 + "type": "object", 187 + "properties": { 188 + "did": {"type": "string"}, 189 + "handle": {"type": "string"}, 190 + }, 191 + "required": ["did", "handle"], 192 + }, 193 + }, 194 + } 195 + 196 + with tempfile.TemporaryDirectory() as tmpdir: 197 + lexicon_dir = Path(tmpdir) / "lexicons" 198 + lexicon_dir.mkdir() 199 + (lexicon_dir / "track.json").write_text(json.dumps(lexicon)) 200 + 201 + output_dir = Path(tmpdir) / "generated" 202 + files = generate(str(lexicon_dir), str(output_dir)) 203 + 204 + content = Path(files[0]).read_text() 205 + 206 + # internal ref should resolve to class name, not dict 207 + assert "list[FmPlyrTrackFeaturedArtist]" in content 208 + assert "dict[str, Any]" not in content or "features" not in content 209 + 210 + # the referenced class should be generated 211 + assert "class FmPlyrTrackFeaturedArtist(BaseModel):" in content 212 + 213 + def test_external_ref_resolution(self): 214 + """external refs should resolve to class names.""" 215 + from pmgfal import generate 216 + 217 + lexicon = { 218 + "lexicon": 1, 219 + "id": "fm.plyr.like", 220 + "defs": { 221 + "main": { 222 + "type": "record", 223 + "record": { 224 + "type": "object", 225 + "properties": { 226 + "subject": { 227 + "type": "ref", 228 + "ref": "com.atproto.repo.strongRef", 229 + }, 230 + }, 231 + "required": ["subject"], 232 + }, 233 + }, 234 + }, 235 + } 236 + 237 + with tempfile.TemporaryDirectory() as tmpdir: 238 + lexicon_dir = Path(tmpdir) / "lexicons" 239 + lexicon_dir.mkdir() 240 + (lexicon_dir / "like.json").write_text(json.dumps(lexicon)) 241 + 242 + output_dir = Path(tmpdir) / "generated" 243 + files = generate(str(lexicon_dir), str(output_dir)) 244 + 245 + content = Path(files[0]).read_text() 246 + 247 + # external ref should resolve to class name 248 + assert "subject: ComAtprotoRepoStrongRef" in content 249 + 250 + # builtin strongRef class should be generated 251 + assert "class ComAtprotoRepoStrongRef(BaseModel):" in content 252 + assert "uri: str" in content 253 + assert "cid: str" in content