diff --git a/README.md b/README.md
index adb301f230a9136e279c40f2f94c15f71e7b28d5..986872e20fc9333bd0c8dd894d6d1abc9fe5ba61 100644
--- a/README.md
+++ b/README.md
@@ -20,3 +20,99 @@ or even used the project work.
 To build for another platform, set `GOOS` and `GOARCH`. To yield a static binary (fully
 self-contained, no dynamic linking) set `CGO_ENABLED=0`. For more details, please refer
 to the [Makefile](./Makefile).
+
+
+#### Run:
+
+```bash
+HOST=0.0.0.0 PORT=8080 ./artifact.bin
+```
+
+
+#### Interact:
+
+##### Landing page 
+
+plain text:
+```bash
+curl http://localhost:8080
+```
+
+HTML:
+```bash
+curl --header 'Content-Type: text/html; charset=utf-8' http://localhost:8080
+# or just open in a browser
+```
+
+
+##### Health check
+
+```bash
+curl http://localhost:8080/health
+```
+
+
+##### Server side environment variables
+
+List environment variables visible by the webservice process if environment
+is not `production`.
+
+```bash
+curl http://localhost:8080/env
+```
+
+
+##### State life cycle
+
+URL slug is used as identifier and the body is the actual *data* being stored.
+Please note, when writing (add or change) something, `Content-Type` must be set
+in the request header.
+
+Write an entry:
+```bash
+curl \
+  -X PUT \
+  --header 'Content-Type: text/plain; charset=utf-8' \
+  --data 'foo' \
+  http://localhost:8080/state/bar
+```
+
+Obtain an entry:
+```bash
+curl \
+  -X GET \
+  http://localhost:8080/state/bar
+```
+
+Remove an entry:
+```bash
+curl \
+  -X DELETE \
+  --verbose \
+  http://localhost:8080/state/bar
+```
+
+List all existing entries (returns JSON or plain text, depending on the `Accept` header):
+```bash
+curl \
+  -X GET
+  --header 'Accept: text/plain'\
+  http://localhost:8080/states
+```
+
+Upload an entire file:
+```bash
+curl \
+  -X PUT \
+  --header 'Content-Type: application/pdf' \
+  --upload-file ./example.pdf \
+  http://localhost:8080/state/pdf-doc
+```
+
+Download a file:
+```bash
+curl \
+  -X GET \
+  --output ./example-copy.pdf \
+  http://localhost:8080/state/pdf-doc
+```
diff --git a/main.go b/main.go
index 7eb62c5ab4956c6adddb9531fa802ee46581141b..c7866eb241bba73c665589eff4684c64edb481b6 100644
--- a/main.go
+++ b/main.go
@@ -11,6 +11,7 @@ import (
 
     "webservice/configuration"
     "webservice/routing"
+    "webservice/state"
 
     "github.com/gofiber/fiber/v2"
 )
@@ -27,9 +28,11 @@ func main() {
         DisableStartupMessage: config.Environment != "development",
     })
 
+    store := state.NewEphemeralStore()
+
     var isHealthy = false
 
