Skip to content

Finalizing Statements

In the production system (but not in the sandbox), finalizing statements removes the draft watermark. Also in the production system (but not in the sandbox), OtterTax charges a fee for finalizing statements, and credit card information is required as part of the finalizeStatements mutation.

In addition to credit card data, the finalizeStatements mutation accepts two arguments: a list of OTX IDs and a list of uploader IDs. The search logic combines these two criteria using a logical and so you should generally supply only one list or the other.

The most up-to-date documentation for the finalizeStatements query is available as part of the introspective GraphQL documentation.

Note

Only valid statements with a current status of draft can be finalized. Use the getStatements query to determine statements' status and validity.

Warning

Statement data cannot be changed after a statement is marked as final.

The finalizeStatements mutation returns a count of the number of statements that were successfully finalized. Compare this value to the number of statements in the parameter list to be sure that all statements in your query were finalized.

The examples below illustrate finalizing statements using the following mutation:

Note

The mutation below includes credit card information which is required to finalize statements in the production system. Credit card information is optional when using the sandbox system. If credit card information is provided on the sandbox system, no charge is actually made to the card. If you would like to test your mutation in the sandbox system to ensure that credit card data is correctly formatted, you can use bogus credit card data (like that provided here).

mutation {
  finalizeStatements(
    creditCard: {
      firstName: "CARD HOLDER'S FIRST NAME"
      lastName: "CARD HOLDER'S LAST NAME"
      companyName: "COMPANY NAME (Optional)"
      address: "CARD HOLDER'S ADDRESS"
      city: "CARD HOLDER'S CITY"
      state: "CARD HOLDER'S STATE ABBREVIATION"
      zipCode: "CARD HOLDER'S ZIP CODE"
      cardNumber: "CREDIT CARD NUMBER"
      cardExpiration: "CARD EXPIRATION MMYY"
      cvv: "CARD VERIFICATION VALUE"
    }
    otxIds: [
      "a5e724cd-ad1c-4a90-928d-e5e8bcc97e15"
      "8c522b06-79e5-4276-b58c-f83e1265ee05"
      "26180e66-83d4-4a00-b0a6-828eb9a05a76"
      "64ffc7a2-e52a-4e4f-ab8e-58d64fb7e561"
    ]
  ) {
    errors
    messages {
      messages
      otxId
      uploaderId
    }
    successCount
  }
}

The code snippets below illustrate running the query. See the documentation on authentication for information about obtaining the credential data passed in the header.

# Terminate lines with \ character to allow command to span multiple lines.
# Escape quotation marks in body of mutation or query with backslashes.
# Use here document for data stream.
# Tested using the bash interpreter on Linux.
curl 'https://sandbox.ottertax.com/v2/graphql' \
  -i \
  -X POST \
  -H 'content-type:  application/json' \
  -H 'access-token:  YOUR ACCESS TOKEN' \
  -H 'client:        YOUR CLIENT ID' \
  -H 'uid:           YOUR UID' \
  -d @- <<END_DATA
    { 
      "query":"
        mutation {
          finalizeStatements(
            creditCard: {
              firstName: \"CARD HOLDER'S FIRST NAME\"
              lastName: \"CARD HOLDER'S LAST NAME\"
              companyName: \"COMPANY NAME (Optional)\"
              address: \"CARD HOLDER'S ADDRESS\"
              city: \"CARD HOLDER'S CITY\"
              state: \"CARD HOLDER'S STATE ABBREVIATION\"
              zipCode: \"CARD HOLDER'S ZIP CODE\"
              cardNumber: \"CREDIT CARD NUMBER\"
              cardExpiration: \"CARD EXPIRATION MMYY\"
              cvv: \"CARD VERIFICATION VALUE\"
            }
            otxIds: [
              \"a5e724cd-ad1c-4a90-928d-e5e8bcc97e15\"
              \"8c522b06-79e5-4276-b58c-f83e1265ee05\"
              \"26180e66-83d4-4a00-b0a6-828eb9a05a76\"
              \"64ffc7a2-e52a-4e4f-ab8e-58d64fb7e561\"
            ]
          ) {
            errors
            messages {
              messages
              otxId
              uploaderId
            }
            successCount
          }
        }
      "
    }
