Phase 1 — SCIM 2.0

Live State

What Vellby has provisioned into this app. Resets on pod restart.

Users

Groups

Events

Recent SCIM Events

No events yet — connect Vellby to start provisioning.

SCIM Endpoints

SCIM 2.0 (RFC 7642–7644) is a standard HTTP API for automating user/group provisioning. Vellby acts as the provider; your app is the consumer.

Auth: Authorization: Bearer <scim_token> — the token is set in the app entity's attributes.scim_token field.

Users

GET/scim/v2/Users

List all provisioned users. Returns a ListResponse.

GET/scim/v2/Users/{id}

Fetch a single user.

POST/scim/v2/Users

Create a user. Return 201 with the created resource.

PUT/scim/v2/Users/{id}

Full replace.

PATCH/scim/v2/Users/{id}

Partial update — toggle active: false to deprovision.

DELETE/scim/v2/Users/{id}

Delete. Return 204.

Groups

GET/scim/v2/Groups

List all groups (= context entities connected to this app entity).

GET/scim/v2/Groups/{id}

Fetch group + members.

POST/scim/v2/Groups

Create a group.

PATCH/scim/v2/Groups/{id}

Add/remove members — the primary provisioning operation.

DELETE/scim/v2/Groups/{id}

Delete a group.

Connect Guide

  1. Create an app entity in Vellby
  2. Set attributes: {"service":"mock","scim_token":"any-secret","scim_endpoint":"https://mock-app.vellby.com/scim/v2"}
  3. Connect that app entity to one or more context entities
  4. Vellby will push group/user changes to this URL automatically on every connection change

Key implementation rules

RuleDetail
IDs are provider-assignedStore and return the id Vellby sends verbatim — don't generate your own.
PATCH uses Operations{"Operations":[{"op":"add"|"remove"|"replace","path":"...","value":...}]}
Filter path syntaxRemove ops use members[value eq "uuid"] — parse with regex.
ListResponse wrapperGET list endpoints must wrap results: {"schemas":[...],"totalResults":N,"Resources":[...]}
204 for DELETENo body on successful delete.

Test with curl

Create a user

curl -X POST https://mock-app.vellby.com/scim/v2/Users   -H "Authorization: Bearer any-token"   -H "Content-Type: application/json"   -d '{"schemas":["urn:ietf:params:scim:schemas:core:2.0:User"],
       "userName":"alice@example.com","displayName":"Alice"}'

Add member to group

curl -X PATCH https://mock-app.vellby.com/scim/v2/Groups/entity-42   -H "Authorization: Bearer any-token"   -H "Content-Type: application/json"   -d '{"schemas":["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
       "Operations":[{"op":"add","path":"members",
         "value":[{"value":"user-uuid","display":"Alice"}]}]}'

Remove member

curl -X PATCH https://mock-app.vellby.com/scim/v2/Groups/entity-42   -H "Authorization: Bearer any-token"   -H "Content-Type: application/json"   -d '{"schemas":["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
       "Operations":[{"op":"remove","path":"members[value eq \"user-uuid\"]"}]}'
Phase 2 — ActivityPub

AP Live State

ActivityPub actors created on this mock service and their follower counts from Vellby.

Actors

No actors yet — send a Follow activity to create one.

Recent Inbox Events

No activities received yet.

ActivityPub Endpoints

ActivityPub (W3C Recommendation) is a decentralised social protocol. Each Vellby entity becomes an Actor. Any ActivityPub-compatible service (Mastodon, Ghost, Lemmy, Pixelfed) can follow it without SCIM or Keycloak.

How federation works: Vellby sends a Follow activity to your service's inbox when a user joins a connected entity. Your service stores the follower and begins delivering content to Vellby's entity inbox. Auth uses HTTP Signatures (draft-cavage-http-signatures) — Vellby signs requests with the entity's RSA private key; your service verifies against the Actor's publicKeyPem.
GET/.well-known/webfinger?resource=acct:{name}@{domain}

Identity discovery (RFC 7033). Returns a JRD pointing to the Actor URL.

GET/ap/actors/{name}

Actor document (JSON-LD, application/activity+json). Contains inbox URL, followers URL, and RSA public key.

GET/ap/actors/{name}/followers

OrderedCollection of follower Actor URLs.

POST/ap/actors/{name}/inbox

Receive activities. Handles Follow, Undo(Follow), Create. Verify HTTP Signature before trusting.

Vellby-side endpoints (on api.vellby.com)

GET/.well-known/webfinger?resource=acct:entity-{id}@api.vellby.com

Discover any Vellby entity as an AP Actor.

GET/ap/entities/{id}

Vellby entity Actor document with RSA public key for HTTP Signature verification.

GET/ap/entities/{id}/followers

Connected person entities as followers.

POST/ap/entities/{id}/inbox

Vellby's inbox — remote services send Follow requests here.

