HTTP API reference
Authentication, request schemas, responses, and retry rules for Jupid's business API.
The API adds a programmatic source alongside Plaid and CSV. Your backend connects a user and business, receives their Jupid IDs, and sends accounts and transactions. No browser session or user interaction is required.
New to the API? Follow the API quickstart to sign a token, connect a business, and upload an account and transaction.
Use the backend URL and signing configuration Jupid supplies for your
environment. The base path is /api/v1. The
OpenAPI document is served by
the backend; use that same path on your staging backend when testing. Confirm
API enablement for the target environment before integration.
Connect a user and business
Sign an HS256 JWT using the
Embed claim format. Keep the
signing secret on your server. sub identifies the person, and stays the
same when that person switches businesses. Send their complete current list
of businesses and the selected business ID:
{
"business_id": "business-123",
"businesses": [
{ "id": "business-123", "name": "Acme Studio", "role": "owner" },
{ "id": "business-456", "name": "Acme Shop", "role": "member" }
],
"tier": "paid"
}| Field | Required | Description |
|---|---|---|
business_id | Yes | Selected external business ID. Must occur in businesses; use null only for an empty list. |
businesses | Yes | Complete list of businesses currently available to this person. IDs must be unique. An empty list revokes all their business access for this partner. |
businesses[].id | Yes | Your stable business ID, 1–200 characters. Shared by all users of that business, scoped to your partner ID. |
businesses[].name | Yes | Business name, 1–120 characters after trimming. |
businesses[].role | Yes | This person's role: owner or member. Multiple owners are equal; a business can initially have only members. |
tier | No | free or paid, according to your partner agreement. Applied to each listed business. |
Jupid creates missing businesses and reconciles this person's memberships and roles together. Omitting a previously listed business revokes access to it. Other people's access, native Jupid companies, and other partners' memberships are unaffected. Adding a person to an existing business gives them its existing financial history; it does not copy or move records.
Send the actual role at every authorization. Owners can perform owner-only
financial operations; members can use shared financial data. Access and role
changes for these companies are managed by your backend, including when the
person is an owner. There is no viewer role or automatic promotion of the
first person to owner.
Send the agreed tier consistently across users of a business. Each connection replaces the source's saved tier; omission does not preserve a previous value. Partner sponsorship applies in the listed company context and does not create a personal Stripe subscription. This payload contains no account snapshot and rejects undocumented fields. Upload financial history through the data routes below.
Send the token in two headers, with no request body:
curl --request POST "$JUPID_BACKEND_URL/api/v1/auth/partner" \
--header "X-Jupid-Partner: $JUPID_PARTNER_ID" \
--header "Authorization: Bearer $JUPID_TOKEN"{
"user_id": "10000000-0000-4000-8000-000000000001",
"organization_id": "10000000-0000-4000-8000-000000000002",
"source_id": "10000000-0000-4000-8000-000000000003",
"organizations": [
{
"business_id": "business-123",
"organization_id": "10000000-0000-4000-8000-000000000002",
"source_id": "10000000-0000-4000-8000-000000000003",
"role": "owner"
},
{
"business_id": "business-456",
"organization_id": "10000000-0000-4000-8000-000000000004",
"source_id": "10000000-0000-4000-8000-000000000005",
"role": "member"
}
]
}Save these IDs. Repeated calls reuse the same user, organization, and source.
Different people with the same partner business ID share its organization and
source. Top-level organization_id and source_id refer to the selected
business; organizations maps the complete supplied list.
| ID | Meaning | Use it for |
|---|---|---|
user_id | The mapped Jupid person. | Your integration's user mapping. |
organization_id | The internal Jupid business shared by its members. | The business segment of each HTTP URL. |
source_id | Your API connection within that business. | Uploading accounts. |
Data requests require both the signed business_id and a current unrevoked
membership matching the URL's organization_id. Changing the URL or sending
an old role does not grant access. The selected company in the Jupid UI does
not retarget HTTP requests. Data requests do not synchronize memberships; call
the authorization endpoint when access changes.
Switching and revoking access
To switch, keep sub unchanged, change business_id, and send the complete
businesses list again. An embedded login with this token activates that
company through the same membership selection used by Jupid. For a partner
host switch, destroy and remount the embed with a freshly issued token.
Calling the HTTP authorization endpoint alone does not switch the UI.
To remove all access, your backend can authorize this payload without opening an iframe:
{ "business_id": null, "businesses": [] }The response retains user_id, returns organizations: [], and sets
organization_id and source_id to null. Opening an embedded session with
this payload applies the revocations and returns HTTP 403.
After reconciliation, subsequent server checks deny removed memberships, including data requests carrying an older token. Remaining members retain the company's financial history. Reloading and reauthorizing the iframe applies a fresh list; reloading alone does not obtain revocations from your system. There is no background Linker permission feed or automatic token refresh loop. Previously displayed or downloaded data cannot be withdrawn by reauthorization.
JWT expiration is checked at authorization and on each HTTP API request. It
does not expire an already created Jupid browser session. Tokens remain
replayable until exp: an older still-valid token sent to the authorization
endpoint can reapply its access list. Jupid applies authorizations in commit
order, without ordering them by iat or consuming jti. Issue short-lived
fresh tokens and serialize authorization changes for each person.
Existing mappings keep their original Jupid sign-in email. A new partner identity is not attached to another Jupid user merely because their emails match; contact Jupid if connecting that identity fails.
Upload accounts
POST /organizations/{organization_id}/accounts
Send Content-Type: application/json with a source_id and an array of
accounts. One account is an array of one item.
{
"source_id": "10000000-0000-4000-8000-000000000003",
"accounts": [
{
"external_id": "checking-123",
"name": "Operating Checking",
"type": "depository",
"currency": "USD",
"current_balance": 12000.34
}
]
}All five account fields are required:
| Field | Type | Accepted value |
|---|---|---|
external_id | string | Your stable account ID, 1–256 characters. |
name | string | Account name, 1–256 characters after trimming whitespace. |
type | string | depository, credit, loan, investment, brokerage, or other. |
currency | string | Three uppercase letters, such as USD. |
current_balance | number | Balance in currency units, with at most two decimal places. |
Balances and transaction amounts must be between −9,999,999,999,999.99 and 9,999,999,999,999.99. Upload bodies reject fields outside the documented schema.
{
"accounts": [
{
"id": "10000000-0000-4000-8000-000000000004",
"external_id": "checking-123"
}
]
}Account uploads target the connected API source returned by the connection
endpoint. Use the returned account id for transaction requests.
Upload transactions
POST /organizations/{organization_id}/accounts/{account_id}/transactions
{
"transactions": [
{
"external_id": "bank-tx-456",
"amount": -42.50,
"date": "2026-09-08",
"description": "Office supplies",
"pending": false,
"currency": "USD"
}
]
}| Field | Required | Accepted value |
|---|---|---|
external_id | Yes | Your stable transaction ID, 1–256 characters. |
amount | Yes | Number in currency units, with at most two decimal places. Same range as account balances. |
date | Yes | A real calendar date in YYYY-MM-DD format. |
description | Yes | Transaction description, 1–4,096 characters. |
pending | No | Boolean; defaults to false. |
currency | No | Three uppercase letters; defaults to the account currency. |
- Amounts use Jupid's signs: positive incoming, negative outgoing. Send dollars for USD, not cents. At most two decimal places are accepted. Plaid transaction amounts use the opposite signs and need conversion.
dateis a real calendar date inYYYY-MM-DDformat. Jupid sets both authorized and processed timestamps to midnight UTC on that date.- Omitting
pendingmeansfalse. Omittingcurrencyuses the account's currency. Send the complete current transaction when updating it.
The API stores the currency you supply; it does not convert amounts between currencies.
{
"transactions": [
{
"id": "10000000-0000-4000-8000-000000000005",
"external_id": "bank-tx-456"
}
]
}The account must belong to a connected API source in this business.
Repeats, updates, and initial history
Keep each account and transaction external_id stable. IDs are unique within
the source and business; transaction IDs must be unique across all accounts
in that source. An existing transaction cannot move to another account.
Idempotency comes from keeping the same external_id for each record.
Sending an existing ID updates its bank fields and preserves its Jupid ID.
Response mappings are keyed by external_id; do not rely on response order.
Missing records in a request are left unchanged. Duplicate IDs within one
batch are rejected.
Each upload accepts 1–500 records, with at most 1 MiB per HTTP request. For example, 50,000 transactions fit into 100 requests of 500 records if each request meets the byte limit. Send changes for the same account sequentially; different businesses can upload independently.
A successful transaction response means the batch was saved and its background-processing event was accepted. Categorization runs through normal Jupid processing after the response. The response does not wait for AI work. Paid-tier processing follows the existing partner agreement; unpaid processing keeps Jupid's normal limit.
Retry the same batch with backoff after a network failure or HTTP 500, including when you did not receive a response. A retry also resends the processing event. Reduce the batch on HTTP 413; correct invalid fields on 422.
Upserts preserve filing state, comments, context, categories, counterparties, and the transaction's deletion flag. These fields cannot be supplied in an upload. Resending a deleted transaction does not restore it.
Delete records
| Request | Result |
|---|---|
DELETE /organizations/{organization_id}/accounts/{account_id} | Disconnect the account and retain transaction history. Requires the business owner. The source is revoked if no active accounts remain. |
DELETE /organizations/{organization_id}/transactions/{transaction_id} | Soft-delete the transaction. |
Both return HTTP 200. For example, deleting a transaction returns:
{
"transaction_id": "10000000-0000-4000-8000-000000000005",
"deleted": true
}Account deletion returns account_id instead. Repeating a deletion succeeds.
After disconnecting the last account, call the connection endpoint again to
reconnect its API source before uploading accounts. Uploading an account again
reactivates it; resending a deleted transaction does not restore it.
The delete operations follow the existing Jupid permissions for records in the business; they are not limited to API-source records. Demo users cannot call any of the four upload/delete operations.
Existing Jupid operations
The API also exposes the thirteen existing
MCP operations. Paths below are relative to
/api/v1/organizations/{organization_id}. Inputs and responses match their
MCP operations, including existing owner-only restrictions. Account deletion
requires the business owner. /billing reports the authenticated person's own
billing status for either role; it does not expose the organization owner's billing.
| HTTP request | Operation |
|---|---|
GET /profile | getProfile |
GET /accounts | listAccounts |
GET /categories | listCategories |
GET /transactions | searchTransactions |
GET /transactions/{transaction_id} | getTransaction |
GET /counterparties | searchCounterparties |
GET /counterparties/{counterparty_id} | getCounterparty |
GET /reports/cashflow | getCashflowReport |
GET /reports/pnl | getPnlReport |
GET /outstanding-issues | listOutstandingIssues |
POST /outstanding-issues/{id}/answers | answerOutstandingIssue |
GET /agent-connection | getAgentConnectionInfo |
GET /billing | getBillingStatus |
Billing status reports synced Stripe subscriptions. Partner-provided paid access is separate and is not reflected in this response.
GET inputs use query parameters with existing camelCase field names. Encode
arrays as JSON, for example accountIds=["..."]; numbers and booleans are
also JSON values, such as limit=20 and includeInactive=true. URL-encode
these values. The answer endpoint accepts answer and optional
selectedOption in a JSON body; the issue ID goes in the path.
Existing MCP OAuth access tokens can call the organization HTTP routes without
X-Jupid-Partner; they cannot call partner connection. MCP authentication is
unchanged. The four ingestion operations are also
MCP tools, using the user's active Jupid
business.
Errors
Errors return a JSON error string. Schema validation errors also include
issues; malformed JSON returns an error without that list.
{
"error": "Connected API source not found"
}| Status | Action |
|---|---|
400 | Read the operation error, such as an unavailable source or ID belonging to another account. |
401 | Supply valid authorization or refresh an expired token. |
403 | Check the connected user, signed business, and current membership. |
404 | Check the route. |
413 | Send a smaller request. |
415 | Set Content-Type: application/json on data POST requests. |
422 | Correct the JSON or fields. |
500 | Retry with backoff; contact Jupid if the same request keeps failing. |
For support, include the HTTP method, path, status, and error message, along with your partner ID and environment. Do not send signing secrets or bearer tokens.