END_DATA
:: Terminate lines with ^ character to allow command to span multiple lines.
:: Precede quotation marks in body of mutation or query with triple backslashes.
:: Precede other quotation marks in data stream with single backslashes.
:: Tested using a command prompt on Windows 10.
curl "https://sandbox.ottertax.com/v2/graphql" ^
  -i ^
  -X POST ^
  -H "content-type: application/json" ^
  -H "access-token: YOUR ACCESS TOKEN" ^
  -H "client:       YOUR CLIENT ID" ^
  -H "uid:          YOUR UID" ^
  -d "{ \"query\":\" ^
    mutation { ^
      finalizeStatements( ^
        creditCard: { ^
          firstName: \\\"CARD HOLDER'S FIRST NAME\\\" ^
          lastName: \\\"CARD HOLDER'S LAST NAME\\\" ^
          companyName: \\\"COMPANY NAME (Optional)\\\" ^
          address: \\\"CARD HOLDER'S ADDRESS\\\" ^
          city: \\\"CARD HOLDER'S CITY\\\" ^
          state: \\\"CARD HOLDER'S STATE ABBREVIATION\\\" ^
          zipCode: \\\"CARD HOLDER'S ZIP CODE\\\" ^
          cardNumber: \\\"CREDIT CARD NUMBER\\\" ^
          cardExpiration: \\\"CARD EXPIRATION MMYY\\\" ^
          cvv: \\\"CARD VERIFICATION VALUE\\\" ^
        } ^
        otxIds: [ ^
          \\\"a5e724cd-ad1c-4a90-928d-e5e8bcc97e15\\\" ^
          \\\"8c522b06-79e5-4276-b58c-f83e1265ee05\\\" ^
          \\\"26180e66-83d4-4a00-b0a6-828eb9a05a76\\\" ^
          \\\"64ffc7a2-e52a-4e4f-ab8e-58d64fb7e561\\\" ^
        ] ^
      ) { ^
        errors ^
        messages { ^
          messages ^
          otxId ^
          uploaderId ^
        } ^
        successCount ^
      } ^
    } ^
     \" }"
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;

// This example uses version 2.9.1 of the
// open source gson library from Google.
// See https://github.com/google/gson.
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

// This example uses httpclient (version 4.5.13)
// and httpcore (version 4.4.13) libraries of the
// open source Apache HttpComponents project.
// See https://hc.apache.org/index.html.
import org.apache.http.entity.StringEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;

// Download the OtterTax java classes for the GraphQL
// query and mutation responses at
// https://github.com/OtterTax/graphql-java-classes
import com.ottertax.support.FinalizeResponse;

public class StatementFinalizer {
  private String endpoint = "https://sandbox.ottertax.com/v2/graphql";
  private String accessToken = "YOUR ACCESS TOKEN";
  private String client = "YOUR CLIENT ID";
  private String uid = "YOUR UID";
  private String gql =
     String.join("\n",
                 "mutation {",
                 "  finalizeStatements(",
                 "    creditCard: {",
                 "      firstName: \"CARD HOLDER'S FIRST NAME\"",
                 "      lastName: \"CARD HOLDER'S LAST NAME\"",
                 "      companyName: \"COMPANY NAME (Optional)\"",
                 "      address: \"CARD HOLDER'S ADDRESS\"",
                 "      city: \"CARD HOLDER'S CITY\"",
                 "      state: \"CARD HOLDER'S STATE ABBREVIATION\"",
                 "      zipCode: \"CARD HOLDER'S ZIP CODE\"",
                 "      cardNumber: \"CREDIT CARD NUMBER\"",
                 "      cardExpiration: \"CARD EXPIRATION MMYY\"",
                 "      cvv: \"CARD VERIFICATION VALUE\"",
                 "    }",
                 "    otxIds: [",
                 "      \"a5e724cd-ad1c-4a90-928d-e5e8bcc97e15\"",
                 "      \"8c522b06-79e5-4276-b58c-f83e1265ee05\"",
                 "      \"26180e66-83d4-4a00-b0a6-828eb9a05a76\"",
                 "      \"64ffc7a2-e52a-4e4f-ab8e-58d64fb7e561\"",
                 "    ]",
                 "  ) {",
                 "    errors",
                 "    messages {",
                 "      messages",
                 "      otxId",
                 "      uploaderId",
                 "    }",
                 "    successCount",
                 "  }",
                 "}");

  private String querify(String rawGraphql) {
    Gson gson = new Gson();
    return("{\"query\":" + gson.toJson(rawGraphql) + "}");
  }

