diff --git a/epicardium/api/caller.c b/epicardium/api/caller.c
new file mode 100644
index 0000000000000000000000000000000000000000..3b6d27b9a2ca277b9fa8607c9cdb92202e105d54
--- /dev/null
+++ b/epicardium/api/caller.c
@@ -0,0 +1,39 @@
+#include <stdlib.h>
+#include "sema.h"
+#include "api/caller.h"
+
+void*_api_call_start(api_id_t id, uintptr_t size)
+{
+	while (SEMA_GetSema(_API_SEMAPHORE) == E_BUSY) {}
+
+	/* TODO: Check flag */
+
+	API_CALL_MEM->id = id;
+	return API_CALL_MEM->buffer;
+}
+
+void*_api_call_transact(void*buffer)
+{
+	API_CALL_MEM->call_flag = _API_FLAG_CALLING;
+	SEMA_FreeSema(_API_SEMAPHORE);
+
+	/* Notify the dispather of the new call */
+	__SEV();
+	__WFE();
+
+	while (1) {
+		/* Wait for the dispather to return */
+		__WFE();
+
+		while (SEMA_GetSema(_API_SEMAPHORE) == E_BUSY) {}
+		if (API_CALL_MEM->call_flag == _API_FLAG_RETURNED) {
+			break;
+		}
+		SEMA_FreeSema(_API_SEMAPHORE);
+	}
+
+	API_CALL_MEM->call_flag = _API_FLAG_IDLE;
+	SEMA_FreeSema(_API_SEMAPHORE);
+
+	return API_CALL_MEM->buffer;
+}
diff --git a/epicardium/api/caller.h b/epicardium/api/caller.h
new file mode 100644
index 0000000000000000000000000000000000000000..092f2fa7b21ae06ef9f3346eda16b749a569cd8d
--- /dev/null
+++ b/epicardium/api/caller.h
@@ -0,0 +1,29 @@
+#include <stdint.h>
+#include "api/common.h"
+
+/*
+ * Initiate an API call.  This function is used internally by code
+ * generated from the API header.
+ *
+ * Args:
+ *   - id: ID of the call to be initiated
+ *   - size: Size of the arguments buffer
+ *
+ * Returns:
+ *   - A pointer to the argument buffer which the caller is supposed
+ *     to fill.  NULL if an error occured or no buffer of the requested
+ *     size is available.
+ */
+void*_api_call_start(api_id_t id, uintptr_t size);
+
+/*
+ * Actually do the API call that was previously initiated using
+ * _api_call_start().
+ *
+ * Args:
+ *   - buffer: Pointer to the buffer that was returned by _api_call_start().
+ *
+ * Returns:
+ *   - Pointer to a buffer containing the return value
+ */
+void*_api_call_transact(void*buffer);
diff --git a/epicardium/api/common.h b/epicardium/api/common.h
new file mode 100644
index 0000000000000000000000000000000000000000..70b24a35a067b417939f3008c17f0b2a19cf26f0
--- /dev/null
+++ b/epicardium/api/common.h
@@ -0,0 +1,39 @@
+#include <stdint.h>
+
+/*
+ * Semaphore used for API synchronization.
+ * TODO: Replace this with a LDREX/STREX based implementation
+ */
+#define _API_SEMAPHORE    0
+
+/* Type of API IDs */
+typedef uint32_t api_id_t ;
+
+#define _API_FLAG_IDLE        0
+#define _API_FLAG_CALLING     1
+#define _API_FLAG_RETURNED    2
+
+/* Layout of the shared memory for API calls */
+struct api_call_mem {
+	/*
+	 * Flag for synchronization of API calls.  When this flag
+	 * is set, the caller has issued a call and is waiting for
+	 * the dispatcher to reset the flag.
+	 */
+	uint8_t call_flag;
+
+	/* ID if the ongoing API call */
+	api_id_t id;
+
+	/*
+	 * Buffer for arguments/return value.  This buffer will be
+	 * *overflown*, because there is guaranteed space behind it.
+	 *
+	 * TODO: Add a maximum bounds check
+	 */
+	uint8_t buffer[1];
+};
+
+/* TODO: Make this address part of the linker script */
+static __attribute__((unused)) struct api_call_mem* API_CALL_MEM =
+	(struct api_call_mem*)0x20080000;
diff --git a/epicardium/api/dispatcher.c b/epicardium/api/dispatcher.c
new file mode 100644
index 0000000000000000000000000000000000000000..d499bd16f261689a667a1ebf95c6bd1da3b8e281
--- /dev/null
+++ b/epicardium/api/dispatcher.c
@@ -0,0 +1,42 @@
+#include <stdlib.h>
+#include "sema.h"
+#include "api/dispatcher.h"
+
+int api_dispatcher_init()
+{
+	int ret;
+
+	ret = SEMA_Init(NULL);
+	API_CALL_MEM->call_flag = _API_FLAG_IDLE;
+
+	/*
+	 * Enable TX events for both cores.
+	 * TODO: Is this the right place?
+	 */
+	MXC_GCR->evten |= 0x24;
+
+	return ret;
+}
+
+api_id_t api_dispatcher_poll()
+{
+	api_id_t id = 0;
+	while (SEMA_GetSema(_API_SEMAPHORE) == E_BUSY) {}
+
+	if (API_CALL_MEM->call_flag != _API_FLAG_CALLING) {
+		SEMA_FreeSema(_API_SEMAPHORE);
+		return 0;
+	}
+
+	id = API_CALL_MEM->id;
+	__api_dispatch_call(id, API_CALL_MEM->buffer);
+	API_CALL_MEM->call_flag = _API_FLAG_RETURNED;
+
+	SEMA_FreeSema(_API_SEMAPHORE);
+
+	/* Notify the caller that we returned */
+	__SEV();
+	__WFE();
+
+	return id;
+}
diff --git a/epicardium/api/dispatcher.h b/epicardium/api/dispatcher.h
new file mode 100644
index 0000000000000000000000000000000000000000..1385b719487b578f93c7294ba0b7863822aab265
--- /dev/null
+++ b/epicardium/api/dispatcher.h
@@ -0,0 +1,17 @@
+#include "api/common.h"
+
+/*
+ * Initialize the API system.  This function *must* be called
+ * before any API action can take place.
+ */
+int api_dispatcher_init();
+
+/*
+ * Attempt to dispatch a call, if the caller has requested one.
+ * Will return 0 if no call was dispatched and the ID of the dispatched
+ * call otherwise.
+ */
+api_id_t api_dispatcher_poll();
+
+/* This function is defined by the generated dispatcher code */
+void __api_dispatch_call(api_id_t id, void*buffer);
diff --git a/epicardium/api/genapi.py b/epicardium/api/genapi.py
new file mode 100644
index 0000000000000000000000000000000000000000..5adddf8fcf725744b14ae91e4779ffbbb72f8399
--- /dev/null
+++ b/epicardium/api/genapi.py
@@ -0,0 +1,189 @@
+import argparse
+import contextlib
+import os
+import re
+import subprocess
+
+
+def main():
+    parser = argparse.ArgumentParser(
+        description="Generate the API stubs from a header file."
+    )
+    parser.add_argument(
+        "-H", "--header", required=True, help="The header to base the definitions on."
+    )
+    parser.add_argument(
+        "-c", "--client", required=True, help="The output client-side c source file."
+    )
+    parser.add_argument(
+        "-s", "--server", required=True, help="The output server-side c source file."
+    )
+    args = parser.parse_args()
+
+    with contextlib.ExitStack() as cx:
+        # Run the preprocessor on the header file to get the API definitions.
+        #
+        # For this, we first need a source to include the header which contains
+        # an alternative definition of the `API` macro that marks definitions in
+        # a way we can find later on.
+        api_src = """\
+#define API(id, def) __GENERATE_API $ __GEN_ID_##id $ def $
+#include "{header}"
+""".format(
+            header=os.path.relpath(args.header)
+        )
+
+        # Evaluate the preprocessor
+        source = subprocess.check_output(
+            ["gcc", "-E", "-"], input=api_src.encode()
+        ).decode()
+
+        # Parse the header for API definitions
+        matcher = re.compile(
+            r"__GENERATE_API \$ __GEN_ID_(?P<id>\w+) \$ (?P<type>\w+(?:\*+|\s+))(?P<decl>.+?)\((?P<args>.*?)\) \$",
+            re.DOTALL | re.MULTILINE,
+        )
+
+        args_matcher = re.compile(r"(?P<type>\w+(?:\*+|\s+))(?P<name>\w+),")
+
+        # Open output files
+        f_client = cx.enter_context(open(args.client, "w"))
+        f_server = cx.enter_context(open(args.server, "w"))
+
+        print("""\
+#include <stdio.h>
+
+#include "{}"
+#include "api/caller.h"
+""".format(
+            os.path.basename(args.header)
+        ), file=f_client)
+
+        print("""\
+#include <stdio.h>
+#include "{}"
+
+void __api_dispatch_call(uint32_t id, void*buffer)
+{{
+    switch (id) {{""".format(
+            os.path.basename(args.header)
+        ), file=f_server)
+
+        for match in matcher.finditer(source):
+            api_id = match.group("id")
+            api_return = match.group("type").strip()
+            api_decl = match.group("decl")
+            api_args = match.group("args")
+
+            api_args_names = []
+            api_args_types = []
+            api_args_sizes = []
+
+            # Destructure args
+            for match in args_matcher.finditer(api_args + ","):
+                arg_type = match.group("type").strip()
+                arg_name = match.group("name")
+
+                api_args_names.append(arg_name)
+                api_args_types.append(arg_type)
+                api_args_sizes.append("sizeof({})".format(arg_type))
+
+            print(
+                """\
+/* Autogenerated stub for {id} */
+{ret} {cdecl}({cargs})
+{{
+    const int size = {total_size};
+    void*buffer;
+
+    buffer = _api_call_start({id}, size);
+    /* TODO: Check if buffer is no NULL */
+""".format(
+                    id=api_id,
+                    ret=api_return,
+                    cdecl=api_decl,
+                    cargs=api_args,
+                    total_size=" + ".join(api_args_sizes) if api_args_sizes != [] else "0",
+                ),
+                file=f_client,
+            )
+
+            if api_return != "void":
+                print("""\
+    case {id}:
+        *(({ret}*)buffer) = {cdecl}(""".format(id=api_id, ret=api_return, cdecl=api_decl),
+                    file=f_server,
+                )
+            else:
+                print("""\
+    case {id}:
+        {cdecl}(""".format(id=api_id, ret=api_return, cdecl=api_decl),
+                    file=f_server,
+                )
+
+            for i, (arg, ty) in enumerate(zip(api_args_names, api_args_types)):
+                print(
+                    """    *({type}*)(buffer + {offset}) = {arg};""".format(
+                        type=ty,
+                        offset=" + ".join(api_args_sizes[:i]) if i > 0 else "0",
+                        arg=arg,
+                    ),
+                    file=f_client,
+                )
+
+                if i != 0:
+                    print(",", file=f_server)
+
+                print(
+                    """\
+            *({type}*)(buffer + {offset})""".format(
+                        type=ty,
+                        offset=" + ".join(api_args_sizes[:i]) if i > 0 else "0",
+                    ),
+                    file=f_server,
+                    end="",
+                )
+
+            print("""
+        );
+        break;""".format(
+                    cdecl=api_decl,
+                    args=", ".join(api_args_names),
+                ),
+                file=f_server,
+            )
+
+            if api_return != "void":
+                print(
+                """
+    return *({ret}*)_api_call_transact(buffer);
+}}
+""".format(
+                        id=api_id,
+                        ret=api_return,
+                    ),
+                    file=f_client,
+                )
+            else:
+                print(
+                """
+    _api_call_transact(buffer);
+}}
+""".format(
+                        id=api_id,
+                    ),
+                    file=f_client,
+                )
+
+        print("""\
+    default:
+        printf("Error: API function %x is unknown!!\\n", {id});
+        break;
+    }}
+}}""".format(
+            id=api_id,
+        ), file=f_server)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h
new file mode 100644
index 0000000000000000000000000000000000000000..ee5900f2142569845b2b3303115a33197902c814
--- /dev/null
+++ b/epicardium/epicardium.h
@@ -0,0 +1,15 @@
+#ifndef _EPICARDIUM_H
+#define _EPICARDIUM_H
+#include <stdint.h>
+
+#ifndef API
+#  define API(id, def) def
+#endif
+
+#define API_UART_WRITE 0x1
+API(API_UART_WRITE, void epic_uart_write_str(char*str, intptr_t length));
+
+#define API_UART_READ  0x2
+API(API_UART_READ, char epic_uart_read_chr(void));
+
+#endif /* _EPICARDIUM_H */
diff --git a/epicardium/main.c b/epicardium/main.c
index cb2051e3dc996aa63dd66e9d242bd45dddab8427..627d876345f860f78a87b15e810fe1cff6496f2c 100644
--- a/epicardium/main.c
+++ b/epicardium/main.c
@@ -1,11 +1,33 @@
 #include <stdio.h>
 #include "card10.h"
