EP

Hosted data server

Every project on Elastic Projects is backed by a real AT Protocol repository. Elastic Projects hosts it here. It serves over standard atproto sync endpoints. This page explains what exists, how the storage works, and where the limits are.

What each project gets

Piece Value
Repo identity did:web:<slug>.elasticprojects.com
Handle <slug>.elasticprojects.com
DID document https://<slug>.elasticprojects.com/.well-known/did.json
Handle proof https://<slug>.elasticprojects.com/.well-known/atproto-did
Sync endpoint https://elasticprojects.com/xrpc/com.atproto.sync.getRepo

The slug comes from the project name plus a short hash of the space URI. It stays stable for the life of the project. The repo is created the first time someone calls a sync endpoint for it.

Fetching a repo

Spaces are permissioned. The sync endpoints take the project share token as the credential. It is the same capability that opens the /join page and the git clone URL. Grab the token from the share dialog, then:

# The signed repository as a CAR file
curl -o repo.car "https://elasticprojects.com/xrpc/com.atproto.sync.getRepo?did=did:web:SLUG.elasticprojects.com&token=TOKEN"

# The current head commit and revision
curl "https://elasticprojects.com/xrpc/com.atproto.sync.getLatestCommit?did=did:web:SLUG.elasticprojects.com&token=TOKEN"

The CAR file is a spec-correct atproto repository. Every commit is signed with the repo secp256k1 key, the one published in its DID document. Any atproto library can verify it. Each published file is a record in the com.elasticprojects.file collection. The key is the path with slashes turned into colons.

Two identity endpoints are public because they only say who a repo is, never what it holds:

curl "https://elasticprojects.com/xrpc/com.atproto.server.describeServer"
curl "https://elasticprojects.com/xrpc/com.atproto.identity.resolveHandle?handle=SLUG.elasticprojects.com"

Writing to a repo

Projects are closed communities. Writes use the same share-token capability as reads. The standard atproto record endpoints are live. A record written over XRPC matches a file published in the app. Same validation, same tree, same signed history.

# Create or replace a file. The rkey is the path with "/" as ":".
curl -X POST "https://elasticprojects.com/xrpc/com.atproto.repo.putRecord" \
  -H "Authorization: Bearer TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "repo": "did:web:SLUG.elasticprojects.com",
    "collection": "com.elasticprojects.file",
    "rkey": "notes:hello.md",
    "record": { "text": "# Hello from XRPC\n" }
  }'

# Read one record back
curl "https://elasticprojects.com/xrpc/com.atproto.repo.getRecord?repo=did:web:SLUG.elasticprojects.com&collection=com.elasticprojects.file&rkey=notes:hello.md&token=TOKEN"

# Delete a file
curl -X POST "https://elasticprojects.com/xrpc/com.atproto.repo.deleteRecord" \
  -H "Authorization: Bearer TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "repo": "did:web:SLUG.elasticprojects.com",
    "collection": "com.elasticprojects.file",
    "rkey": "notes:hello.md"
  }'

com.atproto.repo.applyWrites batches up to 50 creates, updates, and deletes into one signed commit:

curl -X POST "https://elasticprojects.com/xrpc/com.atproto.repo.applyWrites" \
  -H "Authorization: Bearer TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "repo": "did:web:SLUG.elasticprojects.com",
    "writes": [
      { "$type": "com.atproto.repo.applyWrites#create",
        "collection": "com.elasticprojects.file",
        "rkey": "plan:launch.md",
        "value": { "text": "- [ ] pick a date\n" } },
      { "$type": "com.atproto.repo.applyWrites#delete",
        "collection": "com.elasticprojects.file",
        "rkey": "plan:old.md" }
    ]
  }'

Writes enforce the same rules as the app editor. Text files only. Cap is 100 KB per document. JSON must parse. Paths are normalized. Traversal is rejected. Paths that cannot be record keys (for example Next.js bracket segments) get hashed rkeys. Pass the real path in the record path field when writing those. Writes are attributed to the project authority. The share token is a project-level capability, not a personal one.

The ep CLI speaks these endpoints in remote mode. A whole directory can be published or pulled as one batch. See the source control page for the commands.

How the storage works

The repo store runs on object storage with a write-ahead log. That shape matches modern git hosts for repositories at scale. There is no relational database in the write path.

In production the store is Vercel Blob. In development and tests it is a directory on disk with the same compare-and-swap semantics.

Honest limits