Skip to content

Save Xero setup

POST
/api/v1/organizations/{organization_id}/integrations/xero/setup
curl --request POST \
--url https://api.elephants.inc/api/v1/organizations/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0/integrations/xero/setup \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "asset_accounts": [ { "code": "example", "id": "example", "name": "example" } ], "bank_account_id": "example", "category_mappings": [ { "account_id": "example", "category": "example" } ], "create_new_bank_account": true, "default_expense_account_code": "example", "expense_accounts": [ { "code": "example", "id": "example", "name": "example" } ], "new_bank_account_name": "example", "other_mappings": [ { "account_id": "example", "type": "example" } ], "revenue_accounts": [ { "code": "example", "id": "example", "name": "example" } ], "sync_mode": "realtime" }'

Completes the Xero setup wizard: picks the bank account, the sync mode, the default expense account, and the account mappings, all in one write.

Set create_new_bank_account with a new_bank_account_name to have the account created in Xero first and used — otherwise bank_account_id must name an existing one. Creating it is a live write to Xero: a refusal is a 422 xero_validation_error whose message quotes Xero’s reasons, any other rejection a 502 xero_rejected, and a request that never got an answer a 502 xero_unavailable. Nothing is saved in any of the three. If Xero no longer accepts the organization’s token (the app was revoked or the tenant removed), the connection is marked disconnected and the answer is 422 xero_reconnect_required.

Mappings replace what was there, they do not merge. Sending an empty mapping list is the exception: it leaves existing mappings alone rather than deleting them, so a partial save cannot wipe a configured organization by omission. Every mapping must reference an account from the account lists supplied in the same request, every other_mappings type must be one from the mapping-options catalog, and no category or type may repeat — each violation is a 422 rather than a silent drop, coercion, or conflict. An existing bank_account_id is verified against the tenant’s bank accounts (one live Xero read) before it is stored.

Requires an active Xero connection — a live credential, not just retained configuration; a disconnected organization is a 422.

organization_id
required
string format: uuid

Organization to configure

Setup wizard data

Media typeapplication/json
SaveXeroSetupRequestBody
object
asset_accounts
Array<object>
object
code
required
string
<= 255 characters
id
required
string
<= 255 characters
name
required
string
<= 255 characters
bank_account_id

An existing Xero account. Ignored when creating a new one.

string
<= 255 characters
category_mappings
Array<object>
object
account_id
required
string
<= 255 characters
category
required
string
<= 255 characters
create_new_bank_account

Creates the account in Xero first, then uses it. Needs a name.

boolean
default_expense_account_code
string
<= 255 characters
expense_accounts
Array<object>
object
code
required
string
<= 255 characters
id
required
string
<= 255 characters
name
required
string
<= 255 characters
new_bank_account_name
string
<= 255 characters
other_mappings
Array<object>
object
account_id
required
string
<= 255 characters
type
required
string
<= 255 characters
revenue_accounts
Array<object>
object
code
required
string
<= 255 characters
id
required
string
<= 255 characters
name
required
string
<= 255 characters
sync_mode
string
Allowed values: realtime batch

The saved configuration

Media typeapplication/json
XeroOrganization
object
bank_account_id
required

The Xero account the organization’s spend is written to.

string | null
default_expense_account_code
required
string | null
id
required
string format: uuid
status
required
string
sync_mode
required

realtime posts settled transactions every few minutes; batch posts once a day.

string
xero_tenant_id
required
string
xero_tenant_name
required
string | null

Example generated

{
"bank_account_id": "example",
"default_expense_account_code": "example",
"id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
"status": "example",
"sync_mode": "example",
"xero_tenant_id": "example",
"xero_tenant_name": "example"
}

Invalid request parameters or body

Media typeapplication/json
Error

Error response. error.type is the machine-readable code — branch on it, never on error.message or the HTTP status. error.message is display-grade prose safe to render to a user; when a failure has no specific copy it falls back to a generic sentence, so it never carries a machine code. Unrecognised failures collapse to the generic unprocessable_entity code, so treat type as an open enum: match the codes you handle and fall through to a default branch for the rest.

