diff --git a/.gitignore b/.gitignore
index cf65316863..5ffcb9a091 100644
--- a/.gitignore
+++ b/.gitignore
@@ -134,3 +134,4 @@ trace-dtrace-root.h
 trace-dtrace-root.dtrace
 trace-ust-all.h
 trace-ust-all.c
+!/instrument/*
diff --git a/Makefile b/Makefile
index c3d9a4bcd9..646fe2f327 100644
--- a/Makefile
+++ b/Makefile
@@ -602,6 +602,9 @@ endif
 ifdef CONFIG_INSTRUMENT
 	$(INSTALL_DIR) "$(DESTDIR)$(includedir)/qemu-instr/"
 	$(INSTALL_DATA) $(SRC_PATH)/instrument/qemu-instr/control.h "$(DESTDIR)$(includedir)/qemu-instr/"
+	$(INSTALL_DATA) $(SRC_PATH)/instrument/qemu-instr/trace.h "$(DESTDIR)$(includedir)/qemu-instr/"
+	$(INSTALL_DATA) $(SRC_PATH)/instrument/qemu-instr/types.h "$(DESTDIR)$(includedir)/qemu-instr/"
+	$(INSTALL_DATA) $(SRC_PATH)/instrument/qemu-instr/types.inc.h "$(DESTDIR)$(includedir)/qemu-instr/"
 endif
 
 install-datadir:
diff --git a/instrument/Makefile.objs b/instrument/Makefile.objs
index ec76b2080b..d7e6c760c3 100644
--- a/instrument/Makefile.objs
+++ b/instrument/Makefile.objs
@@ -4,3 +4,4 @@ target-obj-$(CONFIG_INSTRUMENT) += cmdline.o
 target-obj-$(CONFIG_INSTRUMENT) += load.o
 target-obj-$(CONFIG_INSTRUMENT) += qmp.o
 target-obj-$(CONFIG_INSTRUMENT) += control.o
+target-obj-$(CONFIG_INSTRUMENT) += trace.o
diff --git a/instrument/error.h b/instrument/error.h
index f8d1dd4b16..7a51d62fdb 100644
--- a/instrument/error.h
+++ b/instrument/error.h
@@ -25,4 +25,10 @@
         return;                      \
     }
 
+#define ERROR_IF_RET(cond, ret, msg, args...)   \
+    if (unlikely(cond)) {                       \
+        _ERROR(msg, ##args);                    \
+        return ret;                             \
+    }                                           \
+
 #endif  /* INSTRUMENT_ERROR_H */