  private void finalizeStatements() {
    CloseableHttpClient httpClient = HttpClients.createDefault();
    Gson gson = new GsonBuilder().setPrettyPrinting().create();
    String response = "";

    try {
      HttpPost httpPost = new HttpPost(endpoint);
      httpPost.addHeader("content-type", "application/json");
      httpPost.addHeader("access-token", accessToken);
      httpPost.addHeader("client", client);
      httpPost.addHeader("uid", uid);

      StringEntity stringEntity = new StringEntity(querify(gql));
      httpPost.setEntity(stringEntity);
      CloseableHttpResponse httpResponse = httpClient.execute(httpPost);

      BufferedReader reader = new BufferedReader(new InputStreamReader(
          httpResponse.getEntity().getContent()));
      StringBuffer responseBuffer = new StringBuffer();
      String inputLine;
      while ((inputLine = reader.readLine()) != null) {
        responseBuffer.append(inputLine);
      }
      reader.close();
      response = responseBuffer.toString();
      int responseCode = httpResponse.getStatusLine().getStatusCode();
      if( responseCode != 200  ) {
        System.out.println("Response code from server was " + String.valueOf(responseCode) + ".");
      }
      httpResponse.close();
      httpClient.close();
    } catch(IOException e) {
      System.out.println("Error posting GraphQL.\nExiting");
      System.exit(1);
    }
    FinalizeResponse finalizeResponse = gson.fromJson(response, FinalizeResponse.class);
    // Show the response from the server.
    System.out.println(gson.toJson(finalizeResponse));
  }

  public static void main(String[] args) {
    StatementFinalizer statementFinalizer = new StatementFinalizer();
    statementFinalizer.finalizeStatements();
  }
}
// Using graphql-request from
// https://github.com/prisma-labs/graphql-request
// Example tested with node version 14.16.0
import { GraphQLClient, gql } from 'graphql-request'

async function main() {
  const endpoint = 'https://sandbox.ottertax.com/v2/graphql'

  const graphQLClient = new GraphQLClient(endpoint, {
    headers: {
      'access-token': 'YOUR ACCESS TOKEN',
      'client':       'YOUR CLIENT ID',
      'uid':          'YOUR UID'
    },
  })
  const query = gql`
    mutation {
      finalizeStatements(
        creditCard: {
          firstName: "CARD HOLDER'S FIRST NAME"
          lastName: "CARD HOLDER'S LAST NAME"
          companyName: "COMPANY NAME (Optional)"
          address: "CARD HOLDER'S ADDRESS"
          city: "CARD HOLDER'S CITY"
          state: "CARD HOLDER'S STATE ABBREVIATION"
          zipCode: "CARD HOLDER'S ZIP CODE"
          cardNumber: "CREDIT CARD NUMBER"
          cardExpiration: "CARD EXPIRATION MMYY"
          cvv: "CARD VERIFICATION VALUE"
        }
        otxIds: [
          "a5e724cd-ad1c-4a90-928d-e5e8bcc97e15"
          "8c522b06-79e5-4276-b58c-f83e1265ee05"
          "26180e66-83d4-4a00-b0a6-828eb9a05a76"
          "64ffc7a2-e52a-4e4f-ab8e-58d64fb7e561"
        ]
      ) {
        errors
        messages {
          messages
          otxId
          uploaderId
        }
        successCount
      }
    }
  `

  const data = await graphQLClient.request(query)
  console.log(JSON.stringify(data))
}

main().catch((error) => console.error(error))
<?php
// Tested with php-cli version 8.0.5.
$query =<<<'END_DATA'
  mutation {
    finalizeStatements(
      creditCard: {
        firstName: "CARD HOLDER'S FIRST NAME"
        lastName: "CARD HOLDER'S LAST NAME"
        companyName: "COMPANY NAME (Optional)"
        address: "CARD HOLDER'S ADDRESS"
        city: "CARD HOLDER'S CITY"
        state: "CARD HOLDER'S STATE ABBREVIATION"
        zipCode: "CARD HOLDER'S ZIP CODE"
        cardNumber: "CREDIT CARD NUMBER"
        cardExpiration: "CARD EXPIRATION MMYY"
        cvv: "CARD VERIFICATION VALUE"
      }
      otxIds: [
        "a5e724cd-ad1c-4a90-928d-e5e8bcc97e15"
        "8c522b06-79e5-4276-b58c-f83e1265ee05"
        "26180e66-83d4-4a00-b0a6-828eb9a05a76"
        "64ffc7a2-e52a-4e4f-ab8e-58d64fb7e561"
      ]
    ) {
      errors
      messages {
        messages
        otxId
        uploaderId
      }
      successCount
    }
  }