object
error
required
object
details

Structured error details (e.g. validation errors), when present

message
required

Display-grade message, safe to render verbatim. Never a machine code — do not branch on it.

string
request_id

Correlation ID for this request (also in the x-request-id response header). Include it when contacting support.

string
step_up
Any of:
StepUpChallenge
object
delivery
required

Method used to deliver this challenge

string
expires_at
required

Challenge expiry

string | null format: date-time
id
required

Sudo session ID returned to completeStepUp

string
match_code
required

3-digit number-matching code. This response is the ORIGINATING surface: display the code; a user approving from the Ele mobile app is asked to enter it. Null only for challenges minted before match codes existed.

string | null
methods
required

Available verification methods

Array<string>
step_up_authentication_id
required

The step-up authentication this challenge was minted from — a DIFFERENT id from id (the sudo session). completeStepUp takes id as sudo_session_id; requestStepUpFallback takes this one.

string format: uuid
webauthn
required
Any of:
PasskeyAuthenticationOptions
object
allow_credentials
required
Array<object>
object
id
required
string
transports
required
Array<string>
type
required
string
challenge
required
string
challenge_id
required
string format: uuid
rp_id
required
string
timeout
required
integer
user_verification
required
string
type
required

Machine-readable error code from a stable vocabulary (open enum — handle unknown values)

string

Example generated

{
"error": {
"details": "example",
"message": "example",
"request_id": "example",
"step_up": {
"delivery": "example",
"expires_at": "2026-04-15T12:00:00Z",
"id": "example",
"match_code": "example",
"methods": [
"example"
],
"step_up_authentication_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
"webauthn": {
"allow_credentials": [
{
"id": "example",
"transports": [
"example"
],
"type": "example"
}
],
"challenge": "example",
"challenge_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
"rp_id": "example",
"timeout": 1,
"user_verification": "example"
}
},
"type": "example"
}
}

Error response. error.type is the machine-readable code — branch on it, never on error.message or the HTTP status. error.message is display-grade prose safe to render to a user; when a failure has no specific copy it falls back to a generic sentence, so it never carries a machine code. Unrecognised failures collapse to the generic unprocessable_entity code, so treat type as an open enum: match the codes you handle and fall through to a default branch for the rest.

Media typeapplication/json
Error

Error response. error.type is the machine-readable code — branch on it, never on error.message or the HTTP status. error.message is display-grade prose safe to render to a user; when a failure has no specific copy it falls back to a generic sentence, so it never carries a machine code. Unrecognised failures collapse to the generic unprocessable_entity code, so treat type as an open enum: match the codes you handle and fall through to a default branch for the rest.

object
error
required
object
details

Structured error details (e.g. validation errors), when present

message
required

Display-grade message, safe to render verbatim. Never a machine code — do not branch on it.

string
request_id

Correlation ID for this request (also in the x-request-id response header). Include it when contacting support.

string
step_up
Any of:
StepUpChallenge
object
delivery
required

Method used to deliver this challenge

string
expires_at
required

Challenge expiry

string | null format: date-time
id
required

Sudo session ID returned to completeStepUp

string
match_code
required

3-digit number-matching code. This response is the ORIGINATING surface: display the code; a user approving from the Ele mobile app is asked to enter it. Null only for challenges minted before match codes existed.

string | null
methods
required

Available verification methods

Array<string>
step_up_authentication_id
required

The step-up authentication this challenge was minted from — a DIFFERENT id from id (the sudo session). completeStepUp takes id as sudo_session_id; requestStepUpFallback takes this one.

string format: uuid
webauthn
required
Any of:
PasskeyAuthenticationOptions
object
allow_credentials
required
Array<object>
object
id
required
string
transports
required
Array<string>
type
required
string
challenge
required
string
challenge_id
required
string format: uuid
rp_id
required
string
timeout
required
integer
user_verification
required
string
type
required

Machine-readable error code from a stable vocabulary (open enum — handle unknown values)

string

Example generated