diff --git a/instrument/qemu-instr/types.h b/instrument/qemu-instr/types.h
new file mode 100644
index 0000000000..ea3a032b4f
--- /dev/null
+++ b/instrument/qemu-instr/types.h
@@ -0,0 +1,51 @@
+/*
+ * QEMU-specific types for instrumentation clients.
+ *
+ * Copyright (C) 2012-2017 Lluís Vilanova <vilanova@ac.upc.edu>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef QI__TYPES_H
+#define QI__TYPES_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * SECTION: types
+ * @section_id: qi-types
+ * @title: Common types
+ */
+
+/**
+ * QITraceEvent:
+ *
+ * Opaque structure defining a tracing event.
+ */
+typedef struct QITraceEvent QITraceEvent;
+
+/**
+ * QITraceEventIter:
+ *
+ * Opaque structure defining a tracing event iterator.
+ */
+typedef struct QITraceEventIter QITraceEventIter;
+
+/**
+ * QICPU:
+ *
+ * Opaque guest CPU pointer.
+ */
+typedef struct QICPU_d *QICPU;
+
+
+#include <qemu-instr/types.inc.h>
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  /* QI__TYPES_H */
diff --git a/instrument/qemu-instr/types.inc.h b/instrument/qemu-instr/types.inc.h
new file mode 100644
index 0000000000..0d99ea59a2
--- /dev/null
+++ b/instrument/qemu-instr/types.inc.h
@@ -0,0 +1,15 @@
+/*
+ * QEMU-specific types for instrumentation clients.
+ *
+ * Copyright (C) 2012-2017 Lluís Vilanova <vilanova@ac.upc.edu>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include <stdlib.h>
+
+
+struct QITraceEventIter {
+    char buffer[(sizeof(size_t) * 2) + sizeof(char *)];
+};
diff --git a/instrument/trace.c b/instrument/trace.c
new file mode 100644
index 0000000000..6a437039b4
--- /dev/null
+++ b/instrument/trace.c
@@ -0,0 +1,125 @@
+/*
+ * API for QEMU's tracing events.
+ *
+ * Copyright (C) 2012-2017 Lluís Vilanova <vilanova@ac.upc.edu>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "instrument/error.h"
+#include "qemu/compiler.h"
+#include "qemu-instr/trace.h"
+#include "trace/control.h"
+
+
+SYM_PUBLIC
+QITraceEvent *qi_trace_event_name(const char *name)
+{
+    ERROR_IF_RET(!name, NULL, "must provide a name");
+    return (QITraceEvent *)trace_event_name(name);
+}
+
+SYM_PUBLIC
+void qi_trace_event_iter_init(QITraceEventIter *iter, const char *pattern)
+{
+    TraceEventIter *iter_ = (TraceEventIter *)iter;
+    ERROR_IF(!iter_, "must provide an iterator");
+    trace_event_iter_init(iter_, pattern);
+}
+
+SYM_PUBLIC
+QITraceEvent *qi_trace_event_iter_next(QITraceEventIter *iter)
+{
+    TraceEventIter *iter_ = (TraceEventIter *)iter;
+    ERROR_IF_RET(!iter_, NULL, "must provide an iterator");
+    return (QITraceEvent *)trace_event_iter_next(iter_);
+}
+
+
+SYM_PUBLIC
+bool qi_trace_event_is_vcpu(QITraceEvent *ev)
+{
+    TraceEvent *ev_ = (TraceEvent *)ev;
+    ERROR_IF_RET(!ev_, false, "must provide an event");
+    return trace_event_is_vcpu(ev_);
+}
+
+SYM_PUBLIC
+const char *qi_trace_event_get_name(QITraceEvent *ev)
+{
+    TraceEvent *ev_ = (TraceEvent *)ev;
+    ERROR_IF_RET(!ev_, false, "must provide an event");
+    return trace_event_get_name(ev_);
+}
+
+
+SYM_PUBLIC
+bool qi_trace_event_get_state(QITraceEvent *ev)
+{
+    TraceEvent *ev_ = (TraceEvent *)ev;
+    ERROR_IF_RET(!ev_, false, "must provide an event");
+    return trace_event_get_state_static(ev_) &&
+        trace_event_get_state_dynamic(ev_);
+}
+
+SYM_PUBLIC
+bool qi_trace_event_get_vcpu_state(QICPU *vcpu, QITraceEvent *ev)
+{
+    CPUState *vcpu_ = (CPUState *)vcpu;
+    TraceEvent *ev_ = (TraceEvent *)ev;
+    ERROR_IF_RET(!vcpu_, false, "must provide a vCPU");
+    ERROR_IF_RET(!ev_, false, "must provide an event");
+    return trace_event_get_state_static(ev_) &&
+        trace_event_get_vcpu_state_dynamic(vcpu_, ev_);
+}
+
+SYM_PUBLIC
+bool qi_trace_event_get_state_static(QITraceEvent *ev)
+{
+    TraceEvent *ev_ = (TraceEvent *)ev;
+    ERROR_IF_RET(!ev_, false, "must provide an event");
+    return trace_event_get_state_static(ev_);
+}
+
+SYM_PUBLIC
+bool qi_trace_event_get_state_dynamic(QITraceEvent *ev)
+{
+    TraceEvent *ev_ = (TraceEvent *)ev;
+    ERROR_IF_RET(!ev_, false, "must provide an event");
+    return trace_event_get_state_dynamic(ev_);
+}
+
+SYM_PUBLIC
+bool qi_trace_event_get_vcpu_state_dynamic(QICPU *vcpu, QITraceEvent *ev)
+{
+    CPUState *vcpu_ = (CPUState *)vcpu;
+    TraceEvent *ev_ = (TraceEvent *)ev;
+    ERROR_IF_RET(!vcpu_, false, "must provide a vCPU");
+    ERROR_IF_RET(!ev_, false, "must provide an event");
+    return trace_event_get_vcpu_state_dynamic(vcpu_, ev_);
+}
+
+SYM_PUBLIC
+void qi_trace_event_set_state_dynamic(QITraceEvent *ev, bool state)
+{
+    TraceEvent *ev_ = (TraceEvent *)ev;
+    ERROR_IF(!ev_, "must provide an event");
+    ERROR_IF(!trace_event_get_state_static(ev_),
+             "event must be statically enabled");
+    trace_event_set_state_dynamic(ev_, state);
+}
+
+SYM_PUBLIC
+void qi_trace_event_set_vcpu_state_dynamic(QICPU *vcpu,
+                                           QITraceEvent *ev, bool state)
+{
+    CPUState *vcpu_ = (CPUState *)vcpu;
+    TraceEvent *ev_ = (TraceEvent *)ev;
+    ERROR_IF(!vcpu_, "must provide a vCPU");
+    ERROR_IF(!ev_, "must provide an event");
+    ERROR_IF(!trace_event_get_state_static(ev_),
+             "event must be statically enabled");
+    trace_event_set_vcpu_state_dynamic(vcpu_, ev_, state);
+}
diff --git a/trace/control.h b/trace/control.h
index 1903e22975..3e6da24c98 100644
--- a/trace/control.h
+++ b/trace/control.h
@@ -13,6 +13,7 @@
 #include "qemu-common.h"
 #include "event-internal.h"
 
+/* NOTE: Keep in sync with size of QITraceEventIter */
 typedef struct TraceEventIter {
     size_t event;
     size_t group;
