Basics

Introduction

Rutter is a unified RESTful API for B2B Financial Products—connect to any commerce, payments, accounting, or ads system in a single API. We offer a range of product options with different capabilities and price points, as well as the ability to fine-tune our API to your needs.

Want to jump straight to the code?

Skip the introduction and dive into the Quickstart

Explore Quickstart

To get started, click through the endpoints to learn more about the functionality across our APIs and check out our Postman Collection to test our API endpoints.

Organization

API reference pages are divided by the object with which the endpoints interact. For example, there is a single page for Bill Payments, but within the page there are endpoints for creating, updating, and listing bill payments.

Each API reference page has roughly three sections:

  1. Overview

    A brief description of the Rutter entity and the set of endpoints.

  2. Object

    A description of the object representing the associated data model.

  3. Endpoints

    A list of endpoints available in the API. This will include a description of the endpoint, the HTTP method, the request and response body, and platform support.

Production Base URL
1
https://production.rutterapi.com/versioned
Sandbox Base URL
1
https://sandbox.rutterapi.com

Versioning

Rutter supports API versioning - all APIs are prefixed with /versioned/ and a version must be supplied in the header by using the key X-Rutter-Version. The supported values are: 2024-08-31, 2024-04-30, 2023-03-14, and 2023-02-07.

Breaking API changes are changes that disrupt the working implementation of our customers. This can happen if we change our endpoints in a way that our customers do not expect them to change. We have a strict policy to never make a breaking change on an published version of our API.

Things we do not consider as breaking changes:

  • Adding new API endpoints (e.g. POST /accounting/bill_credit_applications).
  • Adding new optional request parameters to existing API endpoints.
  • Making a previously required request parameter optional.
  • Adding new property fields to existing API responses.
  • Changing the order of property fields in existing API responses.
  • Changing the length or format of opaque strings, such as object IDs, error messages, and other human-readable strings.

All other API changes maybe considered a breaking change. This includes (but is not limited to):

  • Removing an API endpoint.
  • Making a previously optional request parameter required.
  • Adding a required request parameter to existing API endpoints.
  • Removing or renaming property fields from existing API responses.
  • Changing the property field type of existing API responses.
  • Changing the property field of existing API responses that was non-nullable as nullable.
  • Changing values of an enum type. Note that there are a couple of exceptions to this rule: we may add new values to enum's for platforms we support and for currency codes.

Please see the API upgrades page for more information on differences between our various API versions.

Versioned URL
1
https://production.rutterapi.com/versioned/accounting/accounts

Authorization

Authorizing Requests

Rutter uses your client_id and client_secret keys to control access to our API via HTTP Basic Auth. You can find your keys in your dashboard for each of our API environments (sandbox, production).

When making an API request to the server, Rutter expects your client_id and client_secret to be Base64 encoded within the Authorization header. The header is formed by concatenating the word Basic, followed by a space ( ), and a base64 encoded string of the client_id, a colon (:), and the client_secret.

Authorization: Basic base64({client_id}:{client_secret})

Authorizing Access for a Given Connection

To identify a specific connection, Rutter uses the access_token query parameter.

This token is generated by Rutter and is unique to each connection. The access token is acquired by hitting the Exchange Tokens endpoint.

Authenticated Request
1
curl "https://production.rutterapi.com/versioned/accounting/accounts" \
2
-H "X-Rutter-Version: 2023-02-07" \
3
-H "Authorization: Basic <YOUR_ENCODED_CLIENT_ID_AND_CLIENT_SECRET>"
Authenticated Request with Access Token
1
curl "https://production.rutterapi.com/versioned/accounting/accounts?access_token=<ACCESS_TOKEN>" \
2
-H "X-Rutter-Version: 2023-02-07" \
3
-H "Authorization: Basic <YOUR_ENCODED_CLIENT_ID_AND_CLIENT_SECRET>"

Errors and Error Codes

Rutter uses standard HTTP error codes that follow common industry practices. However, we also utilize a few custom error codes for specific conditions unique to Rutter. These custom codes are designed to offer detailed insights into the nature of the issue, accompanied by actionable error messages wherever possible.

If you are experience any of these errors and are unsure what to do, please reach out to our support team.

Actionable Error Codes

