Skip to main content

Overview

The POST /v1/accounts endpoint accepts the full account onboarding graph in a single request. All children (addresses, contacts, payment methods, tags) are written in one database transaction. Any child failure rolls back the parent account and every already-inserted child — you get a complete record or nothing. The 201 response includes all children hydrated inline; no follow-up GET calls are needed.

Minimal create

Only name and account_type are required.
curl -X POST https://api.arcuserp.com/v1/accounts \
  -H "Authorization: Bearer ark_live_ent_acme_xxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "account_type": "business",
    "name": "Acme Wholesale"
  }'
import Arcus from "@arcuserp/sdk";

const arcus = new Arcus({ apiKey: process.env.ARCUS_API_KEY });

const account = await arcus.accounts.create({
  account_type: "business",
  name: "Acme Wholesale",
});
import arcuserp

client = arcuserp.ArcusERP(api_key=os.environ["ARCUS_API_KEY"])

account = client.accounts.create(
    account_type="business",
    name="Acme Wholesale",
)

Full B2B onboarding graph

One POST creates the account, 3 addresses, 2 contacts, a payment method, and 2 tags.
curl -X POST https://api.arcuserp.com/v1/accounts \
  -H "Authorization: Bearer ark_live_ent_acme_xxxx" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: acme-wholesale-onboard-2026-05-12" \
  -d '{
    "account_type": "business",
    "name": "Acme Wholesale",
    "email": "ap@acme.example",
    "phone": "+1-555-0100",
    "credit_limit": 25000.00,
    "addresses": [
      {
        "type": "billing",
        "label": "HQ",
        "is_default_billing": true,
        "street1": "1 Acme Way",
        "city": "Austin",
        "region": "TX",
        "postal_code": "78701",
        "country": "US"
      },
      {
        "type": "shipping",
        "label": "Main Warehouse",
        "is_default_shipping": true,
        "street1": "100 Industrial Dr",
        "city": "Austin",
        "region": "TX",
        "postal_code": "78702",
        "country": "US"
      },
      {
        "type": "shipping",
        "label": "Branch Warehouse",
        "street1": "200 Branch Rd",
        "city": "Houston",
        "region": "TX",
        "postal_code": "77001",
        "country": "US"
      }
    ],
    "contacts": [
      {
        "first_name": "Jane",
        "last_name": "Doe",
        "email": "jane@acme.example",
        "phone": "+1-555-0101",
        "is_primary": true,
        "role": "AP"
      },
      {
        "first_name": "John",
        "last_name": "Smith",
        "email": "john@acme.example",
        "role": "Operations"
      }
    ],
    "payment_methods": [
      {
        "stripe_payment_method_id": "pm_card_visa",
        "is_default": true
      }
    ],
    "tags": ["VIP", "Wholesale-Tier-1"]
  }'
const account = await arcus.accounts.create({
  account_type: "business",
  name: "Acme Wholesale",
  email: "ap@acme.example",
  phone: "+1-555-0100",
  credit_limit: 25000.0,
  addresses: [
    {
      type: "billing",
      label: "HQ",
      is_default_billing: true,
      street1: "1 Acme Way",
      city: "Austin",
      region: "TX",
      postal_code: "78701",
      country: "US",
    },
    {
      type: "shipping",
      label: "Main Warehouse",
      is_default_shipping: true,
      street1: "100 Industrial Dr",
      city: "Austin",
      region: "TX",
      postal_code: "78702",
      country: "US",
    },
    {
      type: "shipping",
      label: "Branch Warehouse",
      street1: "200 Branch Rd",
      city: "Houston",
      region: "TX",
      postal_code: "77001",
      country: "US",
    },
  ],
  contacts: [
    {
      first_name: "Jane",
      last_name: "Doe",
      email: "jane@acme.example",
      phone: "+1-555-0101",
      is_primary: true,
      role: "AP",
    },
    {
      first_name: "John",
      last_name: "Smith",
      email: "john@acme.example",
      role: "Operations",
    },
  ],
  payment_methods: [
    {
      stripe_payment_method_id: "pm_card_visa",
      is_default: true,
    },
  ],
  tags: ["VIP", "Wholesale-Tier-1"],
});
account = client.accounts.create(
    account_type="business",
    name="Acme Wholesale",
    email="ap@acme.example",
    phone="+1-555-0100",
    credit_limit=25000.00,
    addresses=[
        {
            "type": "billing",
            "label": "HQ",
            "is_default_billing": True,
            "street1": "1 Acme Way",
            "city": "Austin",
            "region": "TX",
            "postal_code": "78701",
            "country": "US",
        },
        {
            "type": "shipping",
            "label": "Main Warehouse",
            "is_default_shipping": True,
            "street1": "100 Industrial Dr",
            "city": "Austin",
            "region": "TX",
            "postal_code": "78702",
            "country": "US",
        },
        {
            "type": "shipping",
            "label": "Branch Warehouse",
            "street1": "200 Branch Rd",
            "city": "Houston",
            "region": "TX",
            "postal_code": "77001",
            "country": "US",
        },
    ],
    contacts=[
        {
            "first_name": "Jane",
            "last_name": "Doe",
            "email": "jane@acme.example",
            "phone": "+1-555-0101",
            "is_primary": True,
            "role": "AP",
        },
        {
            "first_name": "John",
            "last_name": "Smith",
            "email": "john@acme.example",
            "role": "Operations",
        },
    ],
    payment_methods=[
        {
            "stripe_payment_method_id": "pm_card_visa",
            "is_default": True,
        }
    ],
    tags=["VIP", "Wholesale-Tier-1"],
)
Expected 201 response (truncated):
{
  "data": {
    "id": "a1b2c3d4-...",
    "account_type": "business",
    "display_name": "Acme Wholesale",
    "account_number": "CUST-00042",
    "credit_limit": 25000.0,
    "credit_balance": 25000.0,
    "addresses": [
      { "id": "...", "type": "billing",  "label": "HQ",             "line1": "1 Acme Way",      "is_default_billing": true,  ... },
      { "id": "...", "type": "shipping", "label": "Main Warehouse",  "line1": "100 Industrial Dr", "is_default_shipping": true, ... },
      { "id": "...", "type": "shipping", "label": "Branch Warehouse", "line1": "200 Branch Rd",  ...                          }
    ],
    "contacts": [
      { "id": "...", "first_name": "Jane", "last_name": "Doe",   "is_primary": true,  "title": "AP",         ... },
      { "id": "...", "first_name": "John", "last_name": "Smith", "is_primary": false, "title": "Operations",  ... }
    ],
    "payment_methods": [
      { "id": "...", "type": "card", "stripe_payment_method_id": "pm_card_visa", "last_four": "4242", "is_default": true, ... }
    ],
    "tags": [
      { "id": "...", "name": "VIP",              "slug": "vip" },
      { "id": "...", "name": "Wholesale-Tier-1", "slug": "wholesale_tier_1" }
    ]
  }
}

