diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..39249ed1a2b4edc7e10e4b738b0a899b868a0d3f
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1,2 @@
+To run examples, use the following command:
+`python <example.py>`
\ No newline at end of file
diff --git a/examples/configuration/config.yaml b/examples/configuration/config.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..77c630803c3eae1e6a941b7d78110e391099d3b5
--- /dev/null
+++ b/examples/configuration/config.yaml
@@ -0,0 +1,44 @@
+# PyFed Configuration Example
+
+# Core Settings
+domain: example.com
+debug: false
+
+# Database
+database:
+  url: postgresql://user:pass@localhost/pyfed
+  min_connections: 5
+  max_connections: 20
+  timeout: 30
+
+# Redis
+redis:
+  url: redis://localhost
+  pool_size: 10
+  timeout: 30
+
+# Security
+security:
+  key_path: keys/
+  signature_ttl: 300
+  max_payload_size: 5000000
+  allowed_algorithms:
+    - rsa-sha256
+
+# Federation
+federation:
+  shared_inbox: true
+  delivery_timeout: 30
+  max_recipients: 100
+  retry_delay: 300
+
+# Media
+media:
+  upload_path: uploads/
+  max_size: 10000000
+  allowed_types:
+    - image/jpeg
+    - image/png
+    - image/gif
+    - video/mp4
+    - audio/mpeg 
\ No newline at end of file
diff --git a/examples/federation_example.py b/examples/federation_example.py
new file mode 100644
index 0000000000000000000000000000000000000000..8c2d0661af79511f7197bced024ff90d72511b69
--- /dev/null
+++ b/examples/federation_example.py
@@ -0,0 +1,200 @@
+"""
+Federation examples showing common ActivityPub interactions.
+"""
+
+import asyncio
+import logging
+from datetime import datetime, timezone
+from pathlib import Path
+import sys
+
+# Add src directory to Python path
+src_path = Path(__file__).parent.parent / "src"
+sys.path.insert(0, str(src_path))
+
+from pyfed.security.key_management import KeyManager
+from pyfed.federation.delivery import ActivityDelivery
+from pyfed.federation.discovery import InstanceDiscovery
+from pyfed.protocols.webfinger import WebFingerClient
+from pyfed.models import APCreate, APNote, APPerson, APFollow, APLike, APAnnounce
+
+# Configure logging
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+class FederationExample:
+    """Federation interaction examples."""
+
+    def __init__(self, domain: str = "example.com"):
+        self.domain = domain
+        self.key_manager = None
+        self.delivery = None
+        self.webfinger = None
+        self.discovery = None
+
+    async def initialize(self):
+        """Initialize components."""
+        # Initialize key manager
+        self.key_manager = KeyManager(
+            domain=self.domain,
+            keys_path=str(Path("example_keys").resolve())
+        )
+        logger.info("Initializing key manager...")
+        await self.key_manager.initialize()
+
+        # Initialize delivery
+        self.delivery = ActivityDelivery(
+            key_manager=self.key_manager,
+            timeout=30
+        )
+        logger.info("Initializing delivery...")
+        await self.delivery.initialize()
+
+        # Initialize WebFinger client with SSL verification disabled for testing
+        self.webfinger = WebFingerClient(verify_ssl=False)
+        logger.info("Initializing WebFinger client...")
+        await self.webfinger.initialize()
+
+    async def send_public_post(self, content: str):
+        """Send a public post to the Fediverse."""
+        logger.info(f"Sending public post: {content}")
+
+        # Create local actor
+        actor = APPerson(
+            id=f"https://{self.domain}/users/alice",
+            name="Alice",
+            preferred_username="alice",
+            inbox=f"https://{self.domain}/users/alice/inbox",
+            outbox=f"https://{self.domain}/users/alice/outbox"
+        )
+
+        # Create note
+        note = APNote(
+            id=f"https://{self.domain}/notes/{datetime.utcnow().timestamp()}",
+            content=content,
+            attributed_to=str(actor.id),
+            to=["https://www.w3.org/ns/activitystreams#Public"],
+            published=datetime.utcnow().isoformat()
+        )
+
+        # Create activity
+        create_activity = APCreate(
+            id=f"https://{self.domain}/activities/{datetime.utcnow().timestamp()}",
+            actor=str(actor.id),
+            object=note,
+            to=note.to,
+            published=datetime.utcnow().isoformat()
+        )
+
+        # Deliver to followers (example)
+        activity_dict = create_activity.serialize()
+        result = await self.delivery.deliver_activity(
+            activity=activity_dict,
+            recipients=[f"https://{self.domain}/followers"]
+        )
+        logger.info(f"Delivery result: {result}")
+
+    async def send_direct_message(self, recipient: str, content: str):
+        """Send a direct message to a specific user."""
+        logger.info(f"Sending direct message to {recipient}")
+
+        # Resolve recipient's inbox
+        inbox_url = await self.webfinger.get_inbox_url(recipient)
+        if not inbox_url:
+            logger.error(f"Could not find inbox for {recipient}")
+            return
+
+        # Create note
+        note = APNote(
+            id=f"https://{self.domain}/notes/{datetime.utcnow().timestamp()}",
+            content=content,
+            attributed_to=f"https://{self.domain}/users/alice",
+            to=[inbox_url],
+            published=datetime.utcnow().isoformat()
+        )
+
+        # Create activity
+        create_activity = APCreate(
+            id=f"https://{self.domain}/activities/{datetime.utcnow().timestamp()}",
+            actor=f"https://{self.domain}/users/alice",
+            object=note,
+            to=note.to,
+            published=datetime.utcnow().isoformat()
+        )
+
+        # Deliver direct message
+        activity_dict = create_activity.serialize()
+        result = await self.delivery.deliver_activity(
+            activity=activity_dict,
+            recipients=[inbox_url]
+        )
+        logger.info(f"Delivery result: {result}")
+
+    async def follow_account(self, account: str):
+        """Follow a remote account."""
+        logger.info(f"Following account: {account}")
+
+        # Resolve account
+        actor_url = await self.webfinger.get_actor_url(account)
+        if not actor_url:
+            logger.error(f"Could not resolve account {account}")
+            return
+
+        # Create Follow activity
+        follow = APFollow(
+            id=f"https://{self.domain}/activities/follow_{datetime.utcnow().timestamp()}",
+            actor=f"https://{self.domain}/users/alice",
+            object=actor_url,
+            published=datetime.utcnow().isoformat()
+        )
+
+        # Get target inbox
+        inbox_url = await self.webfinger.get_inbox_url(account)
+        if not inbox_url:
+            logger.error(f"Could not find inbox for {account}")
+            return
+
+        # Deliver Follow activity
+        activity_dict = follow.serialize()
+        result = await self.delivery.deliver_activity(
+            activity=activity_dict,
+            recipients=[inbox_url]
+        )
+        logger.info(f"Follow result: {result}")
+
+    async def close(self):
+        """Clean up resources."""
+        if self.delivery and hasattr(self.delivery, 'close'):
+            await self.delivery.close()
+        if self.webfinger and hasattr(self.webfinger, 'close'):
+            await self.webfinger.close()
+
+async def main():
+    """Run federation examples."""
+    federation = FederationExample()
+    logger.info("Initializing federation...")
+    await federation.initialize()
+
+    try:
+        # Example 1: Send public post
+        logger.info("Sending public post...")
+        await federation.send_public_post(
+            "Hello #Fediverse! This is a test post from PyFed!"
+        )
+
+        # Example 2: Send direct message
+        logger.info("Sending direct message...")
+        await federation.send_direct_message(
+            "kene29@mastodon.social",
+            "Hello! This is a direct message test from PyFed."
+        )
+
+        # Example 3: Follow account
+        logger.info("Following account...")
+        await federation.follow_account("kene29@mastodon.social")
+
+    finally:
+        await federation.close()
+
+if __name__ == "__main__":
+    asyncio.run(main()) 
\ No newline at end of file
diff --git a/examples/integration_examples.py b/examples/integration_examples.py
new file mode 100644
index 0000000000000000000000000000000000000000..3c483af555494f80313a6625b6957d47eb6116a3
--- /dev/null
+++ b/examples/integration_examples.py
@@ -0,0 +1,73 @@
+"""
+Framework integration examples.
+"""
+
+# FastAPI Integration
+from fastapi import FastAPI
+from pyfed.integration.frameworks.fastapi import FastAPIIntegration
+from pyfed.integration.config import IntegrationConfig
+
+async def fastapi_example():
+    """FastAPI integration example."""
+    # Create config
+    config = IntegrationConfig(
+        domain="example.com",
+        database_url="postgresql://user:pass@localhost/pyfed",
+        redis_url="redis://localhost",
+        media_path="uploads/",
+        key_path="keys/"
+    )
+
+    # Initialize integration
+    integration = FastAPIIntegration(config)
+    await integration.initialize()
+
+    app = integration.app
+
+    # Add custom routes
+    @app.get("/custom")
+    async def custom_route():
+        return {"message": "Custom route"}
+
+    return app
+
+# Django Integration
+from django.urls import path
+from pyfed.integration.frameworks.django import DjangoIntegration
+
+# settings.py
+PYFED_CONFIG = {
+    'domain': 'example.com',
+    'database_url': 'postgresql://user:pass@localhost/pyfed',
+    'redis_url': 'redis://localhost',
+    'media_path': 'uploads/',
+    'key_path': 'keys/',
+}
+
+# urls.py
+from django.urls import path, include
+
+urlpatterns = [
+    path('', include('pyfed.integration.frameworks.django.urls')),
+]
+
+# Flask Integration
+from flask import Flask
+from pyfed.integration.frameworks.flask import FlaskIntegration
+
+def flask_example():
+    """Flask integration example."""
+    app = Flask(__name__)
+    
+    config = IntegrationConfig(
+        domain="example.com",
+        database_url="postgresql://user:pass@localhost/pyfed",
+        redis_url="redis://localhost",
+        media_path="uploads/",
+        key_path="keys/"
+    )
+
+    integration = FlaskIntegration(config)
+    integration.initialize()
+
+    return app 
\ No newline at end of file
diff --git a/examples/note_example.py b/examples/note_example.py
new file mode 100644
index 0000000000000000000000000000000000000000..c966d54594fc4141f92563910cf5459e5a5b5d6e
--- /dev/null
+++ b/examples/note_example.py
@@ -0,0 +1,147 @@
+"""
+Note creation and interaction examples.
+"""
+
+import asyncio
+import logging
+from datetime import datetime, timezone
+import os
+import sys
+from pathlib import Path
+import ssl
+
+# Add src directory to Python path
+src_path = Path(__file__).parent.parent / "src"
+sys.path.insert(0, str(src_path))
+
+from pyfed.models import APCreate, APNote, APPerson
+from pyfed.security import KeyManager
+from pyfed.storage import StorageBackend
+from pyfed.federation import ActivityDelivery
+from pyfed.protocols.webfinger import WebFingerClient
+from pyfed.serializers.json_serializer import to_json, from_json
+
+# Configure logging
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+def ensure_directories():
+    """Ensure required directories exist."""
+    dirs = [
+        "example_keys",
+        "example_data",
+        "example_media"
+    ]
+    
+    for dir_name in dirs:
+        path = Path(dir_name)
+        path.mkdir(parents=True, exist_ok=True)
+        logger.info(f"Ensured directory exists: {path}")
+
+async def create_note_example():
+    """Example of creating and delivering a note."""
+    try:
+        ensure_directories()
+
+        # Initialize storage
+        storage = StorageBackend.create(
+            provider="sqlite",
+            database_url="example_data/pyfed_example.db"
+        )
+        await storage.initialize()
+
+        # Initialize key manager
+        key_manager = KeyManager(
+            domain="example.com",
+            active_keys=Path("example_keys").resolve(),
+            keys_path=str(Path("example_keys").resolve())
+        )
+        await key_manager.initialize()
+
+        # Initialize delivery
+        delivery = ActivityDelivery(
+            key_manager=key_manager,
+            timeout=30
+        )
+        await delivery.initialize()
+
+        # Initialize WebFinger client with SSL verification disabled for testing
+        webfinger = WebFingerClient(verify_ssl=False)
+        await webfinger.initialize()
+
+        # Get inbox URL for recipient
+        recipient = "kene29@mastodon.social"
+        logger.info(f"Looking up inbox for {recipient}...")
+        inbox_url = await webfinger.get_inbox_url(recipient)
+        if not inbox_url:
+            logger.error(f"Could not find inbox for {recipient}")
+            return
+
+        logger.info(f"Found inbox URL: {inbox_url}")
+
+        # Create actor
+        actor = APPerson(
+            id="https://example.com/users/alice",
+            name="Alice",
+            preferred_username="alice",
+            inbox="https://example.com/users/alice/inbox",
+            outbox="https://example.com/users/alice/outbox",
+            followers="https://example.com/users/alice/followers"
+        )
+
+        # Create note with string attributed_to
+        note = APNote(
+            id=f"https://example.com/notes/{datetime.now(timezone.utc).timestamp()}",
+            content=f"Hello @{recipient}! This is a test note!",
+            attributed_to=str(actor.id),  # Convert URL to string
+            to=[inbox_url],
+            cc=["https://www.w3.org/ns/activitystreams#Public"],
+            published=datetime.now(timezone.utc).isoformat()
+        )
+
+        # Create activity
+        create_activity = APCreate(
+            id=f"https://example.com/activities/{datetime.now(timezone.utc).timestamp()}",
+            actor=str(actor.id),  # Convert URL to string
+            object=note,
+            to=note.to,
+            cc=note.cc,
+            published=datetime.now(timezone.utc).isoformat(),
+        )
+
+        # Serialize and deliver
+        logger.info("Serializing activity...")
+        activity_dict = create_activity.serialize()
+        logger.info(f"Serialized activity: {activity_dict}")
+        logger.info(f"Activity: {to_json(create_activity, indent=2)}")
+
+        logger.info("Delivering activity...")
+        result = await delivery.deliver_activity(
+            activity=activity_dict,
+            recipients=[inbox_url]
+        )
+        logger.info(f"Delivery result: {result}")
+
+    except Exception as e:
+        logger.error(f"Error in note example: {e}")
+        raise
+    finally:
+        if 'storage' in locals():
+            await storage.close()
+        if 'delivery' in locals():
+            await delivery.close()
+        if 'webfinger' in locals():
+            await webfinger.close()
+
+def main():
+    """Main entry point."""
+    try:
+        asyncio.run(create_note_example())
+    except KeyboardInterrupt:
+        logger.info("Example stopped by user")
+    except Exception as e:
+        logger.error(f"Example failed: {e}")
+        sys.exit(1)
+
+if __name__ == "__main__":
+    main() 
\ No newline at end of file
diff --git a/examples/run_example.py b/examples/run_example.py
new file mode 100644
index 0000000000000000000000000000000000000000..420ec82ea95b1e9e4196ebaaa6e8248b3c6b4aa7
--- /dev/null
+++ b/examples/run_example.py
@@ -0,0 +1,145 @@
+"""
+PyFed runnable example.
+"""
+
+import asyncio
+import logging
+from datetime import datetime
+import os
+import sys
+from pathlib import Path
+
+# Add src directory to Python path
+src_path = Path(__file__).parent.parent / "src"
+sys.path.insert(0, str(src_path))
+
+from pyfed.models import APCreate, APNote, APPerson, APLike
+from pyfed.security import KeyManager
+from pyfed.storage import StorageBackend
+from pyfed.federation import ActivityDelivery
+from pyfed.serializers.json_serializer import to_json, from_json
+
+# Configure logging
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+def ensure_directories():
+    """Ensure required directories exist."""
+    dirs = [
+        "example_keys",
+        "example_data",
+        "example_media"
+    ]
+    
+    for dir_name in dirs:
+        path = Path(dir_name)
+        path.mkdir(parents=True, exist_ok=True)
+        logger.info(f"Ensured directory exists: {path}")
+
+async def run_examples():
+    """Run PyFed examples."""
+    try:
+        logger.info("Setting up PyFed components...")
+        ensure_directories()
+
+        # Initialize storage
+        storage = StorageBackend.create(
+            provider="sqlite",
+            database_url="example_data/pyfed_example.db"
+        )
+        await storage.initialize()
+
+        # Initialize key manager
+        key_manager = KeyManager(
+            domain="example.com",
+            active_keys=Path("example_keys").resolve(),
+            keys_path=str(Path("example_keys").resolve())
+        )
+        await key_manager.initialize()
+
+        # Initialize delivery
+        delivery = ActivityDelivery(
+            key_manager=key_manager,
+            timeout=30
+        )
+
+        # Create actor
+        actor = APPerson(
+            id="https://example.com/users/alice",
+            name="Alice",
+            preferred_username="alice",
+            inbox="https://example.com/users/alice/inbox",
+            outbox="https://example.com/users/alice/outbox",
+            followers="https://example.com/users/alice/followers"
+        )
+
+        # Create note
+        note = APNote(
+            id="https://example.com/notes/123",
+            content="Hello, Federation! #test @bob@remote.com",
+            attributed_to=str(actor.id),
+            to=["https://www.w3.org/ns/activitystreams#Public"],
+            published=datetime.utcnow().isoformat()
+        )
+
+        # Create activity
+        create_activity = APCreate(
+            id=f"https://example.com/activities/{datetime.utcnow().timestamp()}",
+            actor=str(actor.id),
+            object=note,
+            to=note.to,
+            published=datetime.utcnow().isoformat()
+        )
+
+        # Store activity
+        logger.info("Storing activity...")
+        activity_dict = create_activity.serialize()
+        logger.info(f"Serialized activity (with context):\n{to_json(create_activity, indent=2)}")
+        activity_id = await storage.create_activity(activity_dict)
+        logger.info(f"Activity stored with ID: {activity_id}")
+
+        # Create like activity
+        like_activity = APLike(
+            id=f"https://example.com/activities/like_{datetime.utcnow().timestamp()}",
+            actor=str(actor.id),
+            object=note.id,
+            to=["https://www.w3.org/ns/activitystreams#Public"],
+            published=datetime.utcnow().isoformat()
+        )
+
+        # Store like
+        logger.info("Storing like activity...")
+        like_dict = like_activity.serialize()
+        logger.info(f"Serialized like activity (with context):\n{to_json(like_activity, indent=2)}")
+        like_id = await storage.create_activity(like_dict)
+        logger.info(f"Like activity stored with ID: {like_id}")
+
+        # Retrieve activities
+        logger.info("\nRetrieving activities...")
+        stored_activity = await storage.get_activity(activity_id)
+        stored_like = await storage.get_activity(like_id)
+
+        logger.info("Retrieved Create activity:")
+        logger.info(stored_activity)
+        logger.info("\nRetrieved Like activity:")
+        logger.info(stored_like)
+
+    except Exception as e:
+        logger.error(f"Error running examples: {e}")
+        raise
+    finally:
+        if storage:
+            await storage.close()
+
+def main():
+    """Main entry point."""
+    try:
+        asyncio.run(run_examples())
+    except KeyboardInterrupt:
+        logger.info("Example stopped by user")
+    except Exception as e:
+        logger.error(f"Example failed: {e}")
+        sys.exit(1)
+
+if __name__ == "__main__":
+    main() 
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index e07d7b75db7c25ec7707d23720ca688a55ad5d7f..c8fb7e92494ece87076a27f43ca73e70b0a92c52 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,20 +5,25 @@ aioredis==2.0.1
 aiosignal==1.3.1
 aiosqlite==0.20.0
 annotated-types==0.7.0