StatusSituationNotes
400Rutter will return this error when a request is made to Rutter with an invalid input. The error message should provide detailed information about the issue and suggestions. Use the information in the error message to identify and address any issues with the input data.
401Rutter will return this error when a request is made to Rutter with missing or invalid credentials. Please check that you have the correct credentials.
404Rutter will return this error when an endpoint is not implemented or is not supported for a given platform.
409Rutter will return this error when a request is made that differs from another request made with the same idempotency-key.
410Rutter will return this error when the endpoint is recognized but the requested resource is not found. This usually happens when the :id of the requested resource is invalid.
429Rutter will return this error when too many requests are being made to the Rutter service. Please allow for some time in between your requests.
450 (custom)Rutter will return this error when the underlying platform returns 400 Bad Request to differentiate from the 400 error Rutter itself returns. The error message will contain details about the problem and recommendations for resolution.
451 (custom)Rutter will return this error when the underlying platform returns an 401 Unauthorized to differentiate from the 401 error Rutter itself returns.
452 (custom)Rutter will return this error when the underlying platform returns a 429 Too Many Requests error to differentiate from the 429 error Rutter itself returns. This typically only happens for POST, PATCH, or DELETE endpoints. In such instances, we recommend leveraging the idempotent endpoint, as this enables Rutter to automatically retry the request using exponential backoff.

Server Error Codes

StatusSituationNotes
500Rutter will return this error in response to unforeseen circumstances. When this Internal Server Error happens, our team will be alerted and prioritize efforts to comprehend and address these issues promptly.
550 (custom)Rutter will return this error when the underlying platform returns 500 Internal Server Error to differentiate from the 500 error Rutter returns itself. In these situations, we will encode the platform error message in our error response. We recommend trying the request again after a delay.

Error Response

If you receive an error, you will receive an object with the following properties:

PropertyTypeDescription
error_typeStringThe type of error that occurred.
error_codeStringA more specific code under the error type.
error_messageStringA string that describes the error that occurred.
error_metadataObjectAn object containing details related to the error. Fields in here are experimental and subject to change:
  • source - Whether the error originated from Rutter ("rutter") or the platform ("platform").
  • human_readable - An easy to read error message for platform-originated errors, e.g. "The account type xxxx is incompatible with this transaction."
  • platform - An object that contains data from the platform, e.g. headers or rate limiting information.

Rate Limiting

Rutter allows for an extremely generous rate limit, which is designed to accommodate the vast majority of use cases. The current limit allows 500 requests in a window of 10 seconds.

When using Rutter's read endpoints, you will not experience the normal rate limits imposed by a specific platform (e.g. QuickBooks or Shopify).

Rutter's write endpoints pass requests directly to their respective platforms and will be subject to each platform's unique rate limits.

How does Rutter resolve rate limit issues?

In order to offer the fastest access to your users' data, Rutter uses a combination of caching and webhooks to store the latest data for a given connection. When you send requests to Rutter's API, most of the time, the data will be cached and returned immediately. One exception is when a customer first authorizes you to use their data. Rutter will begin downloading the data immediately, but it will not be ready until the INITIAL_UPDATE webhook fires.

How does Rutter cache data and keep it up-to-date?

Rutter does an "initial sync" of a user's data when they authenticate your app. This may take hours to days depending on the size of the data. Once this process completes, Rutter automatically updates data from the platform on a regular cadence.

Webhooks

You will receive notifications via a webhook whenever there are new events associated with a Connection or change in data.

Webhook Security

Webhook signatures provide additional security to webhooks emitted by Rutter by verifying that the webhook wasn't sent from an unknown or malicious entity. To do this, Rutter signs each webhook payload with a secret. The resulting signature is included in the header of the request, which you can then use to verify that the webhook received is from Rutter, guaranteeing the validity of the webhook.

Every webhook sent by Rutter contains a header called X-Rutter-Signature whose value is the prefix sha256= followed by a base64 encoded HMAC-SHA256 hash of the webhook payload, where the secret used to generate the hash is your organization's client secret.

Connection Webhooks

All webhooks related to a connection contain the type CONNECTION

Webhook Retries

You can implement rate-limiting of Rutter webhooks to handle load on your server. If we receive a failure HTTP status code from your server when sending a webhook, we will attempt to retry the webhook up to 6 times following an exponential backoff method. The 6 attempts will take place over a period of ~15 minutes. If the webhook fails to be delivered after 6 attempts, we will stop retrying and mark the webhook as failed.

Special Considerations

When working with webhooks, please keep the following in mind:

Webhooks that have the same payload may be sent multiple times. This can be caused by a variety of reasons, such as when the underlying raw platform data changes but not the standardized Rutter data object. Please make sure your webhook handler accounts for this behavior.

Webhooks are not 100% reliable. Expect a small percentage of webhooks to get dropped under normal working conditions. Make sure to handle these missing webhooks gracefully, so that the application still syncs even if no webhooks are received. We recommend calling the API endpoints regularly to check for updates in addition to using webhooks for real-time updates.