{
"error": {
"details": "example",
"message": "example",
"request_id": "example",
"step_up": {
"delivery": "example",
"expires_at": "2026-04-15T12:00:00Z",
"id": "example",
"match_code": "example",
"methods": [
"example"
],
"step_up_authentication_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
"webauthn": {
"allow_credentials": [
{
"id": "example",
"transports": [
"example"
],
"type": "example"
}
],
"challenge": "example",
"challenge_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
"rp_id": "example",
"timeout": 1,
"user_verification": "example"
}
},
"type": "example"
}
}

Error response. error.type is the machine-readable code — branch on it, never on error.message or the HTTP status. error.message is display-grade prose safe to render to a user; when a failure has no specific copy it falls back to a generic sentence, so it never carries a machine code. Unrecognised failures collapse to the generic unprocessable_entity code, so treat type as an open enum: match the codes you handle and fall through to a default branch for the rest.

Media typeapplication/json
Error

Error response. error.type is the machine-readable code — branch on it, never on error.message or the HTTP status. error.message is display-grade prose safe to render to a user; when a failure has no specific copy it falls back to a generic sentence, so it never carries a machine code. Unrecognised failures collapse to the generic unprocessable_entity code, so treat type as an open enum: match the codes you handle and fall through to a default branch for the rest.

object
error
required
object
details

Structured error details (e.g. validation errors), when present

message
required

Display-grade message, safe to render verbatim. Never a machine code — do not branch on it.

string
request_id

Correlation ID for this request (also in the x-request-id response header). Include it when contacting support.

string
step_up
Any of:
StepUpChallenge
object
delivery
required

Method used to deliver this challenge

string
expires_at
required

Challenge expiry

string | null format: date-time
id
required

Sudo session ID returned to completeStepUp

string
match_code
required

3-digit number-matching code. This response is the ORIGINATING surface: display the code; a user approving from the Ele mobile app is asked to enter it. Null only for challenges minted before match codes existed.

string | null
methods
required

Available verification methods

Array<string>
step_up_authentication_id
required

The step-up authentication this challenge was minted from — a DIFFERENT id from id (the sudo session). completeStepUp takes id as sudo_session_id; requestStepUpFallback takes this one.

string format: uuid
webauthn
required
Any of:
PasskeyAuthenticationOptions
object
allow_credentials
required
Array<object>
object
id
required
string
transports
required
Array<string>
type
required
string
challenge
required
string
challenge_id
required
string format: uuid
rp_id
required
string
timeout
required
integer
user_verification
required
string
type
required

Machine-readable error code from a stable vocabulary (open enum — handle unknown values)

string

Example generated

{
"error": {
"details": "example",
"message": "example",
"request_id": "example",
"step_up": {
"delivery": "example",
"expires_at": "2026-04-15T12:00:00Z",
"id": "example",
"match_code": "example",
"methods": [
"example"
],
"step_up_authentication_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
"webauthn": {
"allow_credentials": [
{
"id": "example",
"transports": [
"example"
],
"type": "example"
}
],
"challenge": "example",
"challenge_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
"rp_id": "example",
"timeout": 1,
"user_verification": "example"
}
},
"type": "example"
}
}

Error response. error.type is the machine-readable code — branch on it, never on error.message or the HTTP status. error.message is display-grade prose safe to render to a user; when a failure has no specific copy it falls back to a generic sentence, so it never carries a machine code. Unrecognised failures collapse to the generic unprocessable_entity code, so treat type as an open enum: match the codes you handle and fall through to a default branch for the rest.

Media typeapplication/json
Error

Error response. error.type is the machine-readable code — branch on it, never on error.message or the HTTP status. error.message is display-grade prose safe to render to a user; when a failure has no specific copy it falls back to a generic sentence, so it never carries a machine code. Unrecognised failures collapse to the generic unprocessable_entity code, so treat type as an open enum: match the codes you handle and fall through to a default branch for the rest.

object
error
required
object
details

Structured error details (e.g. validation errors), when present

message
required

Display-grade message, safe to render verbatim. Never a machine code — do not branch on it.

string
request_id

Correlation ID for this request (also in the x-request-id response header). Include it when contacting support.