+anyio==4.6.2.post1
 async-timeout==5.0.0
 asyncpg==0.30.0
 attrs==24.2.0
 backoff==2.2.1
+beautifulsoup4==4.12.3
+bs4==0.0.2
 cachetools==5.5.0
 cffi==1.17.1
 cryptography==43.0.3
 dnspython==2.7.0
 factory_boy==3.3.1
 Faker==30.8.0
+fastapi==0.115.4
 frozenlist==1.4.1
 greenlet==3.1.1
 idna==3.10
 iniconfig==2.0.0
+Markdown==3.7
 motor==3.6.0
 multidict==6.1.0
 packaging==24.1
@@ -34,8 +39,12 @@ pytest==8.3.3
 pytest-aiohttp==1.0.5
 pytest-asyncio==0.24.0
 python-dateutil==2.9.0.post0
+PyYAML==6.0.2
 redis==5.2.0
 six==1.16.0
+sniffio==1.3.1
+soupsieve==2.6
 SQLAlchemy==2.0.36
+starlette==0.41.2
 typing_extensions==4.12.2
-yarl==1.15.2
+yarl==1.15.2
\ No newline at end of file
diff --git a/setup.py b/setup.py
index ca99640739d50ee7f5a4aed9f3282945f107bf88..331dde77841330d9a91d82aca17be3d441bb0b48 100644
--- a/setup.py
+++ b/setup.py
@@ -2,6 +2,29 @@ from setuptools import setup, find_packages
 
 setup(
     name="pyfed",
-    packages=find_packages(where="src"),
+    version="0.1.0",
     package_dir={"": "src"},
+    packages=find_packages(where="src"),
+    python_requires=">=3.8",
+    install_requires=[
+        "cryptography>=3.4.7",
+        "aiohttp>=3.8.0",
+        "pydantic>=1.8.2",
+        "sqlalchemy>=1.4.0",
+        "aiosqlite>=0.17.0",
+        "asyncpg>=0.25.0",
+        "redis>=4.0.0",
+        "beautifulsoup4>=4.9.3",
+        "markdown>=3.3.4",
+    ],
+    extras_require={
+        "fastapi": ["fastapi>=0.68.0", "uvicorn>=0.15.0"],
+        "django": ["django>=3.2.0"],
+        "flask": ["flask>=2.0.0"],
+        "dev": [
+            "pytest>=6.2.5",
+            "pytest-asyncio>=0.15.1",
+            "pytest-cov>=2.12.1",
+        ],
+    },
 ) 