Iniitial Update

This webhook fires after an initial data download for a Connection has been completed. You can now send requests to Rutter to fetch merchant data.

Attempting to fetch data from a Connection without receiving the INITIAL_UPDATE webhook will return a PRODUCT_NOT_READY error.

If your organization has data batching enabled, this webhook will fire after a batch of historical data for a Connection has been synchronized. You can continue to fetch data while this process occurs.

Initial Update
1
{
2
"type": "CONNECTION",
3
"code": "INITIAL_UPDATE",
4
"connection_id": "3adc1519-0d6e-4222-83de-25b457cf42be",
5
"access_token": "313c3e00-2bbc-4b2e-a9dc-13abed546b65"
6
}

Entity Webhooks

Whenever data changes inside of a platform, you will receive a webhook notifying you of the change. There are three types of changes - creates, updates, and deletes.

Every entity webhook contains a type corresponding to the entity, a code corresponding to the type of change, the entity that has been affected, and the access token related to that entity.

Entity Created

Fired after a new entity of a certain type has been created for a connection.

Example Order Created Webhook
1
{
2
"type": "ORDER",
3
"code": "ORDER_CREATED",
4
"access_token": "313c3e00-2bbc-4b2e-a9dc-13abed546b65",
5
"connection_id": "222c3e00-2bbc-4b2e-a9dc-13abed546b65",
6
"order": {
7
// ... New order object
8
}
9
}

Entity Updated

Fired after a new entity of a certain type has been updated for a connection.

Example Account Updated Webhook
1
{
2
"type": "ACCOUNT",
3
"code": "ACCOUNT_UPDATED",
4
"access_token": "313c3e00-2bbc-4b2e-a9dc-13abed546b65",
5
"connection_id": "222c3e00-2bbc-4b2e-a9dc-13abed546b65",
6
"account": {
7
// ... Updated Account object
8
}
9
}

Entity Deleted

Fired after a new entity of a certain type has been deleted for a connection. The final state of the entity before deletion is returned.

Example Bill Deleted Webhook
1
{
2
"type": "BILL",
3
"code": "BILL_DELETED",
4
"access_token": "313c3e00-2bbc-4b2e-a9dc-13abed546b65",
5
"connection_id": "222c3e00-2bbc-4b2e-a9dc-13abed546b65",
6
"bill": {
7
// ... Created Bill object
8
}
9
}

Asynchronous Operations

For some endpoints, Rutter supports the response_mode parameter. response_mode can be either prefer_sync or async.

  • prefer_sync allows you to call the API in a best-effort, synchronous fashion and wait for the response. If this takes too long we will instead return the asynchronous response. This is the default behavior if no response_mode is specified.
  • async allows you to call the API in an asynchronous fashion, receive a response immediately with a URL pointer to the asynchronous job, and wait for job updates via the URL.

Using the async response mode is recommended when:

  • You want to make idempotent operations to avoid creating duplicate requests when sending the same payload. You can accomplish this by passing an idempotency key through the Idempotency-Key request header. For more information, see Making Idempotent Requests.
  • You have specific latency requirements, and do not wish to wait multiple seconds for a response from the Rutter endpoint.
  • You want to make a large set of operations at the same time. This allows Rutter to intelligently avoid rate limits when making many requests to an external platform.

Response Mode: Prefer Sync

If you set response_mode to be prefer_sync, we will return a synchronous response if the job is completed within 30 seconds. If it takes longer, we will instead return the async_response object, which contains a URL pointer for querying the status of the job as it continues to make progress. This async_response object is the same as if you set response_mode to be async.

If you do not specify a response_mode, the default behavior is prefer_sync.

Response Mode: Async

If you set response_mode to be async, the request will be run asynchronously and the API will immediately return an async_response object. This response will have status code 202, which signifies that the job is running asynchronously. The actual response of the job will be available once it is completed. You may query the response_url field in the async_response object to track the progress of the job.

The async_response object has the following properties:

PropertyTypeDescription
idStringA unique UUID string serving as the ID of the asynchronous job.
response_urlStringA link to the Fetch a Job endpoint. You can make a GET request to this URL to retrieve the status and result of the job.
statusStringThe status of the job. One of the following:
  • prequeued
  • pending
  • success
  • failure

We suggest querying the response_url to get updates on the status of the job.

Job Webhook

Both synchronous and asynchronous requests are backed internally by an asynchronous job that executes your request. When an Asynchronous Job is successful, we will also send a webhook to your configured webhook URLs.