string
step_up
Any of:
StepUpChallenge
object
delivery
required

Method used to deliver this challenge

string
expires_at
required

Challenge expiry

string | null format: date-time
id
required

Sudo session ID returned to completeStepUp

string
match_code
required

3-digit number-matching code. This response is the ORIGINATING surface: display the code; a user approving from the Ele mobile app is asked to enter it. Null only for challenges minted before match codes existed.

string | null
methods
required

Available verification methods

Array<string>
step_up_authentication_id
required

The step-up authentication this challenge was minted from — a DIFFERENT id from id (the sudo session). completeStepUp takes id as sudo_session_id; requestStepUpFallback takes this one.

string format: uuid
webauthn
required
Any of:
PasskeyAuthenticationOptions
object
allow_credentials
required
Array<object>
object
id
required
string
transports
required
Array<string>
type
required
string
challenge
required
string
challenge_id
required
string format: uuid
rp_id
required
string
timeout
required
integer
user_verification
required
string
type
required

Machine-readable error code from a stable vocabulary (open enum — handle unknown values)

string

Example generated

{
"error": {
"details": "example",
"message": "example",
"request_id": "example",
"step_up": {
"delivery": "example",
"expires_at": "2026-04-15T12:00:00Z",
"id": "example",
"match_code": "example",
"methods": [
"example"
],
"step_up_authentication_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
"webauthn": {
"allow_credentials": [
{
"id": "example",
"transports": [
"example"
],
"type": "example"
}
],
"challenge": "example",
"challenge_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
"rp_id": "example",
"timeout": 1,
"user_verification": "example"
}
},
"type": "example"
}
}

Too many requests

Media typeapplication/json
RateLimitedError
object
error
required
object
details

Structured error details (e.g. validation errors), when present

message
required

Display-grade message, safe to render verbatim. Never a machine code — do not branch on it.

string
request_id

Correlation ID for this request (also in the x-request-id response header). Include it when contacting support.

string
step_up
Any of:
StepUpChallenge
object
delivery
required

Method used to deliver this challenge

string
expires_at
required

Challenge expiry

string | null format: date-time
id
required

Sudo session ID returned to completeStepUp

string
match_code
required

3-digit number-matching code. This response is the ORIGINATING surface: display the code; a user approving from the Ele mobile app is asked to enter it. Null only for challenges minted before match codes existed.

string | null
methods
required

Available verification methods

Array<string>
step_up_authentication_id
required

The step-up authentication this challenge was minted from — a DIFFERENT id from id (the sudo session). completeStepUp takes id as sudo_session_id; requestStepUpFallback takes this one.

string format: uuid
webauthn
required
Any of:
PasskeyAuthenticationOptions
object
allow_credentials
required
Array<object>
object
id
required
string
transports
required
Array<string>
type
required
string
challenge
required
string
challenge_id
required
string format: uuid
rp_id
required
string
timeout
required
integer
user_verification
required
string
type
required

Machine-readable error code from a stable vocabulary (open enum — handle unknown values)

string
error
required
object
type
required
string
Allowed value: rate_limited

Example

{
"error": {
"type": "rate_limited"
}
}
Retry-After
required
string

How long to wait before retrying, expressed as delay seconds or an HTTP date

Unexpected server error

Media typeapplication/json
Error

Error response. error.type is the machine-readable code — branch on it, never on error.message or the HTTP status. error.message is display-grade prose safe to render to a user; when a failure has no specific copy it falls back to a generic sentence, so it never carries a machine code. Unrecognised failures collapse to the generic unprocessable_entity code, so treat type as an open enum: match the codes you handle and fall through to a default branch for the rest.

object
error
required
object
details

Structured error details (e.g. validation errors), when present

message
required

Display-grade message, safe to render verbatim. Never a machine code — do not branch on it.

string
request_id

Correlation ID for this request (also in the x-request-id response header). Include it when contacting support.

string
step_up
Any of:
StepUpChallenge
object
delivery
required

Method used to deliver this challenge

string
expires_at
required

Challenge expiry

string | null format: date-time
id
required