\ No newline at end of file
diff --git a/src/pyfed/models/actors.py b/src/pyfed/models/actors.py
index 0a5fc0b1cbc777d4546e64271e059a6331350ea7..0056ff87f7e0d365aede0d9b3d3df86e90f759b7 100644
--- a/src/pyfed/models/actors.py
+++ b/src/pyfed/models/actors.py
@@ -11,10 +11,9 @@ from __future__ import annotations
 from pydantic import Field, HttpUrl, field_validator
 from typing import Optional, List, Dict, Any, TypedDict, Literal
 from datetime import datetime
-from pyfed.utils.exceptions import InvalidURLError
-# from pyfed.plugins import plugin_manager
-from pyfed.utils.logging import get_logger
-from pyfed.cache import object_cache
+from ..utils.exceptions import InvalidURLError
+from ..utils.logging import get_logger
+from ..cache import object_cache
 
 from .objects import APObject
 
@@ -64,52 +63,52 @@ class APActor(APObject):
         except ValueError:
             raise InvalidURLError(f"Invalid URL: {v}")
 
-    async def send_to_inbox(self, activity: Dict[str, Any]) -> bool:
-        """
-        Send an activity to this actor's inbox.
+    # async def send_to_inbox(self, activity: Dict[str, Any]) -> bool:
+    #     """
+    #     Send an activity to this actor's inbox.
 
-        Args:
-            activity (Dict[str, Any]): The activity to send.
+    #     Args:
+    #         activity (Dict[str, Any]): The activity to send.
 
-        Returns:
-            bool: True if the activity was successfully delivered, False otherwise.
+    #     Returns:
+    #         bool: True if the activity was successfully delivered, False otherwise.
 
-        Note: This method should implement the logic described in:
-        https://www.w3.org/TR/activitypub/#delivery
-        """
-        logger.info(f"Sending activity to inbox: {self.inbox}")
-        # Execute pre-send hook
-        # plugin_manager.execute_hook('pre_send_to_inbox', self, activity)
+    #     Note: This method should implement the logic described in:
+    #     https://www.w3.org/TR/activitypub/#delivery
+    #     """
+    #     logger.info(f"Sending activity to inbox: {self.inbox}")
+    #     # Execute pre-send hook
+    #     # plugin_manager.execute_hook('pre_send_to_inbox', self, activity)
         
-        # Placeholder for actual implementation
-        return True
+    #     # Placeholder for actual implementation
+    #     return True
 
-    async def fetch_followers(self) -> List[APActor]:
-        """
-        Fetch the followers of this actor.
+    # async def fetch_followers(self) -> List[APActor]:
+    #     """
+    #     Fetch the followers of this actor.
 
-        Returns:
-            List[APActor]: A list of actors following this actor.
-        """
-        logger.info(f"Fetching followers for actor: {self.id}")
+    #     Returns:
+    #         List[APActor]: A list of actors following this actor.
+    #     """
+    #     logger.info(f"Fetching followers for actor: {self.id}")
         
-        # Check cache first
-        cached_followers = object_cache.get(f"followers:{self.id}")
-        if cached_followers is not None:
-            return cached_followers
+    #     # Check cache first
+    #     cached_followers = object_cache.get(f"followers:{self.id}")
+    #     if cached_followers is not None:
+    #         return cached_followers
 
-        # Execute pre-fetch hook
-        # plugin_manager.execute_hook('pre_fetch_followers', self)
+    #     # Execute pre-fetch hook
+    #     # plugin_manager.execute_hook('pre_fetch_followers', self)
         
-        # Fetch followers (placeholder implementation)
-        followers = []  # Actual implementation would go here
+    #     # Fetch followers (placeholder implementation)
+    #     followers = []  # Actual implementation would go here
         
-        # Cache the result
-        object_cache.set(f"followers:{self.id}", followers)
+    #     # Cache the result
+    #     object_cache.set(f"followers:{self.id}", followers)
         
-        return followers
+    #     return followers
 
-    async def create_activity(self, activity_type: str, object: Dict[str, Any]) -> ActivityDict:
+    # async def create_activity(self, activity_type: str, object: Dict[str, Any]) -> ActivityDict:
         """
         Create an activity with this actor as the 'actor'.
 
diff --git a/src/pyfed/models/objects.py b/src/pyfed/models/objects.py
index 032a573d01d4c48b14957b40e5a63d28c0202a1a..cd49aab7ffe88048ce0155293535b28ffa76eedf 100644
--- a/src/pyfed/models/objects.py
+++ b/src/pyfed/models/objects.py
@@ -52,7 +52,7 @@ class APObject(ActivityPubBase):
     Usage: https://www.w3.org/TR/activitypub/#object
     """
     id: HttpUrl = Field(..., description="Unique identifier for the object")
-    type: str = Field(..., description="The type of the object")
+    type: Literal["Object"] = Field(..., description="The type of the object")
     attachment: Optional[List[Union[str, 'APObject']]] = Field(default=None, description="Files attached to the object")
     attributed_to: Optional[Union[str, 'APObject']] = Field(default=None, description="Entity attributed to this object")
     audience: Optional[List[Union[str, 'APObject']]] = Field(default=None, description="Intended audience")