+#include "uart.h"
+#include "api/dispatcher.h"
+
+extern mxc_uart_regs_t * ConsoleUart;
+
+void epic_uart_write_str(char*str, intptr_t length)
+{
+	UART_Write(ConsoleUart, (uint8_t*)str, length);
+}
+
+char epic_uart_read_chr(void)
+{
+	return UART_ReadByte(ConsoleUart);
+}
 
 int main(void)
 {
 	card10_init();
 	card10_diag();
 
+	printf("Initializing dispatcher ...\n");
+	api_dispatcher_init();
+
 	printf("Staring core1 payload ...\n");
 	core1_start();
+
+	while(1) {
+		__WFE();
+		api_dispatcher_poll();
+	}
 }
diff --git a/epicardium/meson.build b/epicardium/meson.build
index 51cc66740759e53e22892ffe8bda02db19e857af..767c9c4f9273203af677cc1dcc10775721510f60 100644
--- a/epicardium/meson.build
+++ b/epicardium/meson.build
@@ -1,9 +1,55 @@
 name = 'epicardium'
 
+##########################################################################
+#
+# API
+#
+##########################################################################
+
+api = custom_target(
+  'api_*.c',
+  input: 'epicardium.h',
+  output: ['api_caller.c', 'api_dispatcher.c'],
+  command: [
+    python3,
+    meson.current_source_dir() + '/api/genapi.py',
+    '-H', '@INPUT0@',
+    '-c', '@OUTPUT0@', '-s', '@OUTPUT1@',
+  ],
+  depend_files: 'api/genapi.py',
+)
+
+api_caller_lib = static_library(
+  'api-caller',
+  'api/caller.c',
+  api[0], # Caller
+  dependencies: periphdriver,
+)
+
+api_caller = declare_dependency(
+  include_directories: include_directories('.'),
+  link_with: api_caller_lib,
+  dependencies: periphdriver,
+)
+
+api_dispatcher_lib = static_library(
+  'api-dispatcher',
+  'api/dispatcher.c',
+  api[1], # Dispatcher
+  dependencies: periphdriver,
+)
+
+##########################################################################
+#
+# Epicardium executable
+#
+##########################################################################
+
 elf = executable(
   name + '.elf',
   'main.c',
   dependencies: [libcard10, max32665_startup_core0],
+  link_with: api_dispatcher_lib,
   link_whole: [max32665_startup_core0_lib, board_card10_lib],
   link_args: [
     '-Wl,-Map=' + meson.current_build_dir() + '/' + name + '.map',
diff --git a/hw-tests/meson.build b/hw-tests/meson.build
index 0523f5006d3e9a55b000f276564ad5a052db24de..ed36cc1c1834553585dc5a8ca578477914520a12 100644
--- a/hw-tests/meson.build
+++ b/hw-tests/meson.build
@@ -1,4 +1,5 @@
-subdir('api-demo/')
+# Disabled due to meson bug in older version
+# subdir('api-demo/')
 subdir('bmatest/')
 subdir('bmetest/')
 subdir('dual-core/')
@@ -7,4 +8,5 @@ subdir('hello-freertos/')
 subdir('hello-world/')
 subdir('imutest/')
 subdir('ips/')
-subdir('upy-minimal/')
+# Disabled due to meson bug in older version
+# subdir('upy-minimal/')
diff --git a/pycardium/meson.build b/pycardium/meson.build
index 7bb11316fe865e7bbf1dae740eddb15dd950ffcd..de4d35be675fff87f3276424068a6fb609d91e0c 100644
--- a/pycardium/meson.build
+++ b/pycardium/meson.build
@@ -53,7 +53,7 @@ executable(
   modsrc,
   mp_headers,
   include_directories: micropython_includes,
-  dependencies: [max32665_startup_core1, board_card10, periphdriver],
+  dependencies: [max32665_startup_core1, board_card10, periphdriver, api_caller],
   link_whole: [max32665_startup_core1_lib, board_card10_lib],
   link_with: upy,
   link_args: [
diff --git a/pycardium/mphalport.c b/pycardium/mphalport.c
index 5f69a7d2f656d01b5147a2a78745e99560fd6ef7..26521235cd2e98cd7c0309b02cef57b8b48a0524 100644
--- a/pycardium/mphalport.c
+++ b/pycardium/mphalport.c
@@ -6,24 +6,22 @@
 #include "py/obj.h"
 #include "py/runtime.h"
 
+#include "epicardium.h"
+
 /******************************************************************************
  * Serial Communication
  */
 
-/* TODO: Use API boundary instead of direct communication */
-#include "uart.h"
-extern mxc_uart_regs_t * ConsoleUart;
-
 /* Receive single character */
 int mp_hal_stdin_rx_chr(void)
 {
-	return UART_ReadByte(ConsoleUart);
+	return (int)epic_uart_read_chr();
 }
 
 /* Send string of given length */
 void mp_hal_stdout_tx_strn(const char *str, mp_uint_t len)
 {
-	UART_Write(ConsoleUart, (uint8_t*)str, len);
+	epic_uart_write_str(str, len);
 }
 
 /******************************************************************************
@@ -60,12 +58,12 @@ MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_open_obj, 1, mp_builtin_open);
 
 int _getpid(void)
 {
-	;
+	return -1;
 }
 
 int _kill(int pid, int f)
 {
-	;
+	return -1;
 }
 
 void _exit(int r)