diff --git a/hw-tests/api-demo/api.h b/hw-tests/api-demo/api.h
new file mode 100644
index 0000000000000000000000000000000000000000..295f59b2d45ea0c80c90a96188f0d510569e019c
--- /dev/null
+++ b/hw-tests/api-demo/api.h
@@ -0,0 +1,24 @@
+#ifndef _API_H
+#define _API_H
+#include <stdint.h>
+
+#ifndef API
+#  define API(id, def) def
+#endif
+
+#define API_BUZZER 0x35c3
+API(API_BUZZER, void api_set_buzzer(uint8_t state));
+
+typedef struct {
+    uint8_t red;
+    uint8_t green;
+    uint8_t blue;
+} led_color_t;
+
+#define API_LED 0xc0ffee
+API(API_LED, void api_set_led(uint8_t led, led_color_t color));
+
+#define API_TEST 0xdeadc0de
+API(API_TEST, void api_test(char test0, short test1, int test2, long test3));
+
+#endif /* _API_H */
diff --git a/hw-tests/api-demo/api/api_caller.c b/hw-tests/api-demo/api/api_caller.c
new file mode 100644
index 0000000000000000000000000000000000000000..311dcc5561187cafd5b685988317ab5d8ee9a58b
--- /dev/null
+++ b/hw-tests/api-demo/api/api_caller.c
@@ -0,0 +1,33 @@
+#include <stdlib.h>
+
+#include "api/api_caller.h"
+#include "tmr_utils.h"
+
+void* api_call_start (uint32_t id, void* args, uint32_t size)
+{
+	// aquire semaphore
+	while (E_BUSY == SEMA_GetSema (API_CALL_SEMA)) ;
+
+	ApiCallSpace->id = id;
+	ApiCallSpace->returning = 0;
+	return ApiCallSpace->buf;
+}
+
+void* api_call_bother_dispatcher (void* buf)
+{
+	SEMA_FreeSema (API_CALL_SEMA);
+	// TODO: set event
+
+	while(1) {
+		// aquire semaphore
+		while (E_BUSY == SEMA_GetSema (API_CALL_SEMA)) ;
+		if (ApiCallSpace->returning == 1) {
+			break;
+		}
+		SEMA_FreeSema(API_CALL_SEMA);
+		TMR_Delay(MXC_TMR2, MSEC(100), 0);
+	}
+	SEMA_FreeSema(API_CALL_SEMA);
+
+	return NULL;
+}
diff --git a/hw-tests/api-demo/api/api_caller.h b/hw-tests/api-demo/api/api_caller.h
new file mode 100644
index 0000000000000000000000000000000000000000..39f7d99a60ad527f189e9750fbe2e5de9931212d
--- /dev/null
+++ b/hw-tests/api-demo/api/api_caller.h
@@ -0,0 +1,6 @@
+#include "mxc_errors.h"
+
+#include "api_common.h"
+
+void* api_call (uint32_t id, void *args, uint32_t size);
+void* api_call_bother_dispatcher (void* buf);
diff --git a/hw-tests/api-demo/api/api_common.h b/hw-tests/api-demo/api/api_common.h
new file mode 100644
index 0000000000000000000000000000000000000000..2c4c79d8be627aee8c4fee3719a7d8c16410ecf4
--- /dev/null
+++ b/hw-tests/api-demo/api/api_common.h
@@ -0,0 +1,12 @@
+#include <stdint.h>
+
+#define API_CALL_SEMA 0
+
+struct api_call
+{
+	uint32_t id;
+	uint8_t returning;
+	unsigned char buf[1];
+};
+
+static struct api_call *ApiCallSpace = (struct api_call *)0x20080001;
diff --git a/hw-tests/api-demo/api/api_dispatcher.c b/hw-tests/api-demo/api/api_dispatcher.c
new file mode 100644
index 0000000000000000000000000000000000000000..1c9c73dd8b259e20f2b15e7d7eedebd3b43242b4
--- /dev/null
+++ b/hw-tests/api-demo/api/api_dispatcher.c
@@ -0,0 +1,33 @@
+#include "api_dispatcher.h"
+
+int api_init (sys_cfg_sema_t *sys_cfg)
+{
+	int ret;
+
+	ret = SEMA_Init (sys_cfg);
+	/* Set the status of the flag to a valid initial state (right before
+	 * api-calls are made) */
+	ApiCallSpace->returning = 1;
+
+	return ret;
+}
+
+/* Generated function */
+void __api_dispatch_call(uint32_t id, void*buffer);
+
+void api_dispatcher()
+{
+	while (SEMA_GetSema(API_CALL_SEMA) == E_BUSY) {}
+
+	if (ApiCallSpace->returning == 1) {
+		SEMA_FreeSema(API_CALL_SEMA);
+		return;
+	}
+
+	printf("core1: Catched API CALL!\n");
+	__api_dispatch_call(ApiCallSpace->id, ApiCallSpace->buf);
+
+	ApiCallSpace->returning = 1;
+
+	SEMA_FreeSema(API_CALL_SEMA);
+}
diff --git a/hw-tests/api-demo/api/api_dispatcher.h b/hw-tests/api-demo/api/api_dispatcher.h
new file mode 100644
index 0000000000000000000000000000000000000000..622c347961f3ed686696e7f714e54721b14475cc
--- /dev/null
+++ b/hw-tests/api-demo/api/api_dispatcher.h
@@ -0,0 +1,6 @@
+#include "api_common.h"
+#include "mxc_sys.h"
+
+int api_init (sys_cfg_sema_t *sys_cfg);
+void api_dispatcher();
+
diff --git a/hw-tests/api-demo/core1-dispatcher.c b/hw-tests/api-demo/core1-dispatcher.c
new file mode 100644
index 0000000000000000000000000000000000000000..b0ad77431b5d0531f537efd37e577309b97f385a
--- /dev/null
+++ b/hw-tests/api-demo/core1-dispatcher.c
@@ -0,0 +1,56 @@
+#include "board.h"
+#include "gpio.h"
+#include "mxc_delay.h"
+#include "api.h"
+#include "tmr_utils.h"
+#include "leds.h"
+
+#include "api/api_dispatcher.h"
+
+static const gpio_cfg_t motor_pin = {PORT_0, PIN_8, GPIO_FUNC_OUT, GPIO_PAD_NONE};
+
+void api_set_buzzer(uint8_t state)
+{
+	if (state) {
+		printf("API: Turning motor ON!\n");
+		GPIO_OutSet(&motor_pin);
+	} else {
+		printf("API: Turning motor OFF!\n");
+		GPIO_OutClr(&motor_pin);
+	}
+}
+
+void api_set_led(uint8_t led, led_color_t color)
+{
+	printf("API: Changing color of led %d.\n", led);
+	printf("Color { r: %3d, g: %3d, b: %3d }\n", color.red, color.green, color.blue);
+	leds_set(led, color.red, color.green, color.blue);
+	leds_update();
+}
+
+void api_test(char test0, short test1, int test2, long test3)
+{
+	printf ("test0: %x, test1: %d, test2: %x, test3: %lx\n",
+			test0, (int)test1, test2, test3);
+}
+
+int main(void)
+{
+	api_init(NULL);
+	leds_init();
+
+	while (1) {
+		api_dispatcher();
+                TMR_Delay(MXC_TMR1, MSEC(100), 0);
+	}
+
+
+#if 0
+	// Enable rxev on core1
+	MXC_GCR->evten |= 0x20;
+	for (int i = 0; 1; i++) {
+		__asm volatile("wfe");
+		printf("core1: Hello! %d\n", i);
+	}
+#endif
+}
diff --git a/hw-tests/api-demo/flash.gdb b/hw-tests/api-demo/flash.gdb
new file mode 100644
index 0000000000000000000000000000000000000000..1b9e60c0a785f39ef897ef9aa2f9c9ae193f0449
--- /dev/null
+++ b/hw-tests/api-demo/flash.gdb
@@ -0,0 +1,13 @@
+source ../../.gdbinit
+
+set confirm off
+
+echo Loading core1 image ...
+file ../../build/hw-tests/api-demo/api-demo-core1.elf
+load
+echo Loading core0 image ...
+file ../../build/hw-tests/api-demo/api-demo-core0.elf
+load
+
+reset
+quit
diff --git a/hw-tests/api-demo/genapi.py b/hw-tests/api-demo/genapi.py
new file mode 100644
index 0000000000000000000000000000000000000000..40cf1099e357ef1198d9ef3a732a7915ba074ac5
--- /dev/null
+++ b/hw-tests/api-demo/genapi.py
@@ -0,0 +1,168 @@
+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+) \$ void (?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 "{}"\n'.format(
+            os.path.basename(args.header)
+        ), file=f_client)
+
+        print("""\
+#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_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} */
+void {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,
+                    cdecl=api_decl,
+                    cargs=api_args,
+                    total_size=" + ".join(api_args_sizes),
+                ),
+                file=f_client,
+            )
+
+            print("""\
+    case {id}:
+        {cdecl}(""".format(id=api_id, 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,
+            )
+
+            print(
+                """
+    printf("Sending call {id}\\nBUF: ");
+    for (int i = 0; i < size; i++) {{
+        printf("0x%02x ", ((char*)buffer)[i]);
+    }}
+    printf("\\n");
+
+    api_call_bother_dispatcher(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/hw-tests/api-demo/main.c b/hw-tests/api-demo/main.c
new file mode 100644
index 0000000000000000000000000000000000000000..ac615346537a6427e1e1a295347940bd807f8791
--- /dev/null
+++ b/hw-tests/api-demo/main.c
@@ -0,0 +1,43 @@
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "card10.h"
+#include "tmr_utils.h"
+#include "api.h"
+
+void Core1_Start(void) {
+    //MXC_GCR->gp0 = (uint32_t)(&__isr_vector_core1);
+    MXC_GCR->gp0 = 0x10040000;
+    MXC_GCR->perckcn1 &= ~MXC_F_GCR_PERCKCN1_CPU1;
+}
+
+void Core1_Stop(void) {
+    MXC_GCR->perckcn1 |= MXC_F_GCR_PERCKCN1_CPU1;
+}
+
+int main(void)
+{
+    int count = 0;
+    led_color_t red = {0x10, 0, 0};
+
+    card10_init();
+    card10_diag();
+
+    printf("API Test.\n");
+    printf("core0: Starting dispatcher on core1\n");
+    Core1_Start();
+    TMR_Delay(MXC_TMR0, MSEC(100), 0);
+
+    api_set_buzzer(1);
+    TMR_Delay(MXC_TMR0, MSEC(300), 0);
+    api_set_buzzer(0);
+    api_set_led(5, red);
+    api_test(0xAB, 0x7DEF, 0x01, 0x02);
+
+    while(1) {
+        printf("count = %d\n", count++);
+
+        TMR_Delay(MXC_TMR0, SEC(1), 0);
+    }
+}
diff --git a/hw-tests/api-demo/meson.build b/hw-tests/api-demo/meson.build
new file mode 100644
index 0000000000000000000000000000000000000000..d80efafa1514d4cfcdc6e37f4b30cb3c7fd8b193
--- /dev/null
+++ b/hw-tests/api-demo/meson.build
@@ -0,0 +1,41 @@
+api_stubs = custom_target(
+  'api_*.c',
+  input: 'api.h',
+  output: ['api_client.c', 'api_server.c'],
+  command: [
+    python3,
+    meson.current_source_dir() + '/genapi.py',
+    '-H', '@INPUT0@',
+    '-c', '@OUTPUT0@', '-s', '@OUTPUT1@',
+  ],
+  depend_files: 'genapi.py',
+)
+
+
+name = 'api-demo-core0'
+
+executable(
+  name + '.elf',
+  'main.c',
+  './api/api_caller.c',
+  api_stubs[0],
+  dependencies: [libcard10, max32665_startup_core0],
+  link_whole: [max32665_startup_core0_lib, board_card10_lib],
+  link_args: [
+    '-Wl,-Map=' + meson.current_build_dir() + '/' + name + '.map',
+  ],
+)
+
+name = 'api-demo-core1'
+
+executable(
+  name + '.elf',
+  'core1-dispatcher.c',
+  './api/api_dispatcher.c',
+  api_stubs[1],
+  dependencies: [libcard10, max32665_startup_core1],
+  link_whole: [max32665_startup_core1_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 757dccd1a4e6773bf3aa7b909cf3cf427085922b..1c04f9e686ffbd17c7c3023fc443b3309beb379f 100644
--- a/hw-tests/meson.build
+++ b/hw-tests/meson.build
@@ -1,3 +1,4 @@
+subdir('api-demo/')
 subdir('bmatest/')
 subdir('bmetest/')
 subdir('dual-core/')
diff --git a/meson.build b/meson.build
index e01f491ce24c50d83d478eacf28cde0f3dc18daa..0cb5a984fa08e582ef5ae817d5bac343a04e7213 100644
--- a/meson.build
+++ b/meson.build
@@ -27,5 +27,7 @@ add_global_link_arguments(
   language: 'c',
 )
 
+python3 = import('python').find_installation('python3')
+
 subdir('lib/')
 subdir('hw-tests/')