diff --git a/src/pyfed/security/__init__.py b/src/pyfed/security/__init__.py
index 0519ecba6ea913e21689ec692e81e9e4973fbf73..f6bdfbdf557a34b8d9362cf5161dc20f769d1ac6 100644
--- a/src/pyfed/security/__init__.py
+++ b/src/pyfed/security/__init__.py
@@ -1 +1,5 @@
- 
\ No newline at end of file
+from .key_management import KeyManager
+
+__all__ = [
+    'KeyManager'
+]
diff --git a/src/pyfed/security/http_signatures.py b/src/pyfed/security/http_signatures.py
index 4e579332f763e66c83fd4e703197dd80bf0de8e7..a4db3edf6bdbf84fdb8d239276e5be4a5d497646 100644
--- a/src/pyfed/security/http_signatures.py
+++ b/src/pyfed/security/http_signatures.py
@@ -1,4 +1,5 @@
 """
+security/http_signatures.py
 Enhanced HTTP Signatures implementation.
 
 Implements HTTP Signatures (draft-cavage-http-signatures) with:
diff --git a/src/pyfed/security/key_management.py b/src/pyfed/security/key_management.py
index 92a1aeafe377bdba3dc4a8f9d2b4f9268688250f..1b8f392677c8bfdd00a3f76e4aba539ffe885f92 100644
--- a/src/pyfed/security/key_management.py
+++ b/src/pyfed/security/key_management.py
@@ -46,10 +46,11 @@ class KeyManager:
 
     def __init__(self,
                  domain: str,
-                 keys_dir: str = "keys",
+                 keys_path: str = "keys",
+                 active_keys: Optional[Path] = None,
                  rotation_config: Optional[KeyRotation] = None):
         self.domain = domain
-        self.keys_dir = Path(keys_dir)
+        self.keys_path = Path(keys_path)
         self.rotation_config = rotation_config or KeyRotation()
         self.active_keys: Dict[str, KeyPair] = {}
         self._rotation_task = None
@@ -58,7 +59,7 @@ class KeyManager:
         """Initialize key manager."""
         try:
             # Create keys directory
-            self.keys_dir.mkdir(parents=True, exist_ok=True)
+            self.keys_path.mkdir(parents=True, exist_ok=True)
             
             # Load existing keys
             await self._load_existing_keys()
@@ -74,7 +75,7 @@ class KeyManager:
             logger.error(f"Failed to initialize key manager: {e}")
             raise KeyManagementError(f"Key manager initialization failed: {e}")
 
-    async def generate_key_pair(self) -> KeyPair:
+    async def  generate_key_pair(self) -> KeyPair:
         """Generate new key pair."""
         try:
             # Generate keys
@@ -170,12 +171,12 @@ class KeyManager:
     async def _load_existing_keys(self) -> None:
         """Load existing keys from disk."""
         try:
-            for key_file in self.keys_dir.glob("*.json"):
+            for key_file in self.keys_path.glob("*.json"):
                 async with aiofiles.open(key_file, 'r') as f:
                     metadata = json.loads(await f.read())
                     
                 # Load private key
-                private_key_path = self.keys_dir / f"{metadata['key_id']}_private.pem"
+                private_key_path = self.keys_path / f"{metadata['key_id']}_private.pem"
                 async with aiofiles.open(private_key_path, 'rb') as f:
                     private_key = serialization.load_pem_private_key(
                         await f.read(),
@@ -203,7 +204,7 @@ class KeyManager:
         """Save key pair to disk."""
         try:
             # Save private key
-            private_key_path = self.keys_dir / f"{key_pair.key_id}_private.pem"
+            private_key_path = self.keys_path / f"{key_pair.key_id}_private.pem"
             private_pem = key_pair.private_key.private_bytes(
                 encoding=serialization.Encoding.PEM,
                 format=serialization.PrivateFormat.PKCS8,
@@ -213,7 +214,7 @@ class KeyManager:
                 await f.write(private_pem)
             
             # Save public key
-            public_key_path = self.keys_dir / f"{key_pair.key_id}_public.pem"
+            public_key_path = self.keys_path / f"{key_pair.key_id}_public.pem"
             public_pem = key_pair.public_key.public_bytes(
                 encoding=serialization.Encoding.PEM,
                 format=serialization.PublicFormat.SubjectPublicKeyInfo
@@ -227,7 +228,7 @@ class KeyManager:
                 'created_at': key_pair.created_at.isoformat(),
                 'expires_at': key_pair.expires_at.isoformat()
             }
-            metadata_path = self.keys_dir / f"{key_pair.key_id}.json"
+            metadata_path = self.keys_path / f"{key_pair.key_id}.json"
             async with aiofiles.open(metadata_path, 'w') as f:
                 await f.write(json.dumps(metadata))
                 
@@ -238,12 +239,12 @@ class KeyManager:
     async def _archive_key_pair(self, key_pair: KeyPair) -> None:
         """Archive an expired key pair."""
         try:
-            archive_dir = self.keys_dir / "archive"
+            archive_dir = self.keys_path / "archive"
             archive_dir.mkdir(exist_ok=True)
             
             # Move key files to archive
             for ext in ['_private.pem', '_public.pem', '.json']:
-                src = self.keys_dir / f"{key_pair.key_id}{ext}"
+                src = self.keys_path / f"{key_pair.key_id}{ext}"
                 dst = archive_dir / f"{key_pair.key_id}{ext}"
                 if src.exists():
                     src.rename(dst)
diff --git a/src/pyfed/security/validators.py b/src/pyfed/security/validators.py
index e416a528ea3f06e86419e3a373737cf52e47d35e..0519ecba6ea913e21689ec692e81e9e4973fbf73 100644
--- a/src/pyfed/security/validators.py
+++ b/src/pyfed/security/validators.py
@@ -1,119 +1 @@
-import asyncio
-from typing import Dict, Any, Optional, Set, Union
-from datetime import datetime
-from dataclasses import dataclass
-from ..utils.logging import get_logger
-from ..utils.exceptions import ValidationError
-from .interfaces import SignatureVerifier
-
-logger = get_logger(__name__)
-
-@dataclass
-class ValidationConfig:
-    """Validation configuration."""
-    max_date_diff: int = 30
-    require_https: bool = True
-    allowed_schemes: Set[str] = {'https'}
-    max_audience_size: int = 100
-    required_headers: Set[str] = {'Host', 'Date', 'Signature'}
-
-class SecurityValidator:
-    """Enhanced security validation."""
-
-    def __init__(self, 
-                signature_verifier: SignatureVerifier, 
-                config: Optional[ValidationConfig] = None):
-        self.signature_verifier = signature_verifier
-        self.config = config or ValidationConfig()
-        self.seen_requests: Dict[str, datetime] = {}
-        self._cleanup_task = asyncio.create_task(self._cleanup_seen_requests())
-
-    async def validate_request(self, 
-                            headers: Dict[str, str], 
-                            body: Dict[str, Any],
-                            method: str,
-                            path: str,
-                            validate_signature: bool = True) -> None:
-        """Enhanced request validation."""
-        try:
-            # Basic header validation
-            self._validate_required_headers(headers)
-            self._validate_date_header(headers.get('Date'))
-            
-            # Replay protection
-            request_id = self._get_request_id(headers, method, path)
-            if request_id in self.seen_requests:
-                raise ValidationError("Duplicate request detected")
-            self.seen_requests[request_id] = datetime.utcnow()
-            
-            # Signature validation
-            if validate_signature:
-                if not await self.signature_verifier.verify_request(headers):
-                    raise ValidationError("Invalid HTTP signature")
-            
-            # Content validation
-            if body:
-                content_type = headers.get('Content-Type', '')
-                if not content_type.startswith('application/activity+json'):
-                    raise ValidationError("Invalid content type")
-                await self.validate_activity_security(body)
-
-        except Exception as e:
-            logger.error(f"Request validation failed: {e}")
-            raise ValidationError(f"Validation failed: {e}")
-
-    async def validate_activity_security(self, activity: Dict[str, Any]) -> None:
-        """Enhanced activity validation."""
-        # Validate basic structure
-        if not isinstance(activity, dict):
-            raise ValidationError("Activity must be a JSON object")
-            
-        # Validate required fields
-        required_fields = {'type', 'actor'}
-        missing = required_fields - set(activity.keys())
-        if missing:
-            raise ValidationError(f"Missing required fields: {missing}")
-
-        # Validate actor
-        await self._validate_actor(activity['actor'])
-
-        # Validate object if present
-        if 'object' in activity:
-            await self._validate_object(activity['object'])
-
-        # Validate audience
-        await self._validate_audience_fields(activity)
-
-    async def _validate_actor(self, actor: Union[str, Dict[str, Any]]) -> None:
-        """Enhanced actor validation."""
-        if isinstance(actor, str):
-            await self.validate_url(actor)
-        elif isinstance(actor, dict):
-            if 'id' not in actor:
-                raise ValidationError("Actor object must have an id")
-            await self.validate_url(actor['id'])
-            
-            # Validate actor ownership if possible
-            if hasattr(self.signature_verifier, 'verify_actor_ownership'):
-                if not await self.signature_verifier.verify_actor_ownership(actor):
-                    raise ValidationError("Invalid actor ownership")
-        else:
-            raise ValidationError("Invalid actor format")
-
-    async def _validate_audience_fields(self, obj: Dict[str, Any]) -> None:
-        """Enhanced audience validation."""
-        audience_fields = {'to', 'cc', 'bto', 'bcc'}
-        total_recipients = 0
-        
-        for field in audience_fields:
-            if field in obj:
-                value = obj[field]
-                if isinstance(value, list):
-                    total_recipients += len(value)
-                    if total_recipients > self.config.max_audience_size:
-                        raise ValidationError("Too many recipients")
-                    for item in value:
-                        await self.validate_url(item)
-                elif isinstance(value, str):
-                    total_recipients += 1
-                    await self.validate_url(value)
\ No newline at end of file
+ 
\ No newline at end of file
diff --git a/src/pyfed/serializers/json_serializer.py b/src/pyfed/serializers/json_serializer.py
index 3cbb28b73720c8153a5c4be7107a4dc89f9c2017..54e34d70453cbc1e788196b13f4f41d59d2664ee 100644
--- a/src/pyfed/serializers/json_serializer.py
+++ b/src/pyfed/serializers/json_serializer.py
@@ -1,113 +1,238 @@
 """