Asynchronous Request
1
curl -X POST "https://production.rutterapi.com/versioned/accounting/invoices?access_token=<ACCESS_TOKEN>" \
2
-H "X-Rutter-Version: 2023-02-07" \
3
-H "Authorization: Basic <YOUR_ENCODED_CLIENT_ID_AND_CLIENT_SECRET>"
Asynchronous Request Body
1
{
2
"response_mode": "async", // or "prefer_sync"
3
"invoice": {
4
"account_id": "00000000-0000-0000-0000-000000000000",
5
"customer_id": "00000000-0000-0000-0000-000000000000",
6
"due_date": "2023-01-02T02:34:56.000Z",
7
...
8
}
9
}
Asynchronous Response
1
{
2
id: "00000000-0000-0000-0000-000000000000",
3
status: "prequeued",
4
response_url: "https://production.rutterapi.com/v2/jobs/b7ed4fa7-dcc9-4f53-a994-7504a592cefe
5
}
Fetch Asynchronous Jobs
1
curl "https://production.rutterapi.com/v2/jobs/<JOB_ID>" \
2
-H "X-Rutter-Version: 2023-02-07" \
3
-H "Authorization: Basic <YOUR_ENCODED_CLIENT_ID_AND_CLIENT_SECRET>"
Asynchronous Jobs Response
1
{
2
"type": "JOB",
3
"code": "JOB_COMPLETED",
4
"job": {
5
"id": "3446b185-117f-46bc-939b-aa53de5c35c1",
6
"status": "success",
7
"request": {
8
"url": "https://production.rutterapi.com/versioned/products",
9
"method": "POST",
10
"body": {
11
// Asynchronous Job Request Body
12
}
13
},
14
"response": {
15
"http_status": 200,
16
"body": {
17
// Asynchronous Job Response Body
18
}
19
}
20
}
21
}

Idempotency

Asynchronous requests support idempotency - you can accomplish this by passing a distinct value through the Idempotency-Key request header, which serves as the unique key.

Asynchronous Job with Idempotency
1
curl -X POST "https://production.rutterapi.com/versioned/accounting/invoices?access_token=<ACCESS_TOKEN>" \
2
-H "X-Rutter-Version: 2023-02-07" \
3
-H "Authorization: Basic <YOUR_ENCODED_CLIENT_ID_AND_CLIENT_SECRET>" \
4
-H "Idempotency-Key: <IDEMPOTENCY_KEY>"

Filtering Objects

On many list endpoints, you can use the filter parameter to only return objects that match a certain criteria. Some examples of filterable endpoints are List Accounts and List Customers. To do this, you will use Rutter's custom query language as described below.

For string fields, you may use the = and ~ operators:

  • = returns all objects matching the given value for that field. Matches are case sensitive.
  • ~ returns all objects that include a word starting with the given value for that field. For example, ~ ru will return "Rutter" and "The Rutter API Company", but not "Truly". ~ is supported for any string field that is not an enum, date, or Rutter id. Matches are case insensitive.

The string value must be surrounded by double quotes (e.g., "...").

String filter example
1
filter=document_number = "1196"
2
filter=name ~ "Ru"

For number fields, you may use any of the following operators: =, <, <=, >, >=.

Number filter example 1
1
filter=balance = 100
Number filter example 2
1
filter=balance >= 0 AND balance <= 100

For timestamp fields, you may use any of the following operators: =, <, <=, >, >=. The timestamp value must be specified as a ISO 8601 timestamp or in YYYY-MM-DD format and surrounded by double quotes (e.g., "...").

Timestamp filter example 1
1
filter=created_at = "2024-04-29T20:35:14.000Z"
Timestamp filter example 2
1
filter=created_at >= "2024-03-01T20:35:14.000Z" AND created_at <= "2024-04-29T20:35:14.000Z"

Multiple conditions can be grouped together within sets of parentheses (). The boolean operators AND and OR are supported.

Compound query filter example 1
1
filter=document_number = "1196" OR (created_at >= "2024-03-01" AND created_at <= "2024-04-29")
Compound query filter example 2
1
filter=(balance >= 0 AND balance <= 100) OR (created_at >= "2024-03-01" AND created_at <= "2024-04-29")

An example using cURL to get all accounts with a balance between $0 and $100 (both inclusive) might look something like this.

cURL example
1
curl "https://production.rutterapi.com/versioned/accounting/accounts?access_token=<ACCESS_TOKEN>&filter=balance>=0+AND+balance<=100" \
2
-H "X-Rutter-Version: 2023-02-07" \
3
-H "Authorization: Basic <YOUR_ENCODED_CLIENT_ID_AND_CLIENT_SECRET>" \

Open API Spec

Have questions?

Contact support for personalized guidance.

Contact support