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
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:
- Overview
A brief description of the Rutter entity and the set of endpoints.
- Object
A description of the object representing the associated data model.
- 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.
1https://production.rutterapi.com/versioned
1https://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 toenum
'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.
1https://production.rutterapi.com/versioned/accounting/accounts
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
Status | Situation | Notes |
---|---|---|
400 | Rutter 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. | |
401 | Rutter will return this error when a request is made to Rutter with missing or invalid credentials. Please check that you have the correct credentials. | |
404 | Rutter will return this error when an endpoint is not implemented or is not supported for a given platform. | |
409 | Rutter will return this error when a request is made that differs from another request made with the same idempotency-key. | |
410 | Rutter 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. | |
429 | Rutter 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
Status | Situation | Notes |
---|---|---|
500 | Rutter 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:
Property | Type | Description |
---|---|---|
error_type | String | The type of error that occurred. |
error_code | String | A more specific code under the error type. |
error_message | String | A string that describes the error that occurred. |
error_metadata | Object | An object containing details related to the error. Fields in here are experimental and subject to change:
|
Error Codes
For error_type
s of CONNECTION_ERROR
, the following error_code
s may be returned:
PRODUCT_NOT_READY
PRODUCT_NOT_ALLOWED
INVALID_CREDENTIALS
LINK_ERROR
ENDFORM_NOT_AVAILABLE
CONNECTION_DISABLED
NEEDS_UPDATE
NO_CREDENTIALS
PENDING
IS_DUPLICATE
ENDPOINT_NOT_SUPPORTED_FOR_YOUR_ORGANIZATION
ENTITY_NOT_READY
Pagination
When making a request for data, Rutter will paginate the returned data if the data set is too large. You'll know when pagination has occurred if the next_cursor
field is supplied. The next_cursor
is a generated base64 encoding of the timestamp and id of the next entity (e.g. an order, a transaction).
To proceed to the next page, set the query parameter cursor
to the next_cursor
value. A response without a next_cursor
field can either signify the last page or that the response was small enough where pagination was not required.
1curl "https://production.rutterapi.com/versioned/commerce/orders?access_token=<ACCESS_TOKEN>" \
2 -H "X-Rutter-Version: 2024-08-31" \
3 -H "Authorization: Basic <YOUR_ENCODED_CLIENT_ID_AND_CLIENT_SECRET>"
1{
2 "orders": [
3 {
4 "id": "0f22c735-57dd-4954-9084-68e41c17b573",
5 },
6 ...
7 ],
8 "next_cursor": "MTYxMDExOTU2Nnx8YWFlOGI4YtM2RmMi00OGZhLThhNGEtNmM1OWJkNjc3NTBm"
9 "connection": {
10 "id": "7ab6d4c4-24bb-45f9-851d-64cfdab8349e",
11 "platform": "SHOPIFY",
12 "orgId": "940dgsed-1abe-4698-8d4e-d59cf69df64c"
13 },
14}
1curl "https://production.rutterapi.com/versioned/commerce/orders?access_token=<ACCESS_TOKEN>&cursor=MTYxMDExOTU2Nnx8YWFlOGI4YtM2RmMi00OGZhLThhNGEtNmM1OWJkNjc3NTBm" \
2 -H "X-Rutter-Version: 2024-08-31" \
3 -H "Authorization: Basic <YOUR_ENCODED_CLIENT_ID_AND_CLIENT_SECRET>"
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.
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.
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.
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.
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 noresponse_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
if the job has not completed yet, which signifies that the job is still running asynchronously. The status code will update to the response of the job 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:
Property | Type | Description |
---|---|---|
id | String | A unique UUID string serving as the ID of the asynchronous job. |
response_url | String | A 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. |
status | String | The status of the job. One of the following:
|
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.
1curl -X POST "https://production.rutterapi.com/versioned/accounting/invoices?access_token=<ACCESS_TOKEN>" \
2 -H "X-Rutter-Version: 2024-08-31" \
3 -H "Authorization: Basic <YOUR_ENCODED_CLIENT_ID_AND_CLIENT_SECRET>"
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}
1{
2 id: "00000000-0000-0000-0000-000000000000",
3 status: "prequeued",
4 response_url: "https://production.rutterapi.com/versioned/jobs/b7ed4fa7-dcc9-4f53-a994-7504a592cefe
5}
1curl "https://production.rutterapi.com/versioned/jobs/<JOB_ID>" \
2 -H "X-Rutter-Version: 2024-08-31" \
3 -H "Authorization: Basic <YOUR_ENCODED_CLIENT_ID_AND_CLIENT_SECRET>"
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.
1curl -X POST "https://production.rutterapi.com/versioned/accounting/invoices?access_token=<ACCESS_TOKEN>" \
2 -H "X-Rutter-Version: 2024-08-31" \
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., "..."
).
At this time, strings with double quotes "
cannot be used in filters.
1filter=document_number = "1196"
2filter=name ~ "Ru"
For number
fields, you may use any of the following operators: =
, <
, <=
, >
, >=
.
1filter=balance = 100
1filter=balance >= 0 AND balance <= 100
For string
fields representing timestamps (such as created_at
or updated_at
), you may use any of the following operators: =
, <
, <=
, >
, >=
. The timestamp value must be specified as a ISO 8601 string or in YYYY-MM-DD
format and surrounded by double quotes (e.g., "..."
).
1filter=created_at = "2024-04-29T20:35:14.000Z"
1filter=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. AND
and OR
operators have the same precedence, so please ensure you use parentheses as necessary for your desired output.
1filter=document_number = "1196" OR (created_at >= "2024-03-01" AND created_at <= "2024-04-29")
1filter=(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.
1curl "https://production.rutterapi.com/versioned/accounting/accounts?access_token=<ACCESS_TOKEN>&filter=balance>=0+AND+balance<=100" \
2 -H "X-Rutter-Version: 2024-08-31" \
3 -H "Authorization: Basic <YOUR_ENCODED_CLIENT_ID_AND_CLIENT_SECRET>" \
Open API Spec
Have questions?
Contact support for personalized guidance.