diff --git a/src/main/de/fitconnect/api/Sender.java b/src/main/de/fitconnect/api/Sender.java
new file mode 100644
index 0000000000000000000000000000000000000000..6a51e282983a574d38671d21e23f10a4d7c921f1
--- /dev/null
+++ b/src/main/de/fitconnect/api/Sender.java
@@ -0,0 +1,52 @@
+package de.fitconnect.api;
+
+import de.fitconnect.api.auth.OAuthToken;
+import de.fitconnect.api.data.Data;
+import de.fitconnect.api.data.Metadata;
+import de.fitconnect.api.validation.ValidationResult;
+
+import java.io.ByteArrayInputStream;
+
+/**
+ * A technical system that creates a submission via the Submission API.
+ */
+public interface Sender {
+
+    /**
+     * Authenticates the sender against the FIT-Co API and retrieves an authentication token
+     *
+     * @param clientId - a unique identifier within the FIT-Co platform environment
+     * @param clientSecret - a secret that is only known by the application and the OAuth server
+     * @param scope - OAuth scope that determines if a submission is accepted by the client
+     * @return {@link OAuthToken} that holds the access-token
+     */
+    OAuthToken retrieveAuthenticationToken(final String clientId, final String clientSecret, String scope);
+
+    // TODO
+    ValidationResult validateCertificateChain(byte[] chain);
+
+    /**
+     * Encrypts the submission data with JWE.
+     *
+     * @param unencryptedData - unencrypted JSON or XML data
+     * @return encrypted JSON or XML data
+     */
+    Data encryptData(final Data unencryptedData);
+
+    /**
+     * Encrypts the submission attachment with JWE.
+     *
+     * @param unencryptedAttachmentBinary - the unencrypted binary data of the attachment
+     * @return the encrypted binary of the attachment
+     */
+    ByteArrayInputStream encryptAttachment(final ByteArrayInputStream unencryptedAttachmentBinary);
+
+    /**
+     * Generates the metadata consisting of the attachment and its hash values.
+     *
+     * @param attachment - the unencrypted binary data of the attachment
+     * @return metadata object
+     */
+    Metadata generateMetadata(byte[] attachment);
+
+}
diff --git a/src/main/de/fitconnect/api/auth/OAuthService.java b/src/main/de/fitconnect/api/auth/OAuthService.java
new file mode 100644
index 0000000000000000000000000000000000000000..5e45fd8d807c51cfb1662dadf2b2807f0fb64800
--- /dev/null
+++ b/src/main/de/fitconnect/api/auth/OAuthService.java
@@ -0,0 +1,8 @@
+package de.fitconnect.api.auth;
+
+import java.util.Optional;
+
+public interface OAuthService {
+
+    Optional<OAuthToken> authenticate(String clientId, String clientSecret, String scope);
+}
diff --git a/src/main/de/fitconnect/client/FitCoAuthService.java b/src/main/de/fitconnect/client/FitCoAuthService.java
new file mode 100644
index 0000000000000000000000000000000000000000..c99a1384b34eb18dfc0bb2ee9b490d3ad8d073ce
--- /dev/null
+++ b/src/main/de/fitconnect/client/FitCoAuthService.java
@@ -0,0 +1,68 @@
+package de.fitconnect.client;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import de.fitconnect.api.auth.OAuthService;
+import de.fitconnect.api.auth.OAuthToken;
+import de.fitconnect.api.logger.SdkLogger;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.util.HashMap;
+import java.util.Optional;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import static java.util.stream.Collectors.joining;
+
+public class FitCoAuthService implements OAuthService {
+
+    private static final Logger logger = SdkLogger.defaultLogger(FitCoAuthService.class);
+    private static final ObjectMapper mapper = new ObjectMapper();
+
+    private final HttpClient client;
+    private final String tokenUrl;
+
+    public FitCoAuthService(final HttpClient client, final String tokenUrl) {
+        this.client = client;
+        this.tokenUrl = tokenUrl;
+    }
+
+    @Override
+    public Optional<OAuthToken> authenticate(String clientId, String clientSecret, String scope) {
+        final String requestBody = buildRequestBody(clientId, clientSecret, scope);
+        try {
+            return Optional.of(performTokenRequest(requestBody));
+        } catch (IOException | InterruptedException e) {
+            logger.log(Level.SEVERE, "error retrieving access token", e);
+            return Optional.empty();
+        }
+    }
+
+    private String buildRequestBody(String clientId, String clientSecret, String scope) {
+        var data = new HashMap<String, String>() {{
+            put("grant_type", "client_credentials");
+            put("client_id", clientId);
+            put("client_secret", clientSecret);
+            put("scope", scope);
+        }};
+
+        return data.entrySet()
+                .stream()
+                .map(e -> e.getKey() + "=" + e.getValue())
+                .collect(joining("&"));
+    }
+
+    private OAuthToken performTokenRequest(final String requestBody) throws IOException, InterruptedException {
+        HttpRequest request = HttpRequest.newBuilder()
+                .header("Accept", "application/json")
+                .header("Content-Type","application/x-www-form-urlencoded;charset=UTF-8")
+                .uri(URI.create(tokenUrl))
+                .POST(HttpRequest.BodyPublishers.ofString(requestBody))
+                .build();
+        var responseBody = client.send(request, HttpResponse.BodyHandlers.ofString()).body();
+        return mapper.readValue(responseBody,OAuthToken.class);
+    }
+}
diff --git a/src/main/de/fitconnect/client/SubmissionSender.java b/src/main/de/fitconnect/client/SubmissionSender.java
new file mode 100644
index 0000000000000000000000000000000000000000..095366384bffc4486a8ee79d2e846475b7238c20
--- /dev/null
+++ b/src/main/de/fitconnect/client/SubmissionSender.java
@@ -0,0 +1,44 @@
+package de.fitconnect.client;
+
+import de.fitconnect.api.Sender;
+import de.fitconnect.api.auth.OAuthToken;
+import de.fitconnect.api.auth.OAuthService;
+import de.fitconnect.api.data.Data;
+import de.fitconnect.api.data.Metadata;
+import de.fitconnect.api.validation.ValidationResult;
+
+import java.io.ByteArrayInputStream;
+
+public class SubmissionSender implements Sender {
+
+    private final OAuthService authService;
+
+    public SubmissionSender(final OAuthService authService){
+        this.authService = authService;
+    }
+
+    @Override
+    public OAuthToken retrieveAuthenticationToken(String clientId, String clientSecret, String scope) {
+        return authService.authenticate(clientId, clientSecret, scope).orElseThrow();
+    }
+
+    @Override
+    public ValidationResult validateCertificateChain(byte[] chain) {
+        throw new UnsupportedOperationException("Not yet implemented");
+    }
+
+    @Override
+    public Data encryptData(Data unencryptedData) {
+        throw new UnsupportedOperationException("Not yet implemented");
+    }
+
+    @Override
+    public ByteArrayInputStream encryptAttachment(ByteArrayInputStream unencryptedAttachmentBinary) {
+        throw new UnsupportedOperationException("Not yet implemented");
+    }
+
+    @Override
+    public Metadata generateMetadata(byte[] attachment) {
+        throw new UnsupportedOperationException("Not yet implemented");
+    }
+}
diff --git a/src/test/java/OAuthTokenIntegrationTest.java b/src/test/java/OAuthTokenIntegrationTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a3ec6a691ecb7a09b0ca10d87e8166b731dff344
--- /dev/null
+++ b/src/test/java/OAuthTokenIntegrationTest.java
@@ -0,0 +1,33 @@
+import de.fitconnect.api.auth.OAuthToken;
+import de.fitconnect.client.FitCoAuthService;
+import de.fitconnect.client.SubmissionSender;
+import org.junit.jupiter.api.Test;
+
+import java.net.http.HttpClient;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class OAuthTokenIntegrationTest {
+
+    @Test
+    void retrieveAuthenticationToken() {
+
+        // Given
+        var tokenUrl = "https://auth-testing.fit-connect.fitko.dev/token";
+        var clientId = "781f6213-0f0f-4a79-9372-e7187ffda98b";
+        var secret = "PnzR8Vbmhpv_VwTkT34wponqXWK8WBm-LADlryYdV4o";
+        var scope = "send:region:DE";
+
+        var httpClient = HttpClient.newHttpClient();
+        var authService = new FitCoAuthService(httpClient, tokenUrl);
+        var sender = new SubmissionSender(authService);
+
+        // When
+        OAuthToken token = sender.retrieveAuthenticationToken(clientId, secret, scope);
+
+        // Then
+        assertNull(token.error());
+        assertNotNull(token.access_token());
+        assertEquals(1800, token.expires_in());
+    }
+}
\ No newline at end of file