-json_serializer.py
-This module provides JSON serialization for ActivityPub objects.
+JSON serializer for ActivityPub objects.
 """
 
+from typing import Any, Dict, Union, List, Optional, Type, get_origin, get_args
+from datetime import datetime, timezone
 import json
-from datetime import datetime
-from typing import Any, Dict
-from pydantic import BaseModel
+import re
+from pydantic import BaseModel, AnyUrl, HttpUrl
 from pydantic_core import Url
-from ..utils.logging import get_logger
-# from ..plugins import plugin_manager
-
-logger = get_logger(__name__)
 
 def to_camel_case(snake_str: str) -> str:
-    """Converts snake_case to camelCase."""
+    """Convert snake_case to camelCase."""
     components = snake_str.split('_')
     return components[0] + ''.join(x.title() for x in components[1:])
 
-def convert_dict_keys_to_camel_case(data: Dict[str, Any]) -> Dict[str, Any]:
-    """Recursively converts all dictionary keys from snake_case to camelCase."""
-    if not isinstance(data, dict):
-        return data
-    
-    return {
-        to_camel_case(key): (
-            convert_dict_keys_to_camel_case(value) if isinstance(value, (dict, list))
-            else value
-        )
-        for key, value in data.items()
-    }
-
-class ActivityPubJSONEncoder(json.JSONEncoder):
-    """Custom JSON encoder for ActivityPub objects."""
-    
-    def default(self, obj: Any) -> Any:
-        if isinstance(obj, BaseModel):
-            return convert_dict_keys_to_camel_case(obj.model_dump())
-        if isinstance(obj, Url):
-            return str(obj)
-        if isinstance(obj, datetime):
-            return obj.isoformat()
-        if isinstance(obj, list):
-            return [self.default(item) for item in obj]
-        return super().default(obj)
+def is_url_field(field_name: str) -> bool:
+    """Check if field name suggests it's a URL."""
+    url_indicators = [
+        'url', 'href', 'id', 'inbox', 'outbox', 'following',
+        'followers', 'liked', 'icon', 'image', 'avatar',
+        'endpoints', 'featured', 'streams'
+    ]
+    return any(indicator in field_name.lower() for indicator in url_indicators)
 
-class ActivityPubBase(BaseModel):
-    """Base class for all ActivityPub models."""
+class ActivityPubSerializer:
+    """ActivityPub serializer implementation."""
     
-    class Config:
-        """Pydantic model configuration."""
-        populate_by_name = True
-        use_enum_values = True
-        alias_generator = to_camel_case
+    @staticmethod
+    def _process_value(value: Any, field_name: str = "", depth: int = 0) -> Any:
+        """
+        Process a single value for serialization.
+        
+        Args:
+            value: Value to process
+            field_name: Name of the field being processed
+            depth: Current recursion depth
+            
+        Returns:
+            Processed value
+        """
+        # Prevent infinite recursion
+        if depth > 10:  # Maximum nesting depth
+            return str(value)
 
-class ActivityPubSerializer:
-    """Serializer for ActivityPub objects."""
+        if value is None:
+            return None
+
+        # Handle BaseModel instances (nested objects)
+        if isinstance(value, BaseModel):
+            # Recursively serialize nested objects
+            serialized = value.model_dump(exclude_none=True)
+            return {
+                to_camel_case(k): ActivityPubSerializer._process_value(v, k, depth + 1)
+                for k, v in serialized.items()
+            }
+
+        # Handle URL types - using pydantic_core.Url instead of AnyUrl
+        if isinstance(value, Url):
+            return str(value)
+
+        # Handle datetime
+        if isinstance(value, datetime):
+            return value.astimezone(timezone.utc).isoformat()
+
+        # Handle lists with potential nested objects
+        if isinstance(value, list):
+            return [
+                ActivityPubSerializer._process_value(item, field_name, depth + 1)
+                for item in value
+            ]
+
+        # Handle dictionaries with potential nested objects
+        if isinstance(value, dict):
+            return {
+                to_camel_case(k): ActivityPubSerializer._process_value(v, k, depth + 1)
+                for k, v in value.items()
+            }
+
+        # Convert string to URL if field name suggests it's a URL
+        if isinstance(value, str) and is_url_field(field_name):
+            if not value.startswith(('http://', 'https://')):
+                value = f"https://{value}"
+            return value
+
+        return value
 
     @staticmethod
-    def serialize(obj: ActivityPubBase, include_context: bool = True, **kwargs) -> str:
+    def serialize(obj: Any, include_context: bool = True) -> Dict[str, Any]:
         """
-        Serialize an ActivityPub object to JSON string.
-
+        Serialize object to dictionary.
+        
         Args:
-            obj (ActivityPubBase): The object to serialize.
-            include_context (bool): Whether to include @context field.
-            **kwargs: Additional arguments passed to json.dumps.
-
+            obj: Object to serialize
+            include_context: Whether to include @context
+            
         Returns:
-            str: JSON string representation of the object.
+            Serialized dictionary
         """
-        logger.debug("Serializing object")
-        
-        # Execute pre-serialize hook
-        # plugin_manager.execute_hook('pre_serialize', obj)
-        
-        # Convert to dictionary and convert keys to camelCase
-        data = convert_dict_keys_to_camel_case(obj.model_dump())
+        if not isinstance(obj, BaseModel):
+            return ActivityPubSerializer._process_value(obj)
+
+        # Process each field
+        processed_data = ActivityPubSerializer._process_value(obj)
         
         # Add context if needed
         if include_context:
-            data["@context"] = "https://www.w3.org/ns/activitystreams"
-        
-        # Serialize to JSON
-        serialized = json.dumps(data, cls=ActivityPubJSONEncoder, **kwargs)
-        
-        return serialized
+            processed_data["@context"] = "https://www.w3.org/ns/activitystreams"
+            
+        return processed_data
 
     @staticmethod
-    def deserialize(json_str: str, model_class: type[ActivityPubBase]) -> ActivityPubBase:
+    def _process_field_value(value: Any, field_type: Any) -> Any:
         """
-        Deserialize a JSON string to an ActivityPub object.
-
+        Process field value during deserialization.
+        
         Args:
-            json_str (str): The JSON string to deserialize.
-            model_class (type[ActivityPubBase]): The class to deserialize into.
-
+            value: Value to process
+            field_type: Type annotation for the field
+            
         Returns:
-            ActivityPubBase: The deserialized object.
+            Processed value
         """
-        logger.debug(f"Deserializing to {model_class.__name__}")
+        # Handle None values
+        if value is None:
+            return None
+
+        # Handle nested BaseModel
+        if hasattr(field_type, 'model_fields'):
+            return ActivityPubSerializer.deserialize(value, field_type)
+
+        # Handle lists
+        origin = get_origin(field_type)
+        if origin is list:
+            args = get_args(field_type)
+            if args and hasattr(args[0], 'model_fields'):
+                return [
+                    ActivityPubSerializer.deserialize(item, args[0])
+                    if isinstance(item, dict)
+                    else item
+                    for item in value
+                ]
+
+        # Handle dictionaries
+        if origin is dict:
+            key_type, val_type = get_args(field_type)
+            if hasattr(val_type, 'model_fields'):
+                return {
+                    k: ActivityPubSerializer.deserialize(v, val_type)
+                    if isinstance(v, dict)
+                    else v
+                    for k, v in value.items()
+                }
+
+        return value
+
+    @staticmethod
+    def deserialize(data: Union[str, Dict[str, Any]], model_class: Type[BaseModel]) -> BaseModel:
+        """
+        Deserialize data to object.
         
-        # Parse JSON
-        data = json.loads(json_str)
+        Args:
+            data: JSON string or dictionary to deserialize
+            model_class: Class to deserialize into
+            
+        Returns:
+            Deserialized object
+        """
+        # Handle JSON string input
+        if isinstance(data, str):
+            try:
+                data_dict = json.loads(data)
+            except json.JSONDecodeError:
+                raise ValueError("Invalid JSON string")
+        else:
+            data_dict = data
+
+        if not isinstance(data_dict, dict):
+            raise ValueError("Data must be a dictionary or JSON string")
+
+        # Make a copy of the data
+        data_dict = dict(data_dict)
         
         # Remove context if present
-        data.pop("@context", None)
+        data_dict.pop('@context', None)
         