END_DATA;
$payload = array ('query' => $query);
$options = array(
  'http' => array(
    'method'  => 'POST',
    'content' => json_encode( $payload ),
    'header'=>  "Content-Type: application/json\r\n" .
                "access-token: YOUR ACCESS TOKEN\r\n" .
                "client:       YOUR CLIENT ID\r\n" .
                "uid:          YOUR UID\r\n"
    )
);

$context  = stream_context_create( $options );
$response = file_get_contents( 'https://sandbox.ottertax.com/v2/graphql',
                               false, $context );
if( $response === FALSE ) {
  echo "Call to server failed.\n";
} else {
  echo $response . "\n";
}
?>
# Using GQL from
# https://github.com/graphql-python/gql
# Tested using python version 3.8.8
from gql import gql, Client
from gql.transport.aiohttp import AIOHTTPTransport

transport = AIOHTTPTransport(url="https://sandbox.ottertax.com/v2/graphql",
                            headers={ 'access-token': 'YOUR ACCESS TOKEN',
                                      'client':       'YOUR CLIENT ID',
                                      'uid':          'YOUR UID' })
client = Client(transport=transport, fetch_schema_from_transport=True)
query = gql(
    """
      mutation {
        finalizeStatements(
          creditCard: {
            firstName: "CARD HOLDER'S FIRST NAME"
            lastName: "CARD HOLDER'S LAST NAME"
            companyName: "COMPANY NAME (Optional)"
            address: "CARD HOLDER'S ADDRESS"
            city: "CARD HOLDER'S CITY"
            state: "CARD HOLDER'S STATE ABBREVIATION"
            zipCode: "CARD HOLDER'S ZIP CODE"
            cardNumber: "CREDIT CARD NUMBER"
            cardExpiration: "CARD EXPIRATION MMYY"
            cvv: "CARD VERIFICATION VALUE"
          }
          otxIds: [
            "a5e724cd-ad1c-4a90-928d-e5e8bcc97e15"
            "8c522b06-79e5-4276-b58c-f83e1265ee05"
            "26180e66-83d4-4a00-b0a6-828eb9a05a76"
            "64ffc7a2-e52a-4e4f-ab8e-58d64fb7e561"
          ]
        ) {
          errors
          messages {
            messages
            otxId
            uploaderId
          }
          successCount
        }
      }
    """
)

result = client.execute(query)
print(result)
# If you wish to use a library instead, see
# https://github.com/github/graphql-client
# Tested using ruby 2.7.2.
require( 'net/http' )
require( 'uri' )
require( 'json' )

uri = URI( "https://sandbox.ottertax.com/v2/graphql" )
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
headers = { 'Content-Type': 'application/json',
            'access-token': 'YOUR ACCESS TOKEN',
            'client':       'YOUR CLIENT ID',
            'uid':          'YOUR UID' }

query = <<-END_DATA
  mutation {
    finalizeStatements(
      creditCard: {
        firstName: "CARD HOLDER'S FIRST NAME"
        lastName: "CARD HOLDER'S LAST NAME"
        companyName: "COMPANY NAME (Optional)"
        address: "CARD HOLDER'S ADDRESS"
        city: "CARD HOLDER'S CITY"
        state: "CARD HOLDER'S STATE ABBREVIATION"
        zipCode: "CARD HOLDER'S ZIP CODE"
        cardNumber: "CREDIT CARD NUMBER"
        cardExpiration: "CARD EXPIRATION MMYY"
        cvv: "CARD VERIFICATION VALUE"
      }
      otxIds: [
        "a5e724cd-ad1c-4a90-928d-e5e8bcc97e15"
        "8c522b06-79e5-4276-b58c-f83e1265ee05"
        "26180e66-83d4-4a00-b0a6-828eb9a05a76"
        "64ffc7a2-e52a-4e4f-ab8e-58d64fb7e561"
      ]
    ) {
      errors
      messages {
        messages
        otxId
        uploaderId
      }
      successCount
    }
  }
END_DATA
request = Net::HTTP::Post.new(uri.request_uri, headers )
request.body = {query: query}.to_json
response = http.request(request)
if( response.code == '200' )
  payload = JSON.parse( response.body )
  STDOUT.puts( payload )
else
  STDOUT.puts( "Response code was #{response.code}:\n#{response.inspect}" )
end