diff --git a/docs/source/api-flows.rst b/docs/source/api-flows.rst
index 7c57be9503..47676aefa3 100644
--- a/docs/source/api-flows.rst
+++ b/docs/source/api-flows.rst
@@ -9,9 +9,13 @@ API: Flows
 
 .. note:: Before reading this page, you should be familiar with the key concepts of :doc:`key-concepts-flows`.
 
+.. contents::
+
 An example flow
 ---------------
-Let's imagine a flow for agreeing a basic ledger update between Alice and Bob. This flow will have two sides:
+Before we discuss the API offered by the flow, let's consider what a standard flow may look like.
+
+Imagine a flow for agreeing a basic ledger update between Alice and Bob. This flow will have two sides:
 
 * An ``Initiator`` side, that will initiate the request to update the ledger
 * A ``Responder`` side, that will respond to the request to update the ledger
@@ -76,23 +80,42 @@ To respond to these actions, the responder takes the following steps:
 
 FlowLogic
 ---------
-In practice, a flow is implemented as one or more communicating ``FlowLogic`` subclasses. Each ``FlowLogic`` subclass
-must override ``FlowLogic.call()``, which describes the actions it will take as part of the flow.
+In practice, a flow is implemented as one or more communicating ``FlowLogic`` subclasses. The ``FlowLogic``
+subclass's constructor can take any number of arguments of any type. The generic of ``FlowLogic`` (e.g.
+``FlowLogic<SignedTransaction>``) indicates the flow's return type.
 
-So in the example above, we would have an ``Initiator`` ``FlowLogic`` subclass and a ``Responder`` ``FlowLogic``
-subclass. The actions of the initiator's side of the flow would be defined in ``Initiator.call``, and the actions
-of the responder's side of the flow would be defined in ``Responder.call``.
+.. container:: codeset
+
+   .. sourcecode:: kotlin
+
+        class Initiator(val arg1: Boolean,
+                        val arg2: Int,
+                        val counterparty: Party): FlowLogic<SignedTransaction>() { }
+
+        class Responder(val otherParty: Party) : FlowLogic<Unit>() { }
+
+   .. sourcecode:: java
+
+        public static class Initiator extends FlowLogic<SignedTransaction> {
+            private final boolean arg1;
+            private final int arg2;
+            private final Party counterparty;
+
+            public Initiator(boolean arg1, int arg2, Party counterparty) {
+                this.arg1 = arg1;
+                this.arg2 = arg2;
+                this.counterparty = counterparty;
+            }
+
+        }
+
+        public static class Responder extends FlowLogic<Void> { }
 
 FlowLogic annotations
-^^^^^^^^^^^^^^^^^^^^^
+---------------------
 Any flow that you wish to start either directly via RPC or as a subflow must be annotated with the
 ``@InitiatingFlow`` annotation. Additionally, if you wish to start the flow via RPC, you must annotate it with the
-``@StartableByRPC`` annotation.
-
-Any flow that responds to a message from another flow must be annotated with the ``@InitiatedBy`` annotation.
-``@InitiatedBy`` takes the class of the flow it is responding to as its single parameter.
-
-So in our example, we would have:
+``@StartableByRPC`` annotation:
 
 .. container:: codeset
 
@@ -100,74 +123,145 @@ So in our example, we would have:
 
         @InitiatingFlow
         @StartableByRPC