-        # Create object
-        obj = model_class.model_validate(data)
-        
-        return obj
+        # Convert keys from camelCase to snake_case and process values
+        processed_data = {}
+        for key, value in data_dict.items():
+            if key == '@context':
+                continue
+                
+            snake_key = re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', key).lower()
+            
+            # Get field info from model
+            field_info = model_class.model_fields.get(snake_key)
+            if field_info is None:
+                continue
+
+            # Process the field value
+            processed_value = ActivityPubSerializer._process_field_value(
+                value, field_info.annotation
+            )
+            
+            processed_data[snake_key] = processed_value
+
+        # Use model_validate instead of direct construction
+        return model_class.model_validate(processed_data)
+
+class ActivityPubBase(BaseModel):
+    """Base class for all ActivityPub objects."""
+    
+    def serialize(self, include_context: bool = True) -> Dict[str, Any]:
+        """Serialize object to dictionary."""
+        return ActivityPubSerializer.serialize(self, include_context)
+
+    @classmethod
+    def deserialize(cls, data: Union[str, Dict[str, Any]]) -> 'ActivityPubBase':
+        """Deserialize dictionary to object."""
+        return ActivityPubSerializer.deserialize(data, cls)
+
+    class Config:
+        """Pydantic config."""
+        alias_generator = to_camel_case
+        populate_by_alias = True
+        extra = "allow"
+        arbitrary_types_allowed = True
+        populate_by_name = True
+
+def to_json(obj: ActivityPubBase, **kwargs) -> str:
+    """Convert object to JSON string."""
+    return json.dumps(ActivityPubSerializer.serialize(obj), **kwargs)
+
+def from_json(json_str: str, model_class: Type[ActivityPubBase]) -> ActivityPubBase:
+    """Convert JSON string to object."""
+    return ActivityPubSerializer.deserialize(json_str, model_class)
+
+
diff --git a/src/pyfed/utils/exceptions.py b/src/pyfed/utils/exceptions.py
index 09885a4d270c26f6d5f2061f1cb302dd91c3d41b..50c854e79e1293277dacb24f92c53d657740dc06 100644
--- a/src/pyfed/utils/exceptions.py
+++ b/src/pyfed/utils/exceptions.py
@@ -118,4 +118,21 @@ class WebFingerError(ActivityPubException):
 
 class KeyManagementError(ActivityPubException):
     """Raised when key manager-related errors occur."""
