diff --git a/cli.js b/cli.js
index d72109484e..0ffdcad34a 100755
--- a/cli.js
+++ b/cli.js
@@ -23,6 +23,7 @@ var updateNotifier = require('update-notifier');
 var chalk = require('chalk');
 var Promise = require('bluebird');
 var log = require('./lib/logger');
+var tap = require('./lib/tap');
 var Api = require('./api');
 
 // Bluebird specific
@@ -36,7 +37,8 @@ var cli = meow([
 	'  --init       Add AVA to your project',
 	'  --fail-fast  Stop after first test failure',
 	'  --serial     Run tests serially',
-	'  --require    Module to preload (Can be repeated)',
+	'  --require    Module to preload (Can be repeated)',,
+	'  --tap        Generate TAP output',
 	'',
 	'Examples',
 	'  ava',
@@ -54,7 +56,8 @@ var cli = meow([
 	],
 	boolean: [
 		'fail-fast',
-		'serial'
+		'serial',
+		'tap'
 	]
 });
 
@@ -65,7 +68,11 @@ if (cli.flags.init) {
 	return;
 }
 
-log.write();
+if (cli.flags.tap) {
+	console.log(tap.start());
+} else {
+	log.write();
+}
 
 var api = new Api(cli.input, {
 	failFast: cli.flags.failFast,
@@ -74,6 +81,11 @@ var api = new Api(cli.input, {
 });
 
 api.on('test', function (test) {
+	if (cli.flags.tap) {
+		console.log(tap.test(test));
+		return;
+	}
+
 	if (test.error) {
 		log.error(test.title, chalk.red(test.error.message));
 	} else {
@@ -87,17 +99,26 @@ api.on('test', function (test) {
 });
 
 api.on('error', function (data) {
+	if (cli.flags.tap) {
+		console.log(tap.unhandledError(data));
+		return;
+	}
+
 	log.unhandledError(data.type, data.file, data);
 });
 
 api.run()
 	.then(function () {
-		log.write();
-		log.report(api.passCount, api.failCount, api.rejectionCount, api.exceptionCount);
-		log.write();
-
-		if (api.failCount > 0) {
-			log.errors(api.errors);
+		if (cli.flags.tap) {
+			console.log(tap.finish(api.passCount, api.failCount, api.rejectionCount, api.exceptionCount));
+		} else {
+			log.write();
+			log.report(api.passCount, api.failCount, api.rejectionCount, api.exceptionCount);
+			log.write();
+
+			if (api.failCount > 0) {
+				log.errors(api.errors);
+			}
 		}
 
 		process.stdout.write('');
diff --git a/lib/tap.js b/lib/tap.js
new file mode 100644
index 0000000000..c4de30b8d9
--- /dev/null
+++ b/lib/tap.js
@@ -0,0 +1,67 @@
+'use strict';
+var format = require('util').format;
+
+// Parses stack trace and extracts original function name, file name and line.
+function getSourceFromStack(stack, index) {
+	return stack
+		.split('\n')
+		.slice(index, index + 1)
+		.join('')
+		.replace(/^\s+at /, '');
+}
+
+exports.start = function () {
+	return 'TAP version 13';
+};
+
+var i = 0;
+
+exports.test = function (test) {
+	var output;
+
+	if (test.error) {
+		output = [
+			'# ' + test.title,
+			format('not ok %d - %s', ++i, test.error.message),
+			'  ---',
+			'    operator: ' + test.error.operator,
+			'    expected: ' + test.error.expected,
+			'    actual: ' + test.error.actual,
+			'    at: ' + getSourceFromStack(test.error.stack, 3),
+			'  ...'
+		];
+	} else {
+		output = [
+			'# ' + test.title,
+			format('ok %d - %s', ++i, test.title)
+		];
+	}
+
+	return output.join('\n');
+};
+
+exports.unhandledError = function (err) {
+	var output = [
+		'# ' + err.message,
+		format('not ok %d - %s', ++i, err.message),
+		'  ---',
+		'    name: ' + err.name,
+		'    at: ' + getSourceFromStack(err.stack, 1),
+		'  ...'
+	];
+
+	return output.join('\n');
+};
+
+exports.finish = function (passCount, failCount, rejectionCount, exceptionCount) {
+	var output = [
+		'',
+		'1..' + (passCount + failCount),
+		'# tests ' + (passCount + failCount),
+		'# pass ' + passCount,
+		'# fail ' + (failCount + rejectionCount + exceptionCount),
+		''
+	];
+
+	return output.join('\n');
+};
diff --git a/media/tap-output.png b/media/tap-output.png
new file mode 100644
index 0000000000..deaa7875dd
Binary files /dev/null and b/media/tap-output.png differ
diff --git a/package.json b/package.json
index 5f7163ba58..b89a850483 100644
--- a/package.json
+++ b/package.json
@@ -74,7 +74,8 @@
     "generators",
     "yield",
     "observable",
-    "observables"
+    "observables",
+		"tap"
   ],
   "dependencies": {
     "arr-flatten": "^1.0.1",
diff --git a/readme.md b/readme.md
index 2b33c37377..c54dd20a01 100644
--- a/readme.md
+++ b/readme.md
@@ -33,6 +33,7 @@ Even though JavaScript is single-threaded, IO in Node.js can happen in parallel
 - [Async function support](#async-function-support)
 - [Observable support](#observable-support)
 - [Enhanced asserts](#enhanced-asserts)
+- [Optional TAP output](#optional-tap-output)
 
 
 ## Test syntax
@@ -105,6 +106,7 @@ $ ava --help
     --fail-fast  Stop after first test failure
     --serial     Run tests serially
     --require    Module to preload (Can be repeated)
+    --tap        Generate TAP output
 
   Examples
     ava
@@ -456,6 +458,16 @@ test.cb(t => {
 });
 ```
 
+### Optional TAP output
+
+AVA can generate TAP output via `--tap` option for use with any [TAP reporter](https://github.com/sindresorhus/awesome-tap#reporters).
+
+```
+$ ava --tap | tap-nyan
+```
+
+<img src="media/tap-output.png" width="398">
+
 
 ## API
 
diff --git a/test/tap.js b/test/tap.js
new file mode 100644
index 0000000000..577bc981f6
--- /dev/null
+++ b/test/tap.js
@@ -0,0 +1,89 @@
+'use strict';
+var test = require('tap').test;
+var tap = require('../lib/tap');
+
+test('start', function (t) {
+	t.is(tap.start(), 'TAP version 13');
+	t.end();
+});
+
+test('passing test', function (t) {
+	var actualOutput = tap.test({
+		title: 'passing'
+	});
+
+	var expectedOutput = [
+		'# passing',
+		'ok 1 - passing'
+	].join('\n');
+
+	t.is(actualOutput, expectedOutput);
+	t.end();
+});
+
+test('failing test', function (t) {
+	var actualOutput = tap.test({
+		title: 'failing',
+		error: {
+			message: 'false == true',
+			operator: '==',
+			expected: true,
+			actual: false,
+			stack: ['', '', '', ' at Test.fn (test.js:1:2)'].join('\n')
+		}
+	});
+
+	var expectedOutput = [
+		'# failing',
+		'not ok 2 - false == true',
+		'  ---',
+		'    operator: ==',
+		'    expected: true',
+		'    actual: false',
+		'    at: Test.fn (test.js:1:2)',
+		'  ...'
+	].join('\n');
+
+	t.is(actualOutput, expectedOutput);
+	t.end();
+});
+
+test('unhandled error', function (t) {
+	var actualOutput = tap.unhandledError({
+		message: 'unhandled',
+		name: 'TypeError',
+		stack: ['', ' at Test.fn (test.js:1:2)'].join('\n')
+	});
+
+	var expectedOutput = [
+		'# unhandled',
+		'not ok 3 - unhandled',
+		'  ---',
+		'    name: TypeError',
+		'    at: Test.fn (test.js:1:2)',
+		'  ...'
+	].join('\n');
+
+	t.is(actualOutput, expectedOutput);
+	t.end();
+});
+
+test('results', function (t) {
+	var passCount = 1;
+	var failCount = 2;
+	var rejectionCount = 3;
+	var exceptionCount = 4;
+
+	var actualOutput = tap.finish(passCount, failCount, rejectionCount, exceptionCount);
+	var expectedOutput = [
+		'',
+		'1..' + (passCount + failCount),
+		'# tests ' + (passCount + failCount),
+		'# pass ' + passCount,
+		'# fail ' + (failCount + rejectionCount + exceptionCount),
+		''
+	].join('\n');
+
+	t.is(actualOutput, expectedOutput);
+	t.end();
+});