ActivityPub Connect Guide

To federate your service with Vellby entities:

  1. Expose GET /.well-known/webfinger — return a JRD with a self link to your Actor URL
  2. Expose GET /ap/actors/{id} — return an Actor document with inbox, followers, and publicKey
  3. Expose POST /ap/actors/{id}/inbox — accept and verify signed activities
  4. On receiving a Follow from Vellby: add the follower and send an Accept back to their inbox
  5. Deliver content as Create activities to all followers' inboxes, signed with your Actor's private key

HTTP Signature verification (pseudocode)

sig_header = request.headers["Signature"]
params = parse_key_value(sig_header)  # keyId, headers, signature
actor = GET(params["keyId"].split("#")[0])  # fetch remote Actor
pem   = actor["publicKey"]["publicKeyPem"]
signed_string = rebuild_signed_string(params["headers"], request)
verify_rsa_sha256(pem, signed_string, base64.decode(params["signature"]))

ActivityPub Test with curl

Discover an actor via WebFinger

curl "https://mock-app.vellby.com/.well-known/webfinger?resource=acct:design-team@mock-app.vellby.com"   -H "Accept: application/jrd+json"

Fetch Actor document

curl https://mock-app.vellby.com/ap/actors/design-team   -H "Accept: application/activity+json"

Send a Follow activity (no sig check in mock)

curl -X POST https://mock-app.vellby.com/ap/actors/design-team/inbox   -H "Content-Type: application/activity+json"   -d '{
    "@context": "https://www.w3.org/ns/activitystreams",
    "type": "Follow",
    "actor": "https://api.vellby.com/ap/entities/5",
    "object": "https://mock-app.vellby.com/ap/actors/design-team"
  }'

Discover a Vellby entity as an AP Actor

curl "https://api.vellby.com/.well-known/webfinger?resource=acct:entity-5@api.vellby.com"   -H "Accept: application/jrd+json"
Phase 3 — DID + Verifiable Credentials

VC Live State

Recent Verifiable Credentials submitted to the verifier on this demo service.

Recent Verifications

No credentials verified yet — use the VC Verifier below.

DID + VC Endpoints

W3C Decentralized Identifiers (DIDs) give each entity a portable, self-sovereign identity. Verifiable Credentials (VCs) let Vellby issue cryptographically signed membership proofs that any service can verify offline — no runtime dependency on Vellby.

Trust model inversion: Instead of "call Vellby to check if this user is a member," the user presents a signed credential. You verify the signature against the issuer's DID public key. Vellby need not be reachable at verification time.

This mock service (demo)

GET/.well-known/did.json

This service's W3C DID document. Contains an RSA public key (JsonWebKey2020) for verifying signatures.

POST/credentials/verify

Submit a JWT VC (from Vellby's /api/credentials/entity/{id}) and get back the decoded payload. Signature not checked in this demo.

Vellby-side endpoints (on api.vellby.com)

GET/.well-known/did.json

Vellby platform DID document. Use this to get the issuer public key for VC signature verification.

GET/users/{keycloak_uuid}/did.json

User DID document — wraps the user's Keycloak UUID in a did:web identifier.

GET/api/credentials/entity/{entity_id}?user_id={uuid}

Issue an EntityMembership VC (JWT format) for the given user+entity pair. User must be an active member.

VC Connect Guide

To accept Vellby-issued VCs as proof of entity membership:

  1. User calls GET https://api.vellby.com/api/credentials/entity/{id}?user_id={uuid} — receives a JWT VC
  2. User presents the JWT to your service (Authorization header, POST body, QR code, etc.)
  3. Your service decodes the JWT header to get alg: RS256
  4. Extract iss from JWT payload — this is the issuer DID, e.g. did:web:api.vellby.com
  5. Resolve the DID: fetch https://api.vellby.com/.well-known/did.json
  6. Extract verificationMethod[0].publicKeyJwk — convert to RSA public key
  7. Verify the JWT RS256 signature
  8. Check exp (not expired), credentialSubject.entity_id matches your expected entity
  9. Grant access based on credentialSubject.role

VC payload structure

{
  "iss": "did:web:api.vellby.com",
  "sub": "did:web:api.vellby.com:users:{keycloak-uuid}",
  "iat": 1234567890,
  "exp": 1266103890,
  "vc": {
    "@context": ["https://www.w3.org/2018/credentials/v1"],
    "type": ["VerifiableCredential", "EntityMembership"],
    "issuer": "did:web:api.vellby.com",
    "credentialSubject": {
      "id": "did:web:api.vellby.com:users:{uuid}",
      "entity_id": 5,
      "entity_name": "Design Team",
      "entity_type": "context",
      "role": "member"
    }
  }
}

VC Verifier

Paste a JWT issued by GET https://api.vellby.com/api/credentials/entity/{id}?user_id={uuid} to inspect it.