-    pass
\ No newline at end of file
+    pass
+
+class CollectionError(ActivityPubException):
+    """Raised when collection-related errors occur."""
+    pass
+
+class ContentError(ActivityPubException):
+    """Raised when content-related errors occur."""
+    pass
+
+class ContentHandlerError(ActivityPubException):
+    """Raised when content handler-related errors occur."""
+    pass
+
+class CollectionHandlerError(ActivityPubException):
+    """Raised when collection handler-related errors occur."""
+    pass
+
diff --git a/tests/models/__init__.py b/tests/unit_tests/models/__init__.py
similarity index 100%
rename from tests/models/__init__.py
rename to tests/unit_tests/models/__init__.py
diff --git a/tests/models/test_activities.py b/tests/unit_tests/models/test_activities.py
similarity index 94%
rename from tests/models/test_activities.py
rename to tests/unit_tests/models/test_activities.py
index 544ec39e5db8e1e3a61b1e6dd0507f6f36232031..5f516325245806df0c0e4248ae07efb22dd8289a 100644
--- a/tests/models/test_activities.py
+++ b/tests/unit_tests/models/test_activities.py
@@ -14,7 +14,6 @@ def test_create_activity():
     """Test creating a Create activity."""
     activity = APCreate(
         id="https://example.com/activity/123",
-        type="Create",
         actor="https://example.com/user/1",
         object={
             "id": "https://example.com/note/123",
@@ -30,7 +29,6 @@ def test_update_activity():
     """Test creating an Update activity."""
     activity = APUpdate(
         id="https://example.com/activity/123",
-        type="Update",
         actor="https://example.com/user/1",
         object={
             "id": "https://example.com/note/123",
@@ -45,7 +43,6 @@ def test_delete_activity():
     """Test creating a Delete activity."""
     activity = APDelete(
         id="https://example.com/activity/123",
-        type="Delete",
         actor="https://example.com/user/1",
         object="https://example.com/note/123"
     )
@@ -56,7 +53,6 @@ def test_follow_activity():
     """Test creating a Follow activity."""
     activity = APFollow(
         id="https://example.com/activity/123",
-        type="Follow",
         actor="https://example.com/user/1",
         object="https://example.com/user/2"
     )
@@ -67,7 +63,6 @@ def test_undo_activity():
     """Test creating an Undo activity."""
     activity = APUndo(
         id="https://example.com/activity/123",
-        type="Undo",
         actor="https://example.com/user/1",
         object={
             "id": "https://example.com/activity/456",
@@ -83,7 +78,6 @@ def test_like_activity():
     """Test creating a Like activity."""
     activity = APLike(
         id="https://example.com/activity/123",
-        type="Like",
         actor="https://example.com/user/1",
         object="https://example.com/note/123"
     )
@@ -94,7 +88,6 @@ def test_announce_activity():
     """Test creating an Announce activity."""
     activity = APAnnounce(
         id="https://example.com/activity/123",
-        type="Announce",
         actor="https://example.com/user/1",
         object="https://example.com/note/123"
     )
@@ -105,7 +98,6 @@ def test_activity_with_target():
     """Test creating an activity with a target."""
     activity = APCreate(
         id="https://example.com/activity/123",
-        type="Create",
         actor="https://example.com/user/1",
         object="https://example.com/note/123",
         target="https://example.com/collection/1"
@@ -116,7 +108,6 @@ def test_activity_with_result():
     """Test creating an activity with a result."""
     activity = APCreate(
         id="https://example.com/activity/123",
-        type="Create",
         actor="https://example.com/user/1",
         object="https://example.com/note/123",
         result={
@@ -132,7 +123,6 @@ def test_invalid_activity_missing_actor():
     with pytest.raises(ValidationError):
         APCreate(
             id="https://example.com/activity/123",
-            type="Create",
             object="https://example.com/note/123"
         )
 
@@ -141,6 +131,5 @@ def test_invalid_activity_missing_object():
     with pytest.raises(ValidationError):
         APCreate(
             id="https://example.com/activity/123",
-            type="Create",
             actor="https://example.com/user/1"
         )
diff --git a/tests/models/test_actors.py b/tests/unit_tests/models/test_actors.py
similarity index 94%
rename from tests/models/test_actors.py
rename to tests/unit_tests/models/test_actors.py
index 29659a4fe627d1f3d6e9a44ed8fec3dfa69d7624..1a1b72b8a4786e9d55858994f066b7fc319a61f0 100644
--- a/tests/models/test_actors.py
+++ b/tests/unit_tests/models/test_actors.py
@@ -12,7 +12,6 @@ from pyfed.models import (
 def test_valid_person():
     person = APPerson(
         id="https://example.com/users/alice",
-        type="Person",
         name="Alice",
         inbox="https://example.com/users/alice/inbox",
         outbox="https://example.com/users/alice/outbox",
@@ -21,12 +20,12 @@ def test_valid_person():
     assert person.type == "Person"
     assert str(person.inbox) == "https://example.com/users/alice/inbox"
     assert str(person.outbox) == "https://example.com/users/alice/outbox"
+    print(person.preferred_username)
     assert person.preferred_username == "alice"
 
 def test_person_with_optional_fields():
     person = APPerson(
         id="https://example.com/users/alice",
-        type="Person",
         name="Alice",
         inbox="https://example.com/users/alice/inbox",
         outbox="https://example.com/users/alice/outbox",
@@ -44,7 +43,6 @@ def test_invalid_person_missing_required():
     with pytest.raises(ValidationError):
         APPerson(
             id="https://example.com/users/alice",
-            type="Person",
             name="Alice"
             # Missing required inbox and outbox
         )
@@ -53,7 +51,6 @@ def test_invalid_person_invalid_url():
     with pytest.raises(ValidationError):
         APPerson(
             id="https://example.com/users/alice",
-            type="Person",
             name="Alice",
             inbox="not-a-url",  # Invalid URL
             outbox="https://example.com/users/alice/outbox"
@@ -62,7 +59,6 @@ def test_invalid_person_invalid_url():
 def test_valid_group():
     group = APGroup(
         id="https://example.com/groups/admins",
-        type="Group",
         name="Administrators",
         inbox="https://example.com/groups/admins/inbox",
         outbox="https://example.com/groups/admins/outbox"
@@ -73,7 +69,6 @@ def test_valid_group():
 def test_valid_organization():
     org = APOrganization(
         id="https://example.com/org/acme",
-        type="Organization",
         name="ACME Corporation",
         inbox="https://example.com/org/acme/inbox",
         outbox="https://example.com/org/acme/outbox"
@@ -84,7 +79,6 @@ def test_valid_organization():
 def test_valid_application():
     app = APApplication(
         id="https://example.com/apps/bot",
-        type="Application",
         name="Bot Application",
         inbox="https://example.com/apps/bot/inbox",
         outbox="https://example.com/apps/bot/outbox"
@@ -95,7 +89,6 @@ def test_valid_application():
 def test_valid_service():
     service = APService(
         id="https://example.com/services/api",
-        type="Service",
         name="API Service",
         inbox="https://example.com/services/api/inbox",
         outbox="https://example.com/services/api/outbox"
@@ -106,7 +99,6 @@ def test_valid_service():
 def test_actor_with_public_key():
     person = APPerson(
         id="https://example.com/users/alice",
-        type="Person",
         name="Alice",
         inbox="https://example.com/users/alice/inbox",
         outbox="https://example.com/users/alice/outbox",
@@ -122,7 +114,6 @@ def test_actor_with_public_key():
 def test_actor_with_endpoints():
     person = APPerson(
         id="https://example.com/users/alice",
-        type="Person",
         name="Alice",
         inbox="https://example.com/users/alice/inbox",
         outbox="https://example.com/users/alice/outbox",
diff --git a/tests/models/test_collections.py b/tests/unit_tests/models/test_collections.py
similarity index 93%
rename from tests/models/test_collections.py
rename to tests/unit_tests/models/test_collections.py
index 1d715263452da32f6a3e294069565da76a013224..b54833f6d6acf0519b15de4cca81293f0779fb6a 100644
--- a/tests/models/test_collections.py
+++ b/tests/unit_tests/models/test_collections.py
@@ -13,7 +13,6 @@ def test_valid_collection():
     """Test creating a valid Collection."""
     collection = APCollection(
         id="https://example.com/collection/123",
-        type="Collection",
         total_items=10,
         items=["https://example.com/item/1", "https://example.com/item/2"]
     )
@@ -25,7 +24,6 @@ def test_collection_with_optional_fields():
     """Test creating a Collection with all optional fields."""
     collection = APCollection(
         id="https://example.com/collection/123",
-        type="Collection",
         total_items=2,
         current="https://example.com/collection/123/current",
         first="https://example.com/collection/123/first",
@@ -49,7 +47,6 @@ def test_valid_ordered_collection():
     """Test creating a valid OrderedCollection."""
     collection = APOrderedCollection(
         id="https://example.com/collection/123",
-        type="OrderedCollection",
         total_items=2,
         ordered_items=["https://example.com/item/1", "https://example.com/item/2"]
     )
@@ -60,7 +57,6 @@ def test_valid_collection_page():
     """Test creating a valid CollectionPage."""
     page = APCollectionPage(
         id="https://example.com/collection/123/page/1",
-        type="CollectionPage",
         part_of="https://example.com/collection/123",
         items=["https://example.com/item/1"]
     )
@@ -71,7 +67,6 @@ def test_collection_page_with_navigation():
     """Test creating a CollectionPage with navigation links."""
     page = APCollectionPage(
         id="https://example.com/collection/123/page/2",
-        type="CollectionPage",
         part_of="https://example.com/collection/123",
         items=["https://example.com/item/2"],
         next="https://example.com/collection/123/page/3",
@@ -84,7 +79,6 @@ def test_valid_ordered_collection_page():
     """Test creating a valid OrderedCollectionPage."""
     page = APOrderedCollectionPage(
         id="https://example.com/collection/123/page/1",
-        type="OrderedCollectionPage",
         part_of="https://example.com/collection/123",
         ordered_items=["https://example.com/item/1"],
         start_index=0
@@ -97,7 +91,6 @@ def test_invalid_ordered_page_negative_index():
     with pytest.raises(ValidationError):
         APOrderedCollectionPage(
             id="https://example.com/collection/123/page/1",
-            type="OrderedCollectionPage",
             start_index=-1
         )
 
@@ -105,7 +98,6 @@ def test_collection_with_object_items():
     """Test creating a Collection with APObject items."""
     collection = APCollection(
         id="https://example.com/collection/123",
-        type="Collection",
         items=[{
             "id": "https://example.com/item/1",
             "type": "Note",
@@ -119,7 +111,6 @@ def test_ordered_collection_empty():
     """Test creating an empty OrderedCollection."""
     collection = APOrderedCollection(
         id="https://example.com/collection/123",
-        type="OrderedCollection",
         total_items=0
     )
     assert collection.total_items == 0
diff --git a/tests/models/test_factories.py b/tests/unit_tests/models/test_factories.py
similarity index 100%
rename from tests/models/test_factories.py
rename to tests/unit_tests/models/test_factories.py
diff --git a/tests/models/test_imports.py b/tests/unit_tests/models/test_imports.py
similarity index 100%
rename from tests/models/test_imports.py
rename to tests/unit_tests/models/test_imports.py
diff --git a/tests/models/test_interactions.py b/tests/unit_tests/models/test_interactions.py
similarity index 97%
rename from tests/models/test_interactions.py
rename to tests/unit_tests/models/test_interactions.py
index 3464332d56a7c0627fb8212c63300e6054c63344..7389fdd9b7ef5df93bfd7a573a6258a81f4a3e00 100644
--- a/tests/models/test_interactions.py
+++ b/tests/unit_tests/models/test_interactions.py
@@ -72,13 +72,11 @@ def test_collection_with_pagination():
     items = [
         APNote(
             id=f"https://example.com/note/{i}",
-            type="Note",
             content=f"Note {i}"
         ) for i in range(1, 6)
     ]
     collection = APCollection(
         id="https://example.com/collection/123",
-        type="Collection",
         total_items=len(items),
         items=items,
         first="https://example.com/collection/123?page=1",
@@ -186,7 +184,6 @@ def test_mention_in_content():
     
     mention = APMention(
         id="https://example.com/mention/123",
-        type="Mention",
         href=mentioned.id,
         name=f"@{mentioned.preferred_username}"
     )
@@ -206,21 +203,18 @@ def test_collection_pagination_interaction():
     items = [
         APNote(
             id=f"https://example.com/note/{i}",
-            type="Note",
             content=f"Note {i}"
         ) for i in range(1, 11)
     ]
     
     collection = APOrderedCollection(
         id="https://example.com/collection/123",
-        type="OrderedCollection",
         total_items=len(items),
         ordered_items=items[:5]  # First page
     )
     
     page = APOrderedCollectionPage(
         id="https://example.com/collection/123/page/1",
-        type="OrderedCollectionPage",
         part_of=collection.id,
         ordered_items=items[5:],  # Second page
         start_index=5
@@ -256,7 +250,6 @@ def test_actor_relationships():
     
     relationship = APRelationship(
         id="https://example.com/relationship/1",
-        type="Relationship",
         subject=member.id,
         object=group.id,
         relationship="member"
@@ -270,7 +263,6 @@ def test_content_with_multiple_attachments():
     """Test creating content with multiple types of attachments."""
     image = APImage(
         id="https://example.com/image/1",
-        type="Image",
         url="https://example.com/image.jpg",
         width=800,
         height=600
@@ -278,7 +270,6 @@ def test_content_with_multiple_attachments():
     
     document = APDocument(
         id="https://example.com/document/1",
-        type="Document",
         name="Specification",
         url="https://example.com/spec.pdf"
     )
@@ -314,7 +305,6 @@ def test_event_series():
     
     collection = APOrderedCollection(
         id="https://example.com/collection/workshop-series",
-        type="OrderedCollection",
         name="Workshop Series",
         ordered_items=events
     )
diff --git a/tests/models/test_links.py b/tests/unit_tests/models/test_links.py
similarity index 94%
rename from tests/models/test_links.py
rename to tests/unit_tests/models/test_links.py
index 40a3f3f7561fa972496853d85712196cb8f63fba..5e47e287e65c8f839ec3c5f5f195dc97dc461c35 100644
--- a/tests/models/test_links.py
+++ b/tests/unit_tests/models/test_links.py
@@ -10,7 +10,7 @@ def test_valid_link():
     """Test creating a valid Link object."""
     link = APLink(
         id="https://example.com/link/123",
-        type="Link",
+        
         href="https://example.com/resource"
     )
     assert link.type == "Link"
@@ -20,7 +20,7 @@ def test_link_with_optional_fields():
     """Test creating a Link with all optional fields."""
     link = APLink(
         id="https://example.com/link/123",
-        type="Link",
+        
         href="https://example.com/resource",
         name="Test Link",
         hreflang="en",
@@ -40,7 +40,6 @@ def test_link_with_rel():
     """Test creating a Link with relationship fields."""
     link = APLink(
         id="https://example.com/link/123",
-        type="Link",
         href="https://example.com/resource",
         rel=["canonical", "alternate"]
     )
@@ -51,7 +50,6 @@ def test_valid_mention():
     """Test creating a valid Mention object."""
     mention = APMention(
         id="https://example.com/mention/123",
-        type="Mention",
         href="https://example.com/user/alice",
         name="@alice"
     )
@@ -64,7 +62,6 @@ def test_invalid_link_missing_href():
     with pytest.raises(ValidationError):
         APLink(
             id="https://example.com/link/123",
-            type="Link"
         )
 
 def test_invalid_link_invalid_url():
@@ -72,7 +69,6 @@ def test_invalid_link_invalid_url():
     with pytest.raises(ValidationError):
         APLink(
             id="https://example.com/link/123",
-            type="Link",
             href="not-a-url"
         )
 
@@ -81,7 +77,6 @@ def test_invalid_link_invalid_media_type():
     with pytest.raises(ValidationError):
         APLink(
             id="https://example.com/link/123",
-            type="Link",
             href="https://example.com/resource",
             media_type="invalid/type"
         )
diff --git a/tests/models/test_objects.py b/tests/unit_tests/models/test_objects.py
similarity index 100%
rename from tests/models/test_objects.py
rename to tests/unit_tests/models/test_objects.py
diff --git a/tests/serializers/__init__.py b/tests/unit_tests/serializers/__init__.py
similarity index 100%
rename from tests/serializers/__init__.py
rename to tests/unit_tests/serializers/__init__.py
diff --git a/tests/serializers/test_serialization.py b/tests/unit_tests/serializers/test_serialization.py
similarity index 55%
rename from tests/serializers/test_serialization.py
rename to tests/unit_tests/serializers/test_serialization.py
index 5f9468fe2a7c1d2e590da55a80645d1c82812951..9d30b32eedf11935f2298dc2f2575637cb9f82fb 100644
--- a/tests/serializers/test_serialization.py
+++ b/tests/unit_tests/serializers/test_serialization.py
@@ -1,13 +1,14 @@
 """
-test_serialization.py
-This module contains tests for JSON serialization of ActivityPub objects.
+Tests for ActivityPub serialization.
 """
+
 import pytest
-from datetime import datetime
-from pydantic import ValidationError
+from datetime import datetime, timezone
+import json
+
 from pyfed.models import (
-    APObject, APPerson, APNote, APImage, APCollection,
-    APCreate, APLike, APFollow, APPlace, APEvent
+    APObject, APNote, APPerson, APCollection,
+    APCreate, APPlace, APEvent
 )
 from pyfed.serializers.json_serializer import ActivityPubSerializer
 
@@ -19,32 +20,29 @@ def test_serialize_ap_object():
         name="Test Object",
         content="This is a test object."
     )
-    serialized = ActivityPubSerializer.serialize(obj)
-    assert '"@context"' in serialized
-    assert '"type": "Object"' in serialized
-    assert '"name": "Test Object"' in serialized
-
-def test_serialize_without_context():
-    """Test serialization without @context field."""
-    obj = APObject(
-        id="https://example.com/object/123",
-        type="Object",
-        name="Test Object"
-    )
-    serialized = ActivityPubSerializer.serialize(obj, include_context=False)
-    assert '"@context"' not in serialized
+    serialized = obj.serialize()
+    
+    # Verify serialization
+    assert serialized["@context"] == "https://www.w3.org/ns/activitystreams"
+    assert serialized["id"] == "https://example.com/object/123"
+    assert serialized["type"] == "Object"
+    assert serialized["name"] == "Test Object"
+    assert serialized["content"] == "This is a test object."
 
 def test_serialize_with_datetime():
     """Test serialization of objects with datetime fields."""
-    now = datetime.utcnow()
+    now = datetime.now(timezone.utc)
     obj = APObject(
         id="https://example.com/object/123",
         type="Object",
         published=now,
         updated=now
     )
-    serialized = ActivityPubSerializer.serialize(obj)
-    assert now.isoformat() in serialized
+    serialized = obj.serialize()
+    
+    # Verify datetime serialization
+    assert serialized["published"] == now.isoformat()
+    assert serialized["updated"] == now.isoformat()
 
 def test_serialize_nested_objects():
     """Test serialization of objects with nested objects."""
@@ -61,9 +59,12 @@ def test_serialize_nested_objects():
         content="Hello, World!",
         attributed_to=author
     )
-    serialized = ActivityPubSerializer.serialize(note)
-    assert '"attributedTo"' in serialized
-    assert '"type": "Person"' in serialized
+    serialized = note.serialize()
+    
+    # Verify nested object serialization
+    assert serialized["attributedTo"]["id"] == "https://example.com/users/alice"
+    assert serialized["attributedTo"]["type"] == "Person"
+    assert serialized["attributedTo"]["name"] == "Alice"
 
 def test_serialize_collection():
     """Test serialization of collections."""
@@ -80,10 +81,13 @@ def test_serialize_collection():
         total_items=len(items),
         items=items
     )
-    serialized = ActivityPubSerializer.serialize(collection)
-    assert '"type": "Collection"' in serialized
-    assert '"totalItems": 3' in serialized
-    assert '"items"' in serialized
+    serialized = collection.serialize()
+    
+    # Verify collection serialization
+    assert serialized["type"] == "Collection"
+    assert serialized["totalItems"] == 3
+    assert len(serialized["items"]) == 3
+    assert all(item["type"] == "Note" for item in serialized["items"])
 
 def test_serialize_activity():
     """Test serialization of activities."""
@@ -98,46 +102,44 @@ def test_serialize_activity():
         actor="https://example.com/users/alice",
         object=note
     )
-    serialized = ActivityPubSerializer.serialize(create)
-    assert '"type": "Create"' in serialized
-    assert '"object"' in serialized
-    assert '"content": "Hello, World!"' in serialized
-
-def test_serialize_with_urls():
-    """Test serialization of objects with URL fields."""
-    place = APPlace(
-        id="https://example.com/places/123",
-        type="Place",
-        name="Test Place",
-        latitude=51.5074,
-        longitude=-0.1278
-    )
-    event = APEvent(
-        id="https://example.com/events/123",
-        type="Event",
-        name="Test Event",
-        location=place,
-        url="https://example.com/events/123/details"
-    )
-    serialized = ActivityPubSerializer.serialize(event)
-    assert '"url":' in serialized
-    assert '"location"' in serialized
+    serialized = create.serialize()
+    
+    # Verify activity serialization
+    assert serialized["type"] == "Create"
+    assert serialized["actor"] == "https://example.com/users/alice"
+    assert serialized["object"]["type"] == "Note"
+    assert serialized["object"]["content"] == "Hello, World!"
 
 def test_deserialize_ap_object():
     """Test basic object deserialization."""
-    json_str = '''
-    {
+    data = {
         "@context": "https://www.w3.org/ns/activitystreams",
         "type": "Object",
         "id": "https://example.com/object/123",
         "name": "Test Object",
         "content": "This is a test object."
     }
-    '''
-    obj = ActivityPubSerializer.deserialize(json_str, APObject)
+    obj = ActivityPubSerializer.deserialize(data, APObject)
+    
+    # Verify deserialization
+    assert str(obj.id) == "https://example.com/object/123"
     assert obj.type == "Object"
     assert obj.name == "Test Object"
+    assert obj.content == "This is a test object."
+
+def test_deserialize_from_json_string():
+    """Test deserialization from JSON string."""
+    json_str = json.dumps({
+        "type": "Object",
+        "id": "https://example.com/object/123",
+        "name": "Test Object"
+    })
+    obj = ActivityPubSerializer.deserialize(json_str, APObject)
+    
+    # Verify deserialization from string
     assert str(obj.id) == "https://example.com/object/123"
+    assert obj.type == "Object"
+    assert obj.name == "Test Object"
 
 def test_deserialize_invalid_json():
     """Test deserialization of invalid JSON."""
@@ -146,9 +148,9 @@ def test_deserialize_invalid_json():
 
 def test_deserialize_missing_required_fields():
     """Test deserialization with missing required fields."""
-    json_str = '{"type": "Object", "name": "Test"}'  # Missing required 'id'
-    with pytest.raises(ValidationError):
-        ActivityPubSerializer.deserialize(json_str, APObject)
+    data = {"type": "Object", "name": "Test"}  # Missing required 'id'
+    with pytest.raises(Exception):  # Pydantic will raise validation error
+        ActivityPubSerializer.deserialize(data, APObject)
 
 def test_serialize_deserialize_complex_object():
     """Test round-trip serialization and deserialization."""
@@ -159,32 +161,27 @@ def test_serialize_deserialize_complex_object():
         to=["https://example.com/users/bob"],
         cc=["https://www.w3.org/ns/activitystreams#Public"]
     )
-    serialized = ActivityPubSerializer.serialize(original)
+    serialized = original.serialize()
     deserialized = ActivityPubSerializer.deserialize(serialized, APNote)
-    assert deserialized.id == original.id
+    
+    # Verify round-trip
+    assert str(deserialized.id) == str(original.id)
+    assert deserialized.type == original.type
     assert deserialized.content == original.content
     assert deserialized.to == original.to
     assert deserialized.cc == original.cc
 
-def test_serialize_with_custom_json_options():
-    """Test serialization with custom JSON options."""
-    obj = APObject(
-        id="https://example.com/object/123",
-        type="Object",
-        name="Test Object"
-    )
-    serialized = ActivityPubSerializer.serialize(obj, indent=2)
-    assert '\n  "' in serialized  # Check for indentation
-
 def test_deserialize_with_extra_fields():
     """Test deserialization with extra fields in JSON."""
-    json_str = '''
-    {
+    data = {
         "type": "Object",
         "id": "https://example.com/object/123",
         "name": "Test Object",
         "extra_field": "Should be ignored"
     }
-    '''
-    obj = ActivityPubSerializer.deserialize(json_str, APObject)
-    assert not hasattr(obj, "extra_field")
+    obj = ActivityPubSerializer.deserialize(data, APObject)
+    
+    # Verify extra fields are handled
+    assert str(obj.id) == "https://example.com/object/123"
+    assert obj.type == "Object"
+    assert obj.name == "Test Object"