-    routing.SetRoutes( server, config, &isHealthy )
+    routing.SetRoutes( server, config, store, &isHealthy )
 
     go func(){
         err := server.Listen( fmt.Sprintf( "%s:%d", config.Host, config.Port ) )
@@ -65,6 +68,10 @@ func main() {
             if err != nil {
                 log.Printf( "HTTP server failed to shut down: %v", err )
             }
+            err = store.Disconnect()
+            if err != nil {
+                log.Printf( "Store failed to disconnect: %v", err )
+            }
             concludeShutdown()
 
         case <-shuttingDown.Done():
diff --git a/routing/routes.go b/routing/routes.go
index 9f9ac3c6db9dcc1ff9cc5dd0aa320d544d74a3b2..048249a24e88c004098deef41d02695836f7c52d 100644
--- a/routing/routes.go
+++ b/routing/routes.go
@@ -9,14 +9,16 @@ import (
     "html/template"
     "log"
     "bytes"
+    "mime"
 
     "webservice/configuration"
+    "webservice/state"
 
     f "github.com/gofiber/fiber/v2"
 )
 
 
-func SetRoutes( router *f.App, config *configuration.Config, healthiness *bool ){
+func SetRoutes( router *f.App, config *configuration.Config, store state.Store, healthiness *bool ) {
 
     indexHtmlTemplate, err := template.New( "index" ).Parse( indexHtml )
     if err != nil {
@@ -96,6 +98,130 @@ func SetRoutes( router *f.App, config *configuration.Config, healthiness *bool )
     })
 
 
+    statePathGroup := router.Group( "/state" )
+
+
+    statePathGroup.Get( "/:name", func( c *f.Ctx ) error {
+        existingItem, err := store.Fetch( c.Params( "name" ) )
+        if err != nil {
+            c.Status( http.StatusInternalServerError )
+            return c.Send( nil )
+        }
+
+        if existingItem == nil {
+            return c.SendStatus( http.StatusNotFound )
+        }
+
+        c.Set( "Content-Type", existingItem.MimeType() )
+        return c.Send( existingItem.Data() )
+    })
+
+
+    statePathGroup.Put( "/:name", func( c *f.Ctx ) error {
+        contentType := c.Get( "Content-Type" )
+        _, _, err := mime.ParseMediaType( contentType )
+        if err != nil {
+            c.Status( http.StatusBadRequest )
+            return c.SendString(
+                fmt.Sprintf( "Invalid MIME type: %s", contentType ),
+            )
+        }
+
+        name := c.Params( "name" )
+        existingItem, err := store.Fetch( name )
+        if err != nil {
+            c.Status( http.StatusInternalServerError )
+            return c.Send( nil )
+        }
+
+        if existingItem != nil {
+            if bytes.Equal( existingItem.Data(), c.Body() ) &&
+               existingItem.MimeType() == contentType {
+                c.Set( "Content-Type", "text/plain; charset=utf-8" )
+                c.Status( http.StatusOK )
+                return c.SendString( "Resource not changed" )
+            }
+            c.Status( http.StatusNoContent )
+        } else {
+            c.Status( http.StatusCreated )
+        }
+
+        newItem := state.NewItem(
+            name,
+            contentType,
+            c.Body(),
+        )
+
+        if err = store.Add( newItem ); err != nil {
+            c.Status( http.StatusInternalServerError )
+            return c.Send( nil )
+        }
+
+        c.Set( "Content-Location", c.Path() )
+        return c.Send( nil )
+    })
+
+
+    statePathGroup.Delete( "/:name", func( c *f.Ctx ) error {
+        name := c.Params( "name" )
+        existingItem, err := store.Fetch( name )
+        if err != nil {
+            return c.SendStatus( http.StatusInternalServerError )
+        }
+
+        if existingItem == nil {
+            return c.SendStatus( http.StatusNotFound )
+        }
+
+        if err = store.Remove( name ); err != nil {
+            return c.SendStatus( http.StatusInternalServerError )
+        }
+
+        return c.SendStatus( http.StatusNoContent )
+    })
+
+
+    statePathGroup.Use( "*", func( c *f.Ctx ) error {
+        if method := c.Method(); method == "OPTIONS" {
+            c.Set( "Allow", "GET, PUT, DELETE, OPTIONS" )
+            return c.SendStatus( http.StatusNoContent )
+        }
+
+        return c.SendStatus( http.StatusNotFound )
+    })
+
+
+    router.Get( "/states", func( c *f.Ctx ) error {
+        states, err := store.Show()
+        if err != nil {
+            return c.SendStatus( http.StatusInternalServerError )
+        }
+
+        const pathPrefix string = "/state"
+        paths := make ( []string, len( states ) )
+        for i, state := range states {
+            paths[ i ] = fmt.Sprintf( "%s/%s", pathPrefix, state )
+        }
+
+        headers := c.GetReqHeaders()
+        var response string
+        if strings.Contains( headers[ "Accept" ], "json" ) {
+            c.Set( "Content-Type", "application/json; charset=utf-8" )
+            resJson, err := json.Marshal( paths )
+            if err != nil {
+                return err
+            }
+            response = string( resJson )
+        } else {
+            c.Set( "Content-Type", "text/plain; charset=utf-8" )
+            response = strings.Join( paths, "\n" )
+        }
+
+        c.Status( http.StatusOK )
+        return c.SendString( response )
+    })
+
+
     router.Use( func( c *f.Ctx ) error {
         return c.SendStatus( http.StatusTeapot )
     })
diff --git a/routing/routes_test.go b/routing/routes_test.go
index 15b1e93c3758ea10d634e42638924c8f18951392..c7295c855e9e4509280a1f44c2792aed68574807 100644
--- a/routing/routes_test.go
+++ b/routing/routes_test.go
@@ -1,9 +1,11 @@
 package routing
 
 import (
+    "bytes"
     "fmt"
     "io"
     "os"
+    "strings"
     "time"
     "math/rand"
     "encoding/json"
@@ -15,10 +17,11 @@ import (
     "github.com/stretchr/testify/assert"
 
     "webservice/configuration"
+    "webservice/state"
 )
 
 
-func setup() ( *f.App, *configuration.Config, *bool ){
+func setup() ( *f.App, *configuration.Config, state.Store, *bool ){
     os.Setenv( "ENV_NAME", "testing" )
     config, _ := configuration.New()
 
@@ -26,11 +29,11 @@ func setup() ( *f.App, *configuration.Config, *bool ){
         AppName: "test",
         DisableStartupMessage: false,
     })
-
+    store := state.NewEphemeralStore()
     var isHealthy = true
-    SetRoutes( server, config, &isHealthy )
+    SetRoutes( server, config, store, &isHealthy )
 
-    return server, config, &isHealthy
+    return server, config, store, &isHealthy
 }
 
 
@@ -59,6 +62,20 @@ func jsonToMap( body *io.ReadCloser ) ( map[string]interface{}, error ) {
     return data, nil
 }
 
+func jsonToStringSlice( body *io.ReadCloser ) ( []string, error ) {
+    defer ( *body ).Close()
+
+    var data []string
+    bodyBytes, err := io.ReadAll( *body )
+    if err != nil {
+        return data, err
+    }
+    if err := json.Unmarshal( bodyBytes, &data ); err != nil {
+       return data, err
+    }
+    return data, nil
+}
+
 
 func generateRandomNumberString() string {
     r := rand.New( rand.NewSource( time.Now().Unix() ) )
@@ -66,9 +83,16 @@ func generateRandomNumberString() string {
     return fmt.Sprintf( "%d", randomNumber )
 }
 
+func generateRandomBytes( length int ) []byte {
+    r := rand.New( rand.NewSource( time.Now().Unix() ) )
+    bytes := make( []byte, length )
+    _, _ = r.Read( bytes )
+    return bytes
+}
+
 
 func TestIndexRoute( t *testing.T ){
-    router, _, _ := setup()
+    router, _, _, _ := setup()
 
     req := ht.NewRequest( "GET", "/", nil )
     req.Header.Add( "Accept", "text/html" )
@@ -87,7 +111,7 @@ func TestIndexRoute( t *testing.T ){
 
 
 func TestHealthRoute( t *testing.T ){
-    router, _, healthiness := setup()
+    router, _, _, healthiness := setup()
 
     req := ht.NewRequest( "GET", "/health", nil )
     res, err := router.Test( req, -1 )
@@ -110,7 +134,7 @@ func TestHealthRoute( t *testing.T ){
 
 
 func TestEnvRoute( t *testing.T ){
-    router, config, _ := setup()
+    router, config, _, _ := setup()
 
     envVarName := "TEST_ENV_VAR"
     envVarValue := generateRandomNumberString()
@@ -130,3 +154,92 @@ func TestEnvRoute( t *testing.T ){
     res, err = router.Test( req, -1 )
     assert.Equal( t, http.StatusForbidden, res.StatusCode )
 }
+
+
+func TestState( t *testing.T ){
+    router, _, store, _ := setup()
+
+    const statePath1 = "/state/just-a-test"
+    const statePath1Mime = "text/plain"
+    const statePath1Body1 = "just a test body"
+    const statePath1Body2 = "this body just changed"
+
+    const statePath2 = "/state/another-test"
+    const statePath2Mime = "application/octet-stream"
+    const statePath2BodySize = 64
+    statePath2Body := generateRandomBytes( statePath2BodySize )
+
+    req := ht.NewRequest( "GET", statePath1, nil )
+    res, _ := router.Test( req, 1 )
+    assert.Equal( t, http.StatusNotFound, res.StatusCode )
+
+    req = ht.NewRequest( "PUT", statePath1, nil )
+    req.Header.Add( "Content-Type", "not a MIME type" )
+    res, _ = router.Test( req, 1 )
+    assert.Equal( t, http.StatusBadRequest, res.StatusCode )
+
+    req = ht.NewRequest( "PUT", statePath1, strings.NewReader( statePath1Body1 ) )
+    req.Header.Add( "Content-Type", statePath1Mime )
+    res, _ = router.Test( req, 1 )
+    assert.Equal( t, http.StatusCreated, res.StatusCode )
+
+    req = ht.NewRequest( "PUT", statePath1, strings.NewReader( statePath1Body1 ) )
+    req.Header.Add( "Content-Type", statePath1Mime )
+    res, _ = router.Test( req, 1 )
+    assert.Equal( t, http.StatusOK, res.StatusCode )
+
+    req = ht.NewRequest( "PUT", statePath1, strings.NewReader( statePath1Body2 ) )
+    req.Header.Add( "Content-Type", statePath1Mime )
+    res, _ = router.Test( req, 1 )
+    assert.Equal( t, http.StatusNoContent, res.StatusCode )
+
+    req = ht.NewRequest( "GET", statePath1, nil )
+    res, _ = router.Test( req, 1 )
+    bodyContent, err := bodyToString( &res.Body )
+    assert.Nil( t, err )
+    assert.Equal( t, http.StatusOK, res.StatusCode )
+    assert.Equal( t, statePath1Mime, res.Header[ "Content-Type" ][0] )
+    assert.Equal( t, statePath1Body2, bodyContent )
+
+    req = ht.NewRequest( "DELETE", statePath2, nil )
+    res, _ = router.Test( req, 1 )
+    assert.Equal( t, http.StatusNotFound, res.StatusCode )
+
+    req = ht.NewRequest( "PUT", statePath2, bytes.NewReader( statePath2Body ) )
+    req.Header.Add( "Content-Type", statePath2Mime )
+    res, _ = router.Test( req, 1 )
+    assert.Equal( t, http.StatusCreated, res.StatusCode )
+
+    req = ht.NewRequest( "GET", statePath2, nil )
+    res, _ = router.Test( req, 1 )
+    bodyBytes, err := io.ReadAll( res.Body )
+    assert.Equal( t, http.StatusOK, res.StatusCode )
+    assert.Equal( t, statePath2Mime, res.Header[ "Content-Type" ][0] )
+    assert.Equal( t, statePath2Body, bodyBytes )
+
+    req = ht.NewRequest( "GET", "/states", nil )
+    req.Header.Add( "Accept", "application/json" )
+    res, _ = router.Test( req, 1 )
+    states, err := jsonToStringSlice( &res.Body )
+    assert.Nil( t, err )
+    assert.Len( t, states, 2 )
+    assert.Contains( t, states, statePath1 )
+    assert.Contains( t, states, statePath2 )
+
+    req = ht.NewRequest( "DELETE", statePath1, nil )
+    res, _ = router.Test( req, 1 )
+    assert.Equal( t, http.StatusNoContent, res.StatusCode )
+
+    req = ht.NewRequest( "GET", "/states", nil )
+    res, _ = router.Test( req, 1 )
+    statesPlain, err := bodyToString( &res.Body )
+    assert.Nil( t, err )
+    assert.NotContains( t, statesPlain, statePath1 )
+    assert.Contains( t, statesPlain, statePath2 )
+
+    err = store.Disconnect()
+    req = ht.NewRequest( "GET", statePath2, nil )
+    res, _ = router.Test( req, 1 )
+    assert.Nil( t, err )
+    assert.Equal( t, http.StatusInternalServerError, res.StatusCode )
+}
diff --git a/state/ephemeral.go b/state/ephemeral.go
new file mode 100644
index 0000000000000000000000000000000000000000..e01ac90f88824fbbc1595ead4a31c9ec394f03e3
--- /dev/null
+++ b/state/ephemeral.go
@@ -0,0 +1,70 @@
+package state
+
+import (
+    "errors"
+)
+
+
+type Ephemeral struct {
+    store map[ string ] *Item
+}
+
+
+func NewEphemeralStore() *Ephemeral {
+    return &Ephemeral{
+        store: map[ string ] *Item {},
+    }
+}
+
+
+func ( e *Ephemeral ) Add( i *Item ) error {
+    if e.store == nil {
+        return errors.New( "ephemeral storage not available" )
+    }
+
+    name := i.Name()
+    e.store[ name ] = i
+    return nil
+}
+
+
+func ( e *Ephemeral ) Remove( name string ) error {
+    if e.store == nil {
+        return errors.New( "ephemeral storage not available" )
+    }
+
+    delete( e.store, name )
+    return nil
+}
+
+
+func ( e *Ephemeral ) Fetch( name string ) ( *Item, error ) {
+    if e.store == nil {
+        return nil, errors.New( "ephemeral storage not available" )
+    }
+
+    item, found := e.store[ name ]
+    if !found {
+        return nil, nil
+    }
+    return item, nil
+}
+
+
+func ( e *Ephemeral ) Show() ( []string, error ) {
+    if e.store == nil {
+        return nil, errors.New( "ephemeral storage not available" )
+    }
+
+    names := make( []string, 0, len( e.store ) )
+    for k := range e.store {
+        names = append( names, k )
+    }
+    return names, nil
+}
+
+
+func ( e *Ephemeral ) Disconnect() error {
+    e.store = nil
+    return nil
+}
diff --git a/state/item.go b/state/item.go
new file mode 100644
index 0000000000000000000000000000000000000000..ccfea74f834450aaf5b10d6504ff94d9e52c25e1
--- /dev/null
+++ b/state/item.go
@@ -0,0 +1,30 @@
+package state
+
+
+type Item struct {
+    name        string
+    mimeType    string
+    data        []byte
+}
+
+
+func NewItem( name string, mimeType string, data []byte ) *Item {
+    return &Item{
+        name: name,
+        mimeType: mimeType,
+        data: data,
+    }
+}
+
+
+func ( i *Item ) Name() string {
+    return i.name
+}
+
+func ( i *Item ) MimeType() string {
+    return i.mimeType
+}
+
+func ( i *Item ) Data() []byte {
+    return i.data
+}
diff --git a/state/store.go b/state/store.go
new file mode 100644
index 0000000000000000000000000000000000000000..c8ec606aacf99790fedf1b2efc038f68190cbcb4
--- /dev/null
+++ b/state/store.go
@@ -0,0 +1,12 @@
+package state
+
+
+
+type Store interface {
+    Add( i *Item ) error
+    Remove( name string ) error
+    Fetch( name string ) ( *Item, error )
+    Show() ( []string, error )
+
+    Disconnect() error
+}