-        class Initiator(): FlowLogic<Unit>() {
-
-        ...
-
-        @InitiatedBy(Initiator::class)
-        class Responder(val otherParty: Party) : FlowLogic<Unit>() {
+        class Initiator(): FlowLogic<Unit>() { }
 
    .. sourcecode:: java
 
         @InitiatingFlow
         @StartableByRPC
-        public static class Initiator extends FlowLogic<Unit> {
+        public static class Initiator extends FlowLogic<Unit> { }
 
-        ...
+Meanwhile, any flow that responds to a message from another flow must be annotated with the ``@InitiatedBy`` annotation.
+``@InitiatedBy`` takes the class of the flow it is responding to as its single parameter:
+
+.. container:: codeset
+
+   .. sourcecode:: kotlin
+
+        @InitiatedBy(Initiator::class)
+        class Responder(val otherParty: Party) : FlowLogic<Unit>() { }
+
+   .. sourcecode:: java
 
         @InitiatedBy(Initiator.class)
-        public static class Responder extends FlowLogic<Void> {
+        public static class Responder extends FlowLogic<Void> { }
 
 Additionally, any flow that is started by a ``SchedulableState`` must be annotated with the ``@SchedulableFlow``
 annotation.
 
-ServiceHub
-----------
-Within ``FlowLogic.call``, the flow developer has access to the node's ``ServiceHub``, which provides access to the
-various services the node provides. See :doc:`api-service-hub` for information about the services the ``ServiceHub``
-offers.
+Call
+----
+Each ``FlowLogic`` subclass must override ``FlowLogic.call()``, which describes the actions it will take as part of
+the flow. For example, the actions of the initiator's side of the flow would be defined in ``Initiator.call``, and the
+actions of the responder's side of the flow would be defined in ``Responder.call``.
 
-Some common tasks performed using the ``ServiceHub`` are:
-
-* Looking up your own identity or the identity of a counterparty using the ``networkMapCache``
-* Identifying the providers of a given service (e.g. a notary service) using the ``networkMapCache``
-* Retrieving states to use in a transaction using the ``vaultService``
-* Retrieving attachments and past transactions to use in a transaction using the ``storageService``
-* Creating a timestamp using the ``clock``
-* Signing a transaction using the ``keyManagementService``
-
-Common flow tasks
------------------
-There are a number of common tasks that you will need to perform within ``FlowLogic.call`` in order to agree ledger
-updates. This section details the API for the most common tasks.
-
-Retrieving information about other nodes
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-We use the network map to retrieve information about other nodes on the network:
+In order for nodes to be able to run multiple flows concurrently, and to allow flows to survive node upgrades and
+restarts, flows need to be checkpointable and serializable to disk. This is achieved by marking ``FlowLogic.call()``,
+as well as any function invoked from within ``FlowLogic.call()``, with an ``@Suspendable`` annotation.
 
 .. container:: codeset
 
    .. sourcecode:: kotlin
 
-        val networkMap = serviceHub.networkMapCache
-
-        val allNodes = networkMap.partyNodes
-        val allNotaryNodes = networkMap.notaryNodes
-        val randomNotaryNode = networkMap.getAnyNotary()
-
-        val alice = networkMap.getNodeByLegalName(X500Name("CN=Alice,O=Alice,L=London,C=GB"))
-        val bob = networkMap.getNodeByLegalIdentityKey(bobsKey)
+        class Initiator(val counterparty: Party): FlowLogic<Unit>() {
+            @Suspendable
+            override fun call() { }
+        }
 
    .. sourcecode:: java
 
-        final NetworkMapCache networkMap = getServiceHub().getNetworkMapCache();
+        public static class InitiatorFlow extends FlowLogic<Void> {
+            private final Party counterparty;
 
-        final List<NodeInfo> allNodes = networkMap.getPartyNodes();
-        final List<NodeInfo> allNotaryNodes = networkMap.getNotaryNodes();
-        final Party randomNotaryNode = networkMap.getAnyNotary(null);
+            public Initiator(Party counterparty) {
+                this.counterparty = counterparty;
+            }
 
-        final NodeInfo alice = networkMap.getNodeByLegalName(new X500Name("CN=Alice,O=Alice,L=London,C=GB"));
-        final NodeInfo bob = networkMap.getNodeByLegalIdentityKey(bobsKey);
+            @Suspendable
+            @Override
+            public Void call() throws FlowException { }
+
+        }
+
+ServiceHub
+----------
+Within ``FlowLogic.call``, the flow developer has access to the node's ``ServiceHub``, which provides access to the
+various services the node provides. We will use the ``ServiceHub`` extensively in the examples that follow. You can
+also see :doc:`api-service-hub` for information about the services the ``ServiceHub`` offers.
+
+Common flow tasks
+-----------------
+There are a number of common tasks that you will need to perform within ``FlowLogic.call`` in order to agree ledger
+updates. This section details the API for common tasks.
+
+Transaction building
+^^^^^^^^^^^^^^^^^^^^
+The majority of the work performed during a flow will be to build, verify and sign a transaction. We cover this in
+:doc:`api-transactions`.
+
+Retrieving information about other nodes
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+We can retrieve information about other nodes on the network and the services they offer using
+``ServiceHub.networkMapCache``.
+
+Notaries
+~~~~~~~~
+Remember that a transaction generally needs a notary to:
+
+* Prevent double-spends if the transaction has inputs
+* Serve as a timestamping authority if the transaction has a time-window
+
+There are several ways to retrieve a notary from the network map:
+
+.. container:: codeset
+
+    .. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
+        :language: kotlin
+        :start-after: DOCSTART 1
+        :end-before: DOCEND 1
+        :dedent: 12
+
+    .. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
+        :language: java
+        :start-after: DOCSTART 1
+        :end-before: DOCEND 1
+        :dedent: 12
+
+Specific counterparties
+~~~~~~~~~~~~~~~~~~~~~~~
+We can also use the network map to retrieve a specific counterparty:
+
+.. container:: codeset
+
+    .. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
+        :language: kotlin
+        :start-after: DOCSTART 2
+        :end-before: DOCEND 2
+        :dedent: 12
+
+    .. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
+        :language: java
+        :start-after: DOCSTART 2
+        :end-before: DOCEND 2
+        :dedent: 12
+
+Specific services
+~~~~~~~~~~~~~~~~~
+Finally, we can use the map to identify nodes providing a specific service (e.g. a regulator or an oracle):
+
+.. container:: codeset
+
+    .. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
+        :language: kotlin
+        :start-after: DOCSTART 3
+        :end-before: DOCEND 3
+        :dedent: 12
+
+    .. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
+        :language: java
+        :start-after: DOCSTART 3
+        :end-before: DOCEND 3
+        :dedent: 12
 
 Communication between parties
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -180,50 +274,121 @@ Communication between parties
 * ``sendAndReceive(receiveType: Class<R>, otherParty: Party, payload: Any)``
     * Sends the ``payload`` object to the ``otherParty``, and receives an object of type ``receiveType`` back
 
-Each ``FlowLogic`` subclass can be annotated to respond to messages from a given *counterparty* flow using the
-``@InitiatedBy`` annotation. When a node first receives a message from a given ``FlowLogic.call()`` invocation, it
-responds as follows:
-
-* The node checks whether they have a ``FlowLogic`` subclass that is registered to respond to the ``FlowLogic`` that
-  is sending the message:
-
-    a. If yes, the node starts an instance of this ``FlowLogic`` by invoking ``FlowLogic.call()``
-    b. Otherwise, the node ignores the message
-
-* The counterparty steps through their ``FlowLogic.call()`` method until they encounter a call to ``receive()``, at
-  which point they process the message from the initiator
-
-Upon calling ``receive()``/``sendAndReceive()``, the ``FlowLogic`` is suspended until it receives a response.
-
-UntrustworthyData
-~~~~~~~~~~~~~~~~~
-``send()`` and ``sendAndReceive()`` return a payload wrapped in an ``UntrustworthyData`` instance. This is a
-reminder that any data received off the wire is untrustworthy and must be verified.
-
-We verify the ``UntrustworthyData`` and retrieve its payload by calling ``unwrap``:
+Send
+~~~~
+We can send arbitrary data to a counterparty:
 
 .. container:: codeset
 
-   .. sourcecode:: kotlin
+    .. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
+        :language: kotlin
+        :start-after: DOCSTART 4
+        :end-before: DOCEND 4
+        :dedent: 12
 
-        val partSignedTx = receive<SignedTransaction>(otherParty).unwrap { partSignedTx ->
-                val wireTx = partSignedTx.verifySignatures(keyPair.public, notaryPubKey)
-                wireTx.toLedgerTransaction(serviceHub).verify()
-                partSignedTx
-            }
+    .. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
+        :language: java
+        :start-after: DOCSTART 4
+        :end-before: DOCEND 4
+        :dedent: 12
 
-   .. sourcecode:: java
+If this is the first ``send``, the counterparty will either:
 
-        final SignedTransaction partSignedTx = receive(SignedTransaction.class, otherParty)
-            .unwrap(tx -> {
-                try {
-                    final WireTransaction wireTx = tx.verifySignatures(keyPair.getPublic(), notaryPubKey);
-                    wireTx.toLedgerTransaction(getServiceHub()).verify();
-                } catch (SignatureException ex) {
-                    throw new FlowException(tx.getId() + " failed signature checks", ex);
-                }
-                return tx;
-            });
+1. Ignore the message if they are not registered to respond to messages from this flow.
+2. Start the flow they have registered to respond to this flow, and run the flow until the first call to ``receive``,
+   at which point they process the message. In other words, we are assuming that the counterparty is registered to
+   respond to this flow, and has a corresponding ``receive`` call.
+
+Receive
+~~~~~~~
+We can also wait to receive arbitrary data of a specific type from a counterparty. Again, this implies a corresponding
+``send`` call in the counterparty's flow. A few scenarios:
+
+* We never receive a message back. In the current design, the flow is paused until the node's owner kills the flow.
+* Instead of sending a message back, the counterparty throws a ``FlowException``. This exception is propagated back
+  to us, and we can use the error message to establish what happened.
+* We receive a message back, but it's of the wrong type. In this case, a ``FlowException`` is thrown.
+* We receive back a message of the correct type. All is good.
+
+Upon calling ``receive`` (or ``sendAndReceive``), the ``FlowLogic`` is suspended until it receives a response.
+
+We receive the data wrapped in an ``UntrustworthyData`` instance. This is a reminder that the data we receive may not
+be what it appears to be! We must unwrap the ``UntrustworthyData`` using a lambda:
+
+.. container:: codeset
+
+    .. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
+        :language: kotlin
+        :start-after: DOCSTART 5
+        :end-before: DOCEND 5
+        :dedent: 12
+
+    .. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
+        :language: java
+        :start-after: DOCSTART 5
+        :end-before: DOCEND 5
+        :dedent: 12
+
+We're not limited to sending to and receiving from a single counterparty. A flow can send messages to as many parties
+as it likes, and each party can invoke a different response flow:
+
+.. container:: codeset
+
+    .. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
+        :language: kotlin
+        :start-after: DOCSTART 6
+        :end-before: DOCEND 6
+        :dedent: 12
+
+    .. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
+        :language: java
+        :start-after: DOCSTART 6
+        :end-before: DOCEND 6
+        :dedent: 12
+
+SendAndReceive
+~~~~~~~~~~~~~~
+We can also use a single call to send data to a counterparty and wait to receive data of a specific type back. The
+type of data sent doesn't need to match the type of the data received back:
+
+.. container:: codeset
+
+    .. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
+        :language: kotlin
+        :start-after: DOCSTART 7
+        :end-before: DOCEND 7
+        :dedent: 12
+
+    .. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
+        :language: java
+        :start-after: DOCSTART 7
+        :end-before: DOCEND 7
+        :dedent: 12
+
+Counterparty response
+~~~~~~~~~~~~~~~~~~~~~
+Suppose we're now on the ``Responder`` side of the flow. We just received the following series of messages from the
+``Initiator``:
+
+1. They sent us an ``Any`` instance
+2. They waited to receive an ``Integer`` instance back
+3. They sent a ``String`` instance and waited to receive a ``Boolean`` instance back
+
+Our side of the flow must mirror these calls. We could do this as follows:
+
+.. container:: codeset
+
+    .. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
+        :language: kotlin
+        :start-after: DOCSTART 8
+        :end-before: DOCEND 8
+        :dedent: 12
+
+    .. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
+        :language: java
+        :start-after: DOCSTART 8
+        :end-before: DOCEND 8
+        :dedent: 12
 
 Subflows
 --------
@@ -236,27 +401,118 @@ Corda provides a number of built-in flows that should be used for handling commo
 * ``NotaryChangeFlow``, which should be used to change a state's notary
 
 These flows are designed to be used as building blocks in your own flows. You invoke them by calling
-``FlowLogic.subFlow`` from within your flow's ``call`` method. Here is an example from ``TwoPartyDealFlow.kt``:
+``FlowLogic.subFlow`` from within your flow's ``call`` method. Let's look at three very common examples.
+
+FinalityFlow
+^^^^^^^^^^^^
+``FinalityFlow`` allows us to notarise the transaction and get it recorded in the vault of the participants of all
+the transaction's states:
 
 .. container:: codeset
 
-    .. literalinclude:: ../../core/src/main/kotlin/net/corda/flows/TwoPartyDealFlow.kt
+    .. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
         :language: kotlin
-        :start-after: DOCSTART 1
-        :end-before: DOCEND 1
+        :start-after: DOCSTART 9
+        :end-before: DOCEND 9
         :dedent: 12
 
-In this example, we are starting a ``CollectSignaturesFlow``, passing in a partially signed transaction, and
-receiving back a fully-signed version of the same transaction.
+    .. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
+        :language: java
+        :start-after: DOCSTART 9
+        :end-before: DOCEND 9
+        :dedent: 12
 
-Subflows in our example flow
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-In practice, many of the actions in our example flow would be automated using subflows:
+We can also choose to send the transaction to additional parties who aren't one of the state's participants:
 
-* Parts 2-4 of ``Initiator.call`` should be automated by invoking ``CollectSignaturesFlow``
-* Part 5 of ``Initiator.call`` should be automated by invoking ``FinalityFlow``
-* Part 1 of ``Responder.call`` should be automated by invoking ``SignTransactionFlow``
-* Part 2 of ``Responder.call`` will be handled automatically when the counterparty invokes ``FinalityFlow``
+.. container:: codeset
+
+    .. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
+        :language: kotlin
+        :start-after: DOCSTART 10
+        :end-before: DOCEND 10
+        :dedent: 12
+
+    .. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
+        :language: java
+        :start-after: DOCSTART 10
+        :end-before: DOCEND 10
+        :dedent: 12
+
+Only one party has to call ``FinalityFlow`` for a given transaction to be recorded by all participants. It does
+**not** need to be called by each participant individually.
+
+CollectSignaturesFlow/SignTransactionFlow
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+The list of parties who need to sign a transaction is dictated by the transaction's commands. Once we've signed a
+transaction ourselves, we can automatically gather the signatures of the other required signers using
+``CollectSignaturesFlow``:
+
+.. container:: codeset
+
+    .. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
+        :language: kotlin
+        :start-after: DOCSTART 15
+        :end-before: DOCEND 15
+        :dedent: 12
+
+    .. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
+        :language: java
+        :start-after: DOCSTART 15
+        :end-before: DOCEND 15
+        :dedent: 12
+
+Each required signer will need to respond by invoking its own ``SignTransactionFlow`` subclass to check the
+transaction and provide their signature if they are satisfied:
+
+.. container:: codeset
+
+    .. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
+        :language: kotlin
+        :start-after: DOCSTART 16
+        :end-before: DOCEND 16
+        :dedent: 12
+
+    .. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
+        :language: java
+        :start-after: DOCSTART 16
+        :end-before: DOCEND 16
+        :dedent: 12
+
+ResolveTransactionsFlow
+^^^^^^^^^^^^^^^^^^^^^^^
+Verifying a transaction will also verify every transaction in the transaction's dependency chain. So if we receive a
+transaction from a counterparty and it has any dependencies, we'd need to download all of these dependencies
+using``ResolveTransactionsFlow`` before verifying it:
+
+.. container:: codeset
+
+    .. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
+        :language: kotlin
+        :start-after: DOCSTART 13
+        :end-before: DOCEND 13
+        :dedent: 12
+
+    .. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
+        :language: java
+        :start-after: DOCSTART 13
+        :end-before: DOCEND 13
+        :dedent: 12
+
+We can also resolve a `StateRef` dependency chain:
+
+.. container:: codeset
+
+    .. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
+        :language: kotlin
+        :start-after: DOCSTART 14
+        :end-before: DOCEND 14
+        :dedent: 12
+
+    .. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
+        :language: java
+        :start-after: DOCSTART 14
+        :end-before: DOCEND 14
+        :dedent: 12
 
 FlowException
 -------------
@@ -283,18 +539,38 @@ There are many scenarios in which throwing a ``FlowException`` would be appropri
 * The transaction does not match the parameters of the deal as discussed
 * You are reneging on a deal
 
-Suspending flows
-----------------
-In order for nodes to be able to run multiple flows concurrently, and to allow flows to survive node upgrades and
-restarts, flows need to be checkpointable and serializable to disk.
+ProgressTracker
+---------------
+We can give our flow a progress tracker. This allows us to see the flow's progress visually in our node's CRaSH shell.
 
-This is achieved by marking any function invoked from within ``FlowLogic.call()`` with an ``@Suspendable`` annotation.
-
-We can see an example in ``CollectSignaturesFlow``:
+To provide a progress tracker, we have to override ``FlowLogic.progressTracker`` in our flow:
 
 .. container:: codeset
 
-    .. literalinclude:: ../../core/src/main/kotlin/net/corda/flows/CollectSignaturesFlow.kt
+    .. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
         :language: kotlin
-        :start-after: DOCSTART 1
-        :end-before: DOCEND 1
\ No newline at end of file
+        :start-after: DOCSTART 17
+        :end-before: DOCEND 17
+        :dedent: 8
+
+    .. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
+        :language: java
+        :start-after: DOCSTART 17
+        :end-before: DOCEND 17
+        :dedent: 8
+
+We then update the progress tracker's current step as we progress through the flow as follows:
+
+.. container:: codeset
+
+    .. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
+        :language: kotlin
+        :start-after: DOCSTART 18
+        :end-before: DOCEND 18
+        :dedent: 12
+
+    .. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
+        :language: java
+        :start-after: DOCSTART 18
+        :end-before: DOCEND 18
+        :dedent: 12
\ No newline at end of file
diff --git a/docs/source/building-a-cordapp-index.rst b/docs/source/building-a-cordapp-index.rst
index d5a3137e7d..1e6cb3114d 100644
--- a/docs/source/building-a-cordapp-index.rst
+++ b/docs/source/building-a-cordapp-index.rst
@@ -7,4 +7,5 @@ Building a CorDapp
    cordapp-overview
    writing-cordapps
    api-index
+   flow-cookbook
    cheat-sheet
\ No newline at end of file
diff --git a/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java b/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
new file mode 100644
index 0000000000..5ac2bff1ce
--- /dev/null
+++ b/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
@@ -0,0 +1,506 @@
+package net.corda.docs;
+
+import co.paralleluniverse.fibers.Suspendable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import net.corda.contracts.asset.Cash;
+import net.corda.core.contracts.*;
+import net.corda.core.contracts.TransactionType.General;
+import net.corda.core.contracts.TransactionType.NotaryChange;
+import net.corda.core.crypto.SecureHash;
+import net.corda.core.flows.*;
+import net.corda.core.identity.Party;
+import net.corda.core.node.services.ServiceType;
+import net.corda.core.node.services.Vault;
+import net.corda.core.node.services.vault.QueryCriteria;
+import net.corda.core.transactions.LedgerTransaction;
+import net.corda.core.transactions.SignedTransaction;
+import net.corda.core.transactions.TransactionBuilder;
+import net.corda.core.transactions.WireTransaction;
+import net.corda.core.utilities.ProgressTracker;
+import net.corda.core.utilities.ProgressTracker.Step;
+import net.corda.core.utilities.UntrustworthyData;
+import net.corda.flows.CollectSignaturesFlow;
+import net.corda.flows.FinalityFlow;
+import net.corda.flows.ResolveTransactionsFlow;
+import net.corda.flows.SignTransactionFlow;
+import org.bouncycastle.asn1.x500.X500Name;
+
+import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria;
+import rx.Observable;
+
+import java.security.PublicKey;
+import java.time.Instant;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static net.corda.core.contracts.ContractsDSL.requireThat;
+import static net.corda.core.utilities.TestConstants.getDUMMY_PUBKEY_1;
+
+// We group our two flows inside a singleton object to indicate that they work
+// together.
+public class FlowCookbookJava {
+    // ``InitiatorFlow`` is our first flow, and will communicate with
+    // ``ResponderFlow``, below.
+    // We mark ``InitiatorFlow`` as an ``InitiatingFlow``, allowing it to be
+    // started directly by the node.
+    @InitiatingFlow
+    // We also mark ``InitiatorFlow`` as ``StartableByRPC``, allowing the
+    // node's owner to start the flow via RPC.
+    @StartableByRPC
+    // Every flow must subclass ``FlowLogic``. The generic indicates the
+    // flow's return type.
+    public static class InitiatorFlow extends FlowLogic<Void> {
+
+        private final boolean arg1;
+        private final int arg2;
+        private final Party counterparty;
+
+        public InitiatorFlow(boolean arg1, int arg2, Party counterparty) {
+            this.arg1 = arg1;
+            this.arg2 = arg2;
+            this.counterparty = counterparty;
+        }
+
+        /*----------------------------------
+         * WIRING UP THE PROGRESS TRACKER *
+        ----------------------------------*/
+        // Giving our flow a progress tracker allows us to see the flow's
+        // progress visually in our node's CRaSH shell.
+        // DOCSTART 17
+        private static final Step ID_OTHER_NODES = new Step("Identifying other nodes on the network.");
+        private static final Step SENDING_AND_RECEIVING_DATA = new Step("Sending data between parties.");
+        private static final Step EXTRACTING_VAULT_STATES = new Step("Extracting states from the vault.");
+        private static final Step OTHER_TX_COMPONENTS = new Step("Gathering a transaction's other components.");
+        private static final Step TX_BUILDING = new Step("Building a transaction.");
+        private static final Step TX_SIGNING = new Step("Signing a transaction.");
+        private static final Step TX_VERIFICATION = new Step("Verifying a transaction.");
+        private static final Step SIGS_GATHERING = new Step("Gathering a transaction's signatures.") {
+            // Wiring up a child progress tracker allows us to see the
+            // subflow's progress steps in our flow's progress tracker.
+            @Override public ProgressTracker childProgressTracker() {
+                return CollectSignaturesFlow.Companion.tracker();
+            }
+        };
+        private static final Step FINALISATION = new Step("Finalising a transaction.") {
+            @Override public ProgressTracker childProgressTracker() {
+                return FinalityFlow.Companion.tracker();
+            }
+        };
+
+        private final ProgressTracker progressTracker = new ProgressTracker(
+                ID_OTHER_NODES,
+                SENDING_AND_RECEIVING_DATA,
+                EXTRACTING_VAULT_STATES,
+                OTHER_TX_COMPONENTS,
+                TX_BUILDING,
+                TX_SIGNING,
+                TX_VERIFICATION,
+                SIGS_GATHERING,
+                FINALISATION
+        );
+        // DOCEND 17
+
+        @Suspendable
+        @Override
+        public Void call() throws FlowException {
+            // We'll be using a dummy public key for demonstration purposes.
+            // These are built in to Corda, and are generally used for writing
+            // tests.
+            PublicKey dummyPubKey = getDUMMY_PUBKEY_1();
+
+            /*---------------------------
+             * IDENTIFYING OTHER NODES *
+            ---------------------------*/
+            // DOCSTART 18
+            progressTracker.setCurrentStep(ID_OTHER_NODES);
+            // DOCEND 18
+
+            // A transaction generally needs a notary:
+            //   - To prevent double-spends if the transaction has inputs
+            //   - To serve as a timestamping authority if the transaction has a time-window
+            // We retrieve a notary from the network map.
+            // DOCSTART 1
+            Party specificNotary = getServiceHub().getNetworkMapCache().getNotary(new X500Name("CN=Notary Service,O=R3,OU=corda,L=London,C=UK"));
+            Party anyNotary = getServiceHub().getNetworkMapCache().getAnyNotary(null);
+            // Unlike the first two methods, ``getNotaryNodes`` returns a
+            // ``List<NodeInfo>``. We have to extract the notary identity of
+            // the node we want.
+            Party firstNotary = getServiceHub().getNetworkMapCache().getNotaryNodes().get(0).getNotaryIdentity();
+            // DOCEND 1
+
+            // We may also need to identify a specific counterparty.
+            // Again, we do so using the network map.
+            // DOCSTART 2
+            Party namedCounterparty = getServiceHub().getNetworkMapCache().getNodeByLegalName(new X500Name("CN=NodeA,O=NodeA,L=London,C=UK")).getLegalIdentity();
+            Party keyedCounterparty = getServiceHub().getNetworkMapCache().getNodeByLegalIdentityKey(dummyPubKey).getLegalIdentity();
+            Party firstCounterparty = getServiceHub().getNetworkMapCache().getPartyNodes().get(0).getLegalIdentity();
+            // DOCEND 2
+
+            // Finally, we can use the map to identify nodes providing a
+            // specific service (e.g. a regulator or an oracle).
+            // DOCSTART 3
+            Party regulator = getServiceHub().getNetworkMapCache().getNodesWithService(ServiceType.Companion.getRegulator()).get(0).getLegalIdentity();
+            // DOCEND 3
+
+            /*------------------------------
+             * SENDING AND RECEIVING DATA *
+            ------------------------------*/
+            progressTracker.setCurrentStep(SENDING_AND_RECEIVING_DATA);
+
+            // We can send arbitrary data to a counterparty.
+            // If this is the first ``send``, the counterparty will either:
+            // 1. Ignore the message if they are not registered to respond
+            //    to messages from this flow.
+            // 2. Start the flow they have registered to respond to this flow,
+            //    and run the flow until the first call to ``receive``, at
+            //    which point they process the message.
+            // In other words, we are assuming that the counterparty is
+            // registered to respond to this flow, and has a corresponding
+            // ``receive`` call.
+            // DOCSTART 4
+            send(counterparty, new Object());
+            // DOCEND 4
+
+            // We can wait to receive arbitrary data of a specific type from a
+            // counterparty. Again, this implies a corresponding ``send`` call
+            // in the counterparty's flow. A few scenarios:
+            // - We never receive a message back. In the current design, the
+            //   flow is paused until the node's owner kills the flow.
+            // - Instead of sending a message back, the counterparty throws a
+            //   ``FlowException``. This exception is propagated back to us,
+            //   and we can use the error message to establish what happened.
+            // - We receive a message back, but it's of the wrong type. In
+            //   this case, a ``FlowException`` is thrown.
+            // - We receive back a message of the correct type. All is good.
+            //
+            // Upon calling ``receive()`` (or ``sendAndReceive()``), the
+            // ``FlowLogic`` is suspended until it receives a response.
+            //
+            // We receive the data wrapped in an ``UntrustworthyData``
+            // instance. This is a reminder that the data we receive may not
+            // be what it appears to be! We must unwrap the
+            // ``UntrustworthyData`` using a lambda.
+            // DOCSTART 5
+            UntrustworthyData<Integer> packet1 = receive(Integer.class, counterparty);
+            Integer integer = packet1.unwrap(data -> {
+                // Perform checking on the object received.
+                // T O D O: Check the received object.
+                // Return the object.
+                return data;
+            });
+            // DOCEND 5
+
+            // We can also use a single call to send data to a counterparty
+            // and wait to receive data of a specific type back. The type of
+            // data sent doesn't need to match the type of the data received
+            // back.
+            // DOCSTART 7
+            UntrustworthyData<Boolean> packet2 = sendAndReceive(Boolean.class, counterparty, "You can send and receive any class!");
+            Boolean bool = packet2.unwrap(data -> {
+                // Perform checking on the object received.
+                // T O D O: Check the received object.
+                // Return the object.
+                return data;
+            });
+            // DOCEND 7
+
+            // We're not limited to sending to and receiving from a single
+            // counterparty. A flow can send messages to as many parties as it
+            // likes, and each party can invoke a different response flow.
+            // DOCSTART 6
+            send(regulator, new Object());
+            UntrustworthyData<Object> packet3 = receive(Object.class, regulator);
+            // DOCEND 6
+
+            /*------------------------------------
+             * EXTRACTING STATES FROM THE VAULT *
+            ------------------------------------*/
+            progressTracker.setCurrentStep(EXTRACTING_VAULT_STATES);
+
+            // Let's assume there are already some ``DummyState``s in our
+            // node's vault, stored there as a result of running past flows,
+            // and we want to consume them in a transaction. There are many
+            // ways to extract these states from our vault.
+
+            // For example, we would extract any unconsumed ``DummyState``s
+            // from our vault as follows:
+            Vault.StateStatus status = Vault.StateStatus.UNCONSUMED;
+            Set<Class<DummyState>> dummyStateTypes = new HashSet<>(ImmutableList.of(DummyState.class));
+
+            VaultQueryCriteria criteria = new VaultQueryCriteria(status, null, dummyStateTypes);
+            Vault.Page<DummyState> results = getServiceHub().getVaultService().queryBy(criteria);
+
+            List<StateAndRef<DummyState>> dummyStates = results.getStates();
+
+            // For a full list of the available ways of extracting states from
+            // the vault, see the Vault Query docs page.
+
+            // When building a transaction, input states are passed in as
+            // ``StateRef`` instances, which pair the hash of the transaction
+            // that generated the state with the state's index in the outputs
+            // of that transaction.
+            StateRef ourStateRef = new StateRef(SecureHash.sha256("DummyTransactionHash"), 0);
+            // A ``StateAndRef`` pairs a ``StateRef`` with the state it points to.
+            StateAndRef ourStateAndRef = getServiceHub().toStateAndRef(ourStateRef);
+
+            /*------------------------------------------
+             * GATHERING OTHER TRANSACTION COMPONENTS *
+            ------------------------------------------*/
+            progressTracker.setCurrentStep(OTHER_TX_COMPONENTS);
+
+            // Output states are constructed from scratch.
+            DummyState ourOutput = new DummyState();
+            // Or as copies of other states with some properties changed.
+            DummyState ourOtherOutput = ourOutput.copy(77);
+
+            // Commands pair a ``CommandData`` instance with a list of
+            // public keys. To be valid, the transaction requires a signature
+            // matching every public key in all of the transaction's commands.
+            CommandData commandData = new DummyContract.Commands.Create();
+            PublicKey ourPubKey = getServiceHub().getLegalIdentityKey();
+            PublicKey counterpartyPubKey = counterparty.getOwningKey();
+            List<PublicKey> requiredSigners = ImmutableList.of(ourPubKey, counterpartyPubKey);
+            Command ourCommand = new Command(commandData, requiredSigners);
+
+            // ``CommandData`` can either be:
+            // 1. Of type ``TypeOnlyCommandData``, in which case it only
+            //    serves to attach signers to the transaction and possibly
+            //    fork the contract's verification logic.
+            TypeOnlyCommandData typeOnlyCommandData = new DummyContract.Commands.Create();
+            // 2. Include additional data which can be used by the contract
+            //    during verification, alongside fulfilling the roles above
+            CommandData commandDataWithData = new Cash.Commands.Issue(12345678);
+
+            // Attachments are identified by their hash.
+            // The attachment with the corresponding hash must have been
+            // uploaded ahead of time via the node's RPC interface.
+            SecureHash ourAttachment = SecureHash.sha256("DummyAttachment");
+
+            // Time windows can have a start and end time, or be open at either end.
+            TimeWindow ourTimeWindow = TimeWindow.between(Instant.MIN, Instant.MAX);
+            TimeWindow ourAfter = TimeWindow.fromOnly(Instant.MIN);
+            TimeWindow ourBefore = TimeWindow.untilOnly(Instant.MAX);
+
+            /*------------------------
+             * TRANSACTION BUILDING *
+            ------------------------*/
+            progressTracker.setCurrentStep(TX_BUILDING);
+
+            // There are two types of transaction (notary-change and general),
+            // and therefore two types of transaction builder:
+            TransactionBuilder notaryChangeTxBuilder = new TransactionBuilder(NotaryChange.INSTANCE, specificNotary);
+            TransactionBuilder regTxBuilder = new TransactionBuilder(General.INSTANCE, specificNotary);
+
+            // We add items to the transaction builder using ``TransactionBuilder.withItems``:
+            regTxBuilder.withItems(
+                    // Inputs, as ``StateRef``s that reference to the outputs of previous transactions
+                    ourStateRef,
+                    // Outputs, as ``ContractState``s
+                    ourOutput,
+                    // Commands, as ``Command``s
+                    ourCommand
+            );
+
+            // We can also add items using methods for the individual components:
+            regTxBuilder.addInputState(ourStateAndRef);
+            regTxBuilder.addOutputState(ourOutput);
+            regTxBuilder.addCommand(ourCommand);
+            regTxBuilder.addAttachment(ourAttachment);
+            regTxBuilder.addTimeWindow(ourTimeWindow);
+
+            /*-----------------------
+             * TRANSACTION SIGNING *
+            -----------------------*/
+            progressTracker.setCurrentStep(TX_SIGNING);
+
+            // We finalise the transaction by signing it,
+            // converting it into a ``SignedTransaction``.
+            SignedTransaction onceSignedTx = getServiceHub().signInitialTransaction(regTxBuilder);
+
+            // If instead this was a ``SignedTransaction`` that we'd received
+            // from a counterparty and we needed to sign it, we would add our
+            // signature using:
+            SignedTransaction twiceSignedTx = getServiceHub().addSignature(onceSignedTx, dummyPubKey);
+
+            /*----------------------------
+             * TRANSACTION VERIFICATION *
+            ----------------------------*/
+            progressTracker.setCurrentStep(TX_VERIFICATION);
+
+            // Verifying a transaction will also verify every transaction in
+            // the transaction's dependency chain. So if this was a
+            // transaction we'd received from a counterparty and it had any
+            // dependencies, we'd need to download all of these dependencies
+            // using``ResolveTransactionsFlow`` before verifying it.
+            // DOCSTART 13
+            subFlow(new ResolveTransactionsFlow(twiceSignedTx, counterparty));
+            // DOCEND 13
+
+            // We can also resolve a `StateRef` dependency chain.
+            // DOCSTART 14
+            subFlow(new ResolveTransactionsFlow(ImmutableSet.of(ourStateRef.getTxhash()), counterparty));
+            // DOCEND 14
+
+            // We verify a transaction using the following one-liner:
+            twiceSignedTx.getTx().toLedgerTransaction(getServiceHub()).verify();
+
+            // Let's break that down...
+
+            // A ``SignedTransaction`` is a pairing of a ``WireTransaction``
+            // with signatures over this ``WireTransaction``. We don't verify
+            // a signed transaction per se, but rather the ``WireTransaction``
+            // it contains.
+            WireTransaction wireTx = twiceSignedTx.getTx();
+            // Before we can verify the transaction, we need the
+            // ``ServiceHub`` to use our node's local storage to resolve the
+            // transaction's inputs and attachments into actual objects,
+            // rather than just references. We do this by converting the
+            // ``WireTransaction`` into a ``LedgerTransaction``.
+            LedgerTransaction ledgerTx = wireTx.toLedgerTransaction(getServiceHub());
+            // We can now verify the transaction.
+            ledgerTx.verify();
+
+            // We'll often want to perform our own additional verification
+            // too. Just because a transaction is valid based on the contract
+            // rules and requires our signature doesn't mean we have to
+            // sign it! We need to make sure the transaction represents an
+            // agreement we actually want to enter into.
+            DummyState outputState = (DummyState) wireTx.getOutputs().get(0).getData();
+            if (outputState.getMagicNumber() != 777) {
+                throw new FlowException("We expected a magic number of 777.");
+            }
+
+            // Of course, if you are not a required signer on the transaction,
+            // you have no power to decide whether it is valid or not. If it
+            // requires signatures from all the required signers and is
+            // contractually valid, it's a valid ledger update.
+
+            /*------------------------
+             * GATHERING SIGNATURES *
+            ------------------------*/
+            progressTracker.setCurrentStep(SIGS_GATHERING);
+
+            // The list of parties who need to sign a transaction is dictated
+            // by the transaction's commands. Once we've signed a transaction
+            // ourselves, we can automatically gather the signatures of the
+            // other required signers using ``CollectSignaturesFlow``.
+            // The responder flow will need to call ``SignTransactionFlow``.
+            // DOCSTART 15
+            SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(twiceSignedTx, SIGS_GATHERING.childProgressTracker()));
+            // DOCEND 15
+
+            /*------------------------------
+             * FINALISING THE TRANSACTION *
+            ------------------------------*/
+            progressTracker.setCurrentStep(FINALISATION);
+
+            // We notarise the transaction and get it recorded in the vault of
+            // the participants of all the transaction's states.
+            // DOCSTART 9
+            SignedTransaction notarisedTx1 = subFlow(new FinalityFlow(fullySignedTx, FINALISATION.childProgressTracker())).get(0);
+            // DOCEND 9
+            // We can also choose to send it to additional parties who aren't one
+            // of the state's participants.
+            // DOCSTART 10
+            Set<Party> additionalParties = ImmutableSet.of(regulator, regulator);
+            SignedTransaction notarisedTx2 = subFlow(new FinalityFlow(ImmutableList.of(fullySignedTx), additionalParties, FINALISATION.childProgressTracker())).get(0);
+            // DOCEND 10
+
+            return null;
+        }
+    }
+
+    // ``ResponderFlow`` is our second flow, and will communicate with
+    // ``InitiatorFlow``.
+    // We mark ``ResponderFlow`` as an ``InitiatedByFlow``, meaning that it
+    // can only be started in response to a message from its initiating flow.
+    // That's ``InitiatorFlow`` in this case.
+    // Each node also has several flow pairs registered by default - see
+    // ``AbstractNode.installCoreFlows``.
+    @InitiatedBy(InitiatorFlow.class)
+    public static class ResponderFlow extends FlowLogic<Void> {
+
+        private final Party counterparty;
+
+        public ResponderFlow(Party counterparty) {
+            this.counterparty = counterparty;
+        }
+
+        private static final Step RECEIVING_AND_SENDING_DATA = new Step("Sending data between parties.");
+        private static final Step SIGNING = new Step("Responding to CollectSignaturesFlow.");
+        private static final Step FINALISATION = new Step("Finalising a transaction.");
+
+        private final ProgressTracker progressTracker = new ProgressTracker(
+                RECEIVING_AND_SENDING_DATA,
+                SIGNING,
+                FINALISATION
+        );
+
+        @Suspendable
+        @Override
+        public Void call() throws FlowException {
+            // The ``ResponderFlow` has all the same APIs available. It looks
+            // up network information, sends and receives data, and constructs
+            // transactions in exactly the same way.
+
+            /*------------------------------
+             * SENDING AND RECEIVING DATA *
+             -----------------------------*/
+            progressTracker.setCurrentStep(RECEIVING_AND_SENDING_DATA);
+
+            // We need to respond to the messages sent by the initiator:
+            // 1. They sent us an ``Any`` instance
+            // 2. They waited to receive an ``Integer`` instance back
+            // 3. They sent a ``String`` instance and waited to receive a
+            //    ``Boolean`` instance back
+            // Our side of the flow must mirror these calls.
+            // DOCSTART 8
+            Object obj = receive(Object.class, counterparty).unwrap(data -> data);
+            String string = sendAndReceive(String.class, counterparty, 99).unwrap(data -> data);
+            send(counterparty, true);
+            // DOCEND 8
+
+            /*-----------------------------------------
+             * RESPONDING TO COLLECT_SIGNATURES_FLOW *
+            -----------------------------------------*/
+            progressTracker.setCurrentStep(SIGNING);
+
+            // The responder will often need to respond to a call to
+            // ``CollectSignaturesFlow``. It does so my invoking its own
+            // ``SignTransactionFlow`` subclass.
+            // DOCSTART 16
+            class SignTxFlow extends SignTransactionFlow {
+                private SignTxFlow(Party otherParty, ProgressTracker progressTracker) {
+                    super(otherParty, progressTracker);
+                }
+
+                @Override
+                protected void checkTransaction(SignedTransaction stx) {
+                    requireThat(require -> {
+                        // Any additional checking we see fit...
+                        DummyState outputState = (DummyState) stx.getTx().getOutputs().get(0).getData();
+                        assert (outputState.getMagicNumber() == 777);
+                        return null;
+                    });
+                }
+            }
+
+            subFlow(new SignTxFlow(counterparty, SignTransactionFlow.Companion.tracker()));
+            // DOCEND 16
+
+            /*------------------------------
+             * FINALISING THE TRANSACTION *
+            ------------------------------*/
+            progressTracker.setCurrentStep(FINALISATION);
+
+            // Nothing to do here! As long as some other party calls
+            // ``FinalityFlow``, the recording of the transaction on our node
+            // we be handled automatically.
+
+            return null;
+        }
+    }
+}
\ No newline at end of file
diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
new file mode 100644
index 0000000000..edc75fbb0e
--- /dev/null
+++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
@@ -0,0 +1,470 @@
+package net.corda.docs
+
+import co.paralleluniverse.fibers.Suspendable
+import net.corda.contracts.asset.Cash
+import net.corda.core.contracts.*
+import net.corda.core.contracts.TransactionType.General
+import net.corda.core.contracts.TransactionType.NotaryChange
+import net.corda.core.crypto.SecureHash
+import net.corda.core.flows.*
+import net.corda.core.identity.Party
+import net.corda.core.node.services.ServiceType
+import net.corda.core.node.services.Vault
+import net.corda.core.node.services.vault.QueryCriteria
+import net.corda.core.transactions.LedgerTransaction
+import net.corda.core.transactions.SignedTransaction
+import net.corda.core.transactions.TransactionBuilder
+import net.corda.core.transactions.WireTransaction
+import net.corda.core.utilities.*
+import net.corda.core.utilities.ProgressTracker.Step
+import net.corda.flows.CollectSignaturesFlow
+import net.corda.flows.FinalityFlow
+import net.corda.flows.ResolveTransactionsFlow
+import net.corda.flows.SignTransactionFlow
+import org.bouncycastle.asn1.x500.X500Name
+import java.security.PublicKey
+import java.time.Instant
+
+// We group our two flows inside a singleton object to indicate that they work
+// together.
+object FlowCookbook {
+    // ``InitiatorFlow`` is our first flow, and will communicate with
+    // ``ResponderFlow``, below.
+    // We mark ``InitiatorFlow`` as an ``InitiatingFlow``, allowing it to be
+    // started directly by the node.
+    @InitiatingFlow
+    // We also mark ``InitiatorFlow`` as ``StartableByRPC``, allowing the
+    // node's owner to start the flow via RPC.
+    @StartableByRPC
+    // Every flow must subclass ``FlowLogic``. The generic indicates the
+    // flow's return type.
+    class InitiatorFlow(val arg1: Boolean, val arg2: Int, val counterparty: Party) : FlowLogic<Unit>() {
+
+        /**---------------------------------
+         * WIRING UP THE PROGRESS TRACKER *
+        ---------------------------------**/
+        // Giving our flow a progress tracker allows us to see the flow's
+        // progress visually in our node's CRaSH shell.
+        // DOCSTART 17
+        companion object {
+            object ID_OTHER_NODES : Step("Identifying other nodes on the network.")
+            object SENDING_AND_RECEIVING_DATA : Step("Sending data between parties.")
+            object EXTRACTING_VAULT_STATES : Step("Extracting states from the vault.")
+            object OTHER_TX_COMPONENTS : Step("Gathering a transaction's other components.")
+            object TX_BUILDING : Step("Building a transaction.")
+            object TX_SIGNING : Step("Signing a transaction.")
+            object TX_VERIFICATION : Step("Verifying a transaction.")
+            object SIGS_GATHERING : Step("Gathering a transaction's signatures.") {
+                // Wiring up a child progress tracker allows us to see the
+                // subflow's progress steps in our flow's progress tracker.
+                override fun childProgressTracker() = CollectSignaturesFlow.tracker()
+            }
+            object FINALISATION : Step("Finalising a transaction.") {
+                override fun childProgressTracker() = FinalityFlow.tracker()
+            }
+
+            fun tracker() = ProgressTracker(
+                    ID_OTHER_NODES,
+                    SENDING_AND_RECEIVING_DATA,
+                    EXTRACTING_VAULT_STATES,
+                    OTHER_TX_COMPONENTS,
+                    TX_BUILDING,
+                    TX_SIGNING,
+                    TX_VERIFICATION,
+                    SIGS_GATHERING,
+                    FINALISATION
+            )
+        }
+        // DOCEND 17
+
+        override val progressTracker: ProgressTracker = tracker()
+
+        @Suspendable
+        override fun call() {
+            // We'll be using a dummy public key for demonstration purposes.
+            // These are built in to Corda, and are generally used for writing
+            // tests.
+            val dummyPubKey: PublicKey = DUMMY_PUBKEY_1
+
+            /**--------------------------
+             * IDENTIFYING OTHER NODES *
+            --------------------------**/
+            // DOCSTART 18
+            progressTracker.currentStep = ID_OTHER_NODES
+            // DOCEND 18
+
+            // A transaction generally needs a notary:
+            //   - To prevent double-spends if the transaction has inputs
+            //   - To serve as a timestamping authority if the transaction has a time-window
+            // We retrieve the notary from the network map.
+            // DOCSTART 1
+            val specificNotary: Party? = serviceHub.networkMapCache.getNotary(X500Name("CN=Notary Service,O=R3,OU=corda,L=London,C=UK"))
+            val anyNotary: Party? = serviceHub.networkMapCache.getAnyNotary()
+            // Unlike the first two methods, ``getNotaryNodes`` returns a
+            // ``List<NodeInfo>``. We have to extract the notary identity of
+            // the node we want.
+            val firstNotary: Party = serviceHub.networkMapCache.notaryNodes[0].notaryIdentity
+            // DOCEND 1
+
+            // We may also need to identify a specific counterparty. Again, we
+            // do so using the network map.
+            // DOCSTART 2
+            val namedCounterparty: Party? = serviceHub.networkMapCache.getNodeByLegalName(X500Name("CN=NodeA,O=NodeA,L=London,C=UK"))?.legalIdentity
+            val keyedCounterparty: Party? = serviceHub.networkMapCache.getNodeByLegalIdentityKey(dummyPubKey)?.legalIdentity
+            val firstCounterparty: Party = serviceHub.networkMapCache.partyNodes[0].legalIdentity
+            // DOCEND 2
+
+            // Finally, we can use the map to identify nodes providing a
+            // specific service (e.g. a regulator or an oracle).
+            // DOCSTART 3
+            val regulator: Party = serviceHub.networkMapCache.getNodesWithService(ServiceType.regulator)[0].legalIdentity
+            // DOCEND 3
+
+            /**-----------------------------
+             * SENDING AND RECEIVING DATA *
+            -----------------------------**/
+            progressTracker.currentStep = SENDING_AND_RECEIVING_DATA
+
+            // We can send arbitrary data to a counterparty.
+            // If this is the first ``send``, the counterparty will either:
+            // 1. Ignore the message if they are not registered to respond
+            //    to messages from this flow.
+            // 2. Start the flow they have registered to respond to this flow,
+            //    and run the flow until the first call to ``receive``, at
+            //    which point they process the message.
+            // In other words, we are assuming that the counterparty is
+            // registered to respond to this flow, and has a corresponding
+            // ``receive`` call.
+            // DOCSTART 4
+            send(counterparty, Any())
+            // DOCEND 4
+
+            // We can wait to receive arbitrary data of a specific type from a
+            // counterparty. Again, this implies a corresponding ``send`` call
+            // in the counterparty's flow. A few scenarios:
+            // - We never receive a message back. In the current design, the
+            //   flow is paused until the node's owner kills the flow.
+            // - Instead of sending a message back, the counterparty throws a
+            //   ``FlowException``. This exception is propagated back to us,
+            //   and we can use the error message to establish what happened.
+            // - We receive a message back, but it's of the wrong type. In
+            //   this case, a ``FlowException`` is thrown.
+            // - We receive back a message of the correct type. All is good.
+            //
+            // Upon calling ``receive()`` (or ``sendAndReceive()``), the
+            // ``FlowLogic`` is suspended until it receives a response.
+            //
+            // We receive the data wrapped in an ``UntrustworthyData``
+            // instance. This is a reminder that the data we receive may not
+            // be what it appears to be! We must unwrap the
+            // ``UntrustworthyData`` using a lambda.
+            // DOCSTART 5
+            val packet1: UntrustworthyData<Int> = receive<Int>(counterparty)
+            val int: Int = packet1.unwrap { data ->
+                // Perform checking on the object received.
+                // T O D O: Check the received object.
+                // Return the object.
+                data
+            }
+            // DOCEND 5
+
+            // We can also use a single call to send data to a counterparty
+            // and wait to receive data of a specific type back. The type of
+            // data sent doesn't need to match the type of the data received
+            // back.
+            // DOCSTART 7
+            val packet2: UntrustworthyData<Boolean> = sendAndReceive<Boolean>(counterparty, "You can send and receive any class!")
+            val boolean: Boolean = packet2.unwrap { data ->
+                // Perform checking on the object received.
+                // T O D O: Check the received object.
+                // Return the object.
+                data
+            }
+            // DOCEND 7
+
+            // We're not limited to sending to and receiving from a single
+            // counterparty. A flow can send messages to as many parties as it
+            // likes, and each party can invoke a different response flow.
+            // DOCSTART 6
+            send(regulator, Any())
+            val packet3: UntrustworthyData<Any> = receive<Any>(regulator)
+            // DOCEND 6
+
+            /**-----------------------------------
+             * EXTRACTING STATES FROM THE VAULT *
+            -----------------------------------**/
+            progressTracker.currentStep = EXTRACTING_VAULT_STATES
+
+            // Let's assume there are already some ``DummyState``s in our
+            // node's vault, stored there as a result of running past flows,
+            // and we want to consume them in a transaction. There are many
+            // ways to extract these states from our vault.
+
+            // For example, we would extract any unconsumed ``DummyState``s
+            // from our vault as follows:
+            val criteria = QueryCriteria.VaultQueryCriteria() // default is UNCONSUMED
+            val results: Vault.Page<DummyState> = serviceHub.vaultService.queryBy<DummyState>(criteria)
+            val dummyStates: List<StateAndRef<DummyState>> = results.states
+
+            // For a full list of the available ways of extracting states from
+            // the vault, see the Vault Query docs page.
+
+            // When building a transaction, input states are passed in as
+            // ``StateRef`` instances, which pair the hash of the transaction
+            // that generated the state with the state's index in the outputs
+            // of that transaction.
+            val ourStateRef: StateRef = StateRef(SecureHash.sha256("DummyTransactionHash"), 0)
+            // A ``StateAndRef`` pairs a ``StateRef`` with the state it points to.
+            val ourStateAndRef: StateAndRef<DummyState> = serviceHub.toStateAndRef<DummyState>(ourStateRef)
+
+            /**-----------------------------------------
+             * GATHERING OTHER TRANSACTION COMPONENTS *
+            -----------------------------------------**/
+            progressTracker.currentStep = OTHER_TX_COMPONENTS
+
+            // Output states are constructed from scratch.
+            val ourOutput: DummyState = DummyState()
+            // Or as copies of other states with some properties changed.
+            val ourOtherOutput: DummyState = ourOutput.copy(magicNumber = 77)
+
+            // Commands pair a ``CommandData`` instance with a list of
+            // public keys. To be valid, the transaction requires a signature
+            // matching every public key in all of the transaction's commands.
+            val commandData: CommandData = DummyContract.Commands.Create()
+            val ourPubKey: PublicKey = serviceHub.legalIdentityKey
+            val counterpartyPubKey: PublicKey = counterparty.owningKey
+            val requiredSigners: List<PublicKey> = listOf(ourPubKey, counterpartyPubKey)
+            val ourCommand: Command = Command(commandData, requiredSigners)
+
+            // ``CommandData`` can either be:
+            // 1. Of type ``TypeOnlyCommandData``, in which case it only
+            //    serves to attach signers to the transaction and possibly
+            //    fork the contract's verification logic.
+            val typeOnlyCommandData: TypeOnlyCommandData = DummyContract.Commands.Create()
+            // 2. Include additional data which can be used by the contract
+            //    during verification, alongside fulfilling the roles above
+            val commandDataWithData: CommandData = Cash.Commands.Issue(nonce = 12345678)
+
+            // Attachments are identified by their hash.
+            // The attachment with the corresponding hash must have been
+            // uploaded ahead of time via the node's RPC interface.
+            val ourAttachment: SecureHash = SecureHash.sha256("DummyAttachment")
+
+            // Time windows can have a start and end time, or be open at either end.
+            val ourTimeWindow: TimeWindow = TimeWindow.between(Instant.MIN, Instant.MAX)
+            val ourAfter: TimeWindow = TimeWindow.fromOnly(Instant.MIN)
+            val ourBefore: TimeWindow = TimeWindow.untilOnly(Instant.MAX)
+
+            /**-----------------------
+             * TRANSACTION BUILDING *
+            -----------------------**/
+            progressTracker.currentStep = TX_BUILDING
+
+            // There are two types of transaction (notary-change and general),
+            // and therefore two types of transaction builder:
+            val notaryChangeTxBuilder: TransactionBuilder = TransactionBuilder(NotaryChange, specificNotary)
+            val regTxBuilder: TransactionBuilder = TransactionBuilder(General, specificNotary)
+
+            // We add items to the transaction builder using ``TransactionBuilder.withItems``:
+            regTxBuilder.withItems(
+                    // Inputs, as ``StateRef``s that reference to the outputs of previous transactions
+                    ourStateRef,
+                    // Outputs, as ``ContractState``s
+                    ourOutput,
+                    // Commands, as ``Command``s
+                    ourCommand
+            )
+
+            // We can also add items using methods for the individual components:
+            regTxBuilder.addInputState(ourStateAndRef)
+            regTxBuilder.addOutputState(ourOutput)
+            regTxBuilder.addCommand(ourCommand)
+            regTxBuilder.addAttachment(ourAttachment)
+            regTxBuilder.addTimeWindow(ourTimeWindow)
+
+            /**----------------------
+             * TRANSACTION SIGNING *
+            ----------------------**/
+            progressTracker.currentStep = TX_SIGNING
+
+            // We finalise the transaction by signing it, converting it into a
+            // ``SignedTransaction``.
+            val onceSignedTx: SignedTransaction = serviceHub.signInitialTransaction(regTxBuilder)
+
+            // If instead this was a ``SignedTransaction`` that we'd received
+            // from a counterparty and we needed to sign it, we would add our
+            // signature using:
+            val twiceSignedTx: SignedTransaction = serviceHub.addSignature(onceSignedTx, dummyPubKey)
+
+            // In practice, however, the process of gathering every signature
+            // but the first can be automated using ``CollectSignaturesFlow``.
+            // See the "Gathering Signatures" section below.
+
+            /**---------------------------
+             * TRANSACTION VERIFICATION *
+            ---------------------------**/
+            progressTracker.currentStep = TX_VERIFICATION
+
+            // Verifying a transaction will also verify every transaction in
+            // the transaction's dependency chain. So if this was a
+            // transaction we'd received from a counterparty and it had any
+            // dependencies, we'd need to download all of these dependencies
+            // using``ResolveTransactionsFlow`` before verifying it.
+            // DOCSTART 13
+            subFlow(ResolveTransactionsFlow(twiceSignedTx, counterparty))
+            // DOCEND 13
+
+            // We can also resolve a `StateRef` dependency chain.
+            // DOCSTART 14
+            subFlow(ResolveTransactionsFlow(setOf(ourStateRef.txhash), counterparty))
+            // DOCEND 14
+
+            // We verify a transaction using the following one-liner:
+            twiceSignedTx.tx.toLedgerTransaction(serviceHub).verify()
+
+            // Let's break that down...
+
+            // A ``SignedTransaction`` is a pairing of a ``WireTransaction``
+            // with signatures over this ``WireTransaction``. We don't verify
+            // a signed transaction per se, but rather the ``WireTransaction``
+            // it contains.
+            val wireTx: WireTransaction = twiceSignedTx.tx
+            // Before we can verify the transaction, we need the
+            // ``ServiceHub`` to use our node's local storage to resolve the
+            // transaction's inputs and attachments into actual objects,
+            // rather than just references. We do this by converting the
+            // ``WireTransaction`` into a ``LedgerTransaction``.
+            val ledgerTx: LedgerTransaction = wireTx.toLedgerTransaction(serviceHub)
+            // We can now verify the transaction.
+            ledgerTx.verify()
+
+            // We'll often want to perform our own additional verification
+            // too. Just because a transaction is valid based on the contract
+            // rules and requires our signature doesn't mean we have to
+            // sign it! We need to make sure the transaction represents an
+            // agreement we actually want to enter into.
+            val outputState: DummyState = wireTx.outputs.single().data as DummyState
+            if (outputState.magicNumber == 777) {
+                // ``FlowException`` is a special exception type. It will be
+                // propagated back to any counterparty flows waiting for a
+                // message from this flow, notifying them that the flow has
+                // failed.
+                throw FlowException("We expected a magic number of 777.")
+            }
+
+            // Of course, if you are not a required signer on the transaction,
+            // you have no power to decide whether it is valid or not. If it
+            // requires signatures from all the required signers and is
+            // contractually valid, it's a valid ledger update.
+
+            /**-----------------------
+             * GATHERING SIGNATURES *
+            -----------------------**/
+            progressTracker.currentStep = SIGS_GATHERING
+
+            // The list of parties who need to sign a transaction is dictated
+            // by the transaction's commands. Once we've signed a transaction
+            // ourselves, we can automatically gather the signatures of the
+            // other required signers using ``CollectSignaturesFlow``.
+            // The responder flow will need to call ``SignTransactionFlow``.
+            // DOCSTART 15
+            val fullySignedTx: SignedTransaction = subFlow(CollectSignaturesFlow(twiceSignedTx, SIGS_GATHERING.childProgressTracker()))
+            // DOCEND 15
+
+            /**-----------------------------
+             * FINALISING THE TRANSACTION *
+            -----------------------------**/
+            progressTracker.currentStep = FINALISATION
+
+            // We notarise the transaction and get it recorded in the vault of
+            // the participants of all the transaction's states.
+            // DOCSTART 9
+            val notarisedTx1: SignedTransaction = subFlow(FinalityFlow(fullySignedTx, FINALISATION.childProgressTracker())).single()
+            // DOCEND 9
+            // We can also choose to send it to additional parties who aren't one
+            // of the state's participants.
+            // DOCSTART 10
+            val additionalParties: Set<Party> = setOf(regulator)
+            val notarisedTx2: SignedTransaction = subFlow(FinalityFlow(listOf(fullySignedTx), additionalParties, FINALISATION.childProgressTracker())).single()
+            // DOCEND 10
+        }
+    }
+
+    // ``ResponderFlow`` is our second flow, and will communicate with
+    // ``InitiatorFlow``.
+    // We mark ``ResponderFlow`` as an ``InitiatedByFlow``, meaning that it
+    // can only be started in response to a message from its initiating flow.
+    // That's ``InitiatorFlow`` in this case.
+    // Each node also has several flow pairs registered by default - see
+    // ``AbstractNode.installCoreFlows``.
+    @InitiatedBy(InitiatorFlow::class)
+    class ResponderFlow(val counterparty: Party) : FlowLogic<Unit>() {
+
+        companion object {
+            object RECEIVING_AND_SENDING_DATA : Step("Sending data between parties.")
+            object SIGNING : Step("Responding to CollectSignaturesFlow.")
+            object FINALISATION : Step("Finalising a transaction.")
+
+            fun tracker() = ProgressTracker(
+                    RECEIVING_AND_SENDING_DATA,
+                    SIGNING,
+                    FINALISATION
+            )
+        }
+
+        override val progressTracker: ProgressTracker = tracker()
+
+        @Suspendable
+        override fun call() {
+            // The ``ResponderFlow` has all the same APIs available. It looks
+            // up network information, sends and receives data, and constructs
+            // transactions in exactly the same way.
+
+            /**-----------------------------
+             * SENDING AND RECEIVING DATA *
+            -----------------------------**/
+            progressTracker.currentStep = RECEIVING_AND_SENDING_DATA
+
+            // We need to respond to the messages sent by the initiator:
+            // 1. They sent us an ``Any`` instance
+            // 2. They waited to receive an ``Integer`` instance back
+            // 3. They sent a ``String`` instance and waited to receive a
+            //    ``Boolean`` instance back
+            // Our side of the flow must mirror these calls.
+            // DOCSTART 8
+            val any: Any = receive<Any>(counterparty).unwrap { data -> data }
+            val string: String = sendAndReceive<String>(counterparty, 99).unwrap { data -> data }
+            send(counterparty, true)
+            // DOCEND 8
+
+            /**----------------------------------------
+             * RESPONDING TO COLLECT_SIGNATURES_FLOW *
+            ----------------------------------------**/
+            progressTracker.currentStep = SIGNING
+
+            // The responder will often need to respond to a call to
+            // ``CollectSignaturesFlow``. It does so my invoking its own
+            // ``SignTransactionFlow`` subclass.
+            // DOCSTART 16
+            val signTransactionFlow: SignTransactionFlow = object : SignTransactionFlow(counterparty) {
+                override fun checkTransaction(stx: SignedTransaction) = requireThat {
+                    // Any additional checking we see fit...
+                    val outputState = stx.tx.outputs.single().data as DummyState
+                    assert(outputState.magicNumber == 777)
+                }
+            }
+
+            subFlow(signTransactionFlow)
+            // DOCEND 16
+
+            /**-----------------------------
+             * FINALISING THE TRANSACTION *
+            -----------------------------**/
+            progressTracker.currentStep = FINALISATION
+
+            // Nothing to do here! As long as some other party calls
+            // ``FinalityFlow``, the recording of the transaction on our node
+            // we be handled automatically.
+        }
+    }
+}
\ No newline at end of file
diff --git a/docs/source/flow-cookbook.rst b/docs/source/flow-cookbook.rst
new file mode 100644
index 0000000000..1402c2ea1c
--- /dev/null
+++ b/docs/source/flow-cookbook.rst
@@ -0,0 +1,18 @@
+.. highlight:: kotlin
+.. raw:: html
+
+   <script type="text/javascript" src="_static/jquery.js"></script>
+   <script type="text/javascript" src="_static/codesets.js"></script>
+
+Flow cookbook
+=============
+
+This flow showcases how to use Corda's API, in both Java and Kotlin.
+
+.. container:: codeset
+
+   .. literalinclude:: example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
+      :language: kotlin
+
+   .. literalinclude:: example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
+      :language: java
\ No newline at end of file