Sudo session ID returned to completeStepUp

string
match_code
required

3-digit number-matching code. This response is the ORIGINATING surface: display the code; a user approving from the Ele mobile app is asked to enter it. Null only for challenges minted before match codes existed.

string | null
methods
required

Available verification methods

Array<string>
step_up_authentication_id
required

The step-up authentication this challenge was minted from — a DIFFERENT id from id (the sudo session). completeStepUp takes id as sudo_session_id; requestStepUpFallback takes this one.

string format: uuid
webauthn
required
Any of:
PasskeyAuthenticationOptions
object
allow_credentials
required
Array<object>
object
id
required
string
transports
required
Array<string>
type
required
string
challenge
required
string
challenge_id
required
string format: uuid
rp_id
required
string
timeout
required
integer
user_verification
required
string
type
required

Machine-readable error code from a stable vocabulary (open enum — handle unknown values)

string

Example generated

{
"error": {
"details": "example",
"message": "example",
"request_id": "example",
"step_up": {
"delivery": "example",
"expires_at": "2026-04-15T12:00:00Z",
"id": "example",
"match_code": "example",
"methods": [
"example"
],
"step_up_authentication_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
"webauthn": {
"allow_credentials": [
{
"id": "example",
"transports": [
"example"
],
"type": "example"
}
],
"challenge": "example",
"challenge_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
"rp_id": "example",
"timeout": 1,
"user_verification": "example"
}
},
"type": "example"
}
}

Error response. error.type is the machine-readable code — branch on it, never on error.message or the HTTP status. error.message is display-grade prose safe to render to a user; when a failure has no specific copy it falls back to a generic sentence, so it never carries a machine code. Unrecognised failures collapse to the generic unprocessable_entity code, so treat type as an open enum: match the codes you handle and fall through to a default branch for the rest.

Media typeapplication/json
Error

Error response. error.type is the machine-readable code — branch on it, never on error.message or the HTTP status. error.message is display-grade prose safe to render to a user; when a failure has no specific copy it falls back to a generic sentence, so it never carries a machine code. Unrecognised failures collapse to the generic unprocessable_entity code, so treat type as an open enum: match the codes you handle and fall through to a default branch for the rest.

object
error
required
object
details

Structured error details (e.g. validation errors), when present

message
required

Display-grade message, safe to render verbatim. Never a machine code — do not branch on it.

string
request_id

Correlation ID for this request (also in the x-request-id response header). Include it when contacting support.

string
step_up
Any of:
StepUpChallenge
object
delivery
required

Method used to deliver this challenge

string
expires_at
required

Challenge expiry

string | null format: date-time
id
required

Sudo session ID returned to completeStepUp

string
match_code
required

3-digit number-matching code. This response is the ORIGINATING surface: display the code; a user approving from the Ele mobile app is asked to enter it. Null only for challenges minted before match codes existed.

string | null
methods
required

Available verification methods

Array<string>
step_up_authentication_id
required

The step-up authentication this challenge was minted from — a DIFFERENT id from id (the sudo session). completeStepUp takes id as sudo_session_id; requestStepUpFallback takes this one.

string format: uuid
webauthn
required
Any of:
PasskeyAuthenticationOptions
object
allow_credentials
required
Array<object>
object
id
required
string
transports
required
Array<string>
type
required
string
challenge
required
string
challenge_id
required
string format: uuid
rp_id
required
string
timeout
required
integer
user_verification
required
string
type
required

Machine-readable error code from a stable vocabulary (open enum — handle unknown values)

string

Example generated

{
"error": {
"details": "example",
"message": "example",
"request_id": "example",
"step_up": {
"delivery": "example",
"expires_at": "2026-04-15T12:00:00Z",
"id": "example",
"match_code": "example",
"methods": [
"example"
],
"step_up_authentication_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
"webauthn": {
"allow_credentials": [
{
"id": "example",
"transports": [
"example"
],
"type": "example"
}
],
"challenge": "example",
"challenge_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
"rp_id": "example",
"timeout": 1,
"user_verification": "example"
}
},
"type": "example"
}
}