Address field aliases

The endpoint accepts Stripe-style field names as aliases for the internal column names:
Public API fieldInternal column
street1line1
street2line2
regionstate
postal_codezip
Both sets of names are accepted; canonical names (line1, state, zip) are returned in all responses.

Default billing and shipping flags

  • Exactly one billing address may have is_default_billing: true.
  • Exactly one shipping address may have is_default_shipping: true.
  • If no address explicitly sets the flag, the first address of each type is auto-defaulted.
  • Sending more than one is_default_billing: true (or is_default_shipping: true) returns 422.

Contact role vs title

The role field in the API payload is stored internally as title. Both field names are accepted in the request body; title is returned in responses.

Payment methods: test mode

In test mode, use Stripe test card tokens:
  • pm_card_visa — Visa, succeeds
  • pm_card_mastercard — Mastercard, succeeds
  • pm_card_visa_chargeDeclined — always fails
Stripe must be configured for your entity. If Stripe is not configured, payment_methods[] will return an error; omit the array if you are testing without Stripe.

Credit limit

credit_limit is a JS number (float) in the response, never a Postgres NUMERIC string. credit_balance on a new account equals credit_limit (no outstanding AR yet).

Tags: auto-create

Tags supplied by name are auto-created for the entity if they do not exist. The name is slugified (lowercased, non-alphanumeric chars replaced by _) to form the slug. Applying the same tag name twice in tags[] is idempotent.

Error envelope

All errors follow the canonical envelope:
{
  "error": "missing_required_field",
  "code": "missing_required_field",
  "type": "validation_error",
  "hint": "Account 'name' is required.",
  "param": "name"
}
Common validation errors:
error codeparamdescription
missing_required_fieldnamename (or an alias) not provided
invalid_enumaccount_typeaccount_type not one of: business, individual, vendor, lead
invalid_enumaddresses[N].typeaddress type must be billing or shipping
invalid_valueaddresses[N].countrycountry must be ISO 3166-1 alpha-2
invalid_valueaddresses[].is_default_billingmore than one billing default
invalid_valueaddresses[].is_default_shippingmore than one shipping default
invalid_valuecontacts[].is_primarymore than one primary contact
missing_required_fieldpayment_methods[N].stripe_payment_method_idPM token missing
forbidden_fieldentity_identity_id may not be set in request body

Idempotency

Supply Idempotency-Key: <unique-string> to safely retry creates without duplicating records. The key is honored for 24 hours.
  • GET /v1/accounts/:id?expand[]=addresses,contacts — retrieve with children expanded
  • POST /v1/accounts/:id/addresses — add an address to an existing account
  • POST /v1/accounts/:id/contacts — add a contact to an existing account
  • POST /v1/accounts/:id/payment-methods — attach a payment method to an existing account
  • POST /v1/accounts/:id/merge — merge two accounts