Skip to main content
Support Center

Creating Orders via API: the Open (Created) Status

This is an advanced, developer-focused guide. It assumes you’re comfortable making authenticated HTTP requests and are building an app or integration on top of the Jumpseller API — not just using the Admin Panel. If you’re looking for the point-and-click version of this feature, see Manual Orders instead.

The POST /v1/orders.json endpoint lets you create an order programmatically, on behalf of a customer. Most integrations set status explicitly (Paid, Pending Payment, Canceled, Abandoned). There is a fifth state that unlocks a very useful pattern: omit the status field entirely, and the order is created as Created — the same state the Admin Panel labels “Open” in its Orders filter.

Why this matters

An order in Created/Open status is not paid and not abandoned — it’s a live cart tied to a real customer, sitting in Jumpseller, waiting to be completed. Jumpseller automatically emails the customer a checkout link and a payment QR code, so they can finish the purchase themselves through Jumpseller’s own checkout — you don’t have to build a payment UI.

That single mechanic is the building block for several integration patterns.

Use cases

1. Assisted / offline sales (phone, WhatsApp, live chat, in-store)

A sales rep or a chatbot builds the cart on the customer’s behalf and creates the order via API with no status. The customer receives an email with a “Complete Payment” button and a QR code — no custom checkout has to be built by your app. This is the API equivalent of the Admin Panel’s “send a checkout link” manual order flow, but automatable and scriptable.

2. Marketplace / external channel connectors

When you sync orders in from an external channel (marketplace, POS, subscription platform) and the payment hasn’t been resolved yet on the Jumpseller side, create the order as Created/Open first. Once your integration confirms payment externally, transition it with a follow-up PUT to Paid or Pending Payment.

3. Pre-orders, backorders, and quote-to-order flows

Build the order as soon as a quote is accepted or a backordered product is requested, leave it Open, and only flip it to Paid/Pending Payment once stock is confirmed or the merchant approves it. The customer already has a checkout link sitting in their inbox for when it’s time to pay.

Requirements before creating an order

  • The order requires a customer.id — an email alone is not enough. Sending customer: { "email": "..." } without an id, even for a customer that already exists, returns "Account not found". There is no auto-create-by-email shortcut: look the customer up first (GET /v1/customers.json?email=...), create them with POST /v1/customers.json if they don’t exist yet, and always send their numeric id in the order payload.
  • If shipping_required is true, the customer must already have a shipping address on file. Jumpseller validates this at order-creation time — the shipping_address block you send inline is used for display, but the customer record needs at least one saved address, or the request is rejected. Add a shipping_address when creating or updating the customer if none exists.
  • Set shipping_required: false for digital/virtual products or appointments, and the address requirement disappears entirely — confirmed with a customer that had zero saved addresses. This isn’t tied to the product’s type (digital, appointment, etc.); it’s the shipping_required flag on the order itself that Jumpseller checks. Omit shipping_method_name/shipping_price too, since there’s nothing to ship.

How shipping_required gets decided when you don’t send it

If you omit shipping_required entirely, Jumpseller derives it for the whole cart, not per line item — tested against a customer with zero saved addresses:

Cart contents shipping_required sent Result
1 digital product only (omitted) Derived as false200 OK, no address needed
1 appointment product only (omitted) Derived as false200 OK, no address needed
1 physical product only (omitted) Derived as true400 Customer without shipping address
1 physical + 1 digital product (mixed) (omitted) Derived as true400 Customer without shipping address
1 digital product true (forced) 400 Customer without shipping address, despite the product being digital
1 physical product false (forced) 200 OK, address requirement skipped even for a physical product

Two takeaways: a single physical item in the cart is enough to require an address, even if everything else is digital — so a fully address-free flow only works for carts with no physical products. And the shipping_required value you explicitly send always wins over what the product types would otherwise imply, in both directions.

  • Prefer shipping_method_name + shipping_price over shipping_method_id. The _id form triggers coverage-area validation by municipality, which often rejects perfectly valid orders. The name-based form skips that check entirely.
  • region must be a code, not a name (e.g. "12", not "Metropolitana de Santiago").
  • Products are referenced by numeric id (or variant_id for variants), not by SKU.

Full example: a brand-new customer

Since the order endpoint needs a customer.id (see above), creating an order for someone who has never bought from the store before is a three-step flow: look up, create-if-missing, then create the order.

cURL (three requests)

# 1. Look up the customer by email
curl -s -u "$JUMPSELLER_LOGIN:$JUMPSELLER_AUTH_TOKEN" \
  "https://api.jumpseller.com/v1/customers.json?email=new.customer@example.com&limit=1"

# 2. Not found? Create them with a shipping address
curl -s -u "$JUMPSELLER_LOGIN:$JUMPSELLER_AUTH_TOKEN" \
  -X POST "https://api.jumpseller.com/v1/customers.json" \
  -H "Content-Type: application/json" \
  -d '{
    "customer": {
      "email": "new.customer@example.com",
      "fullname": "New Customer",
      "status": "approved",
      "shipping_address": {
        "name": "New", "surname": "Customer",
        "address": "123 Main St", "city": "Santiago",
        "region": "12", "country": "CL", "municipality": "Santiago"
      }
    }
  }'
# => returns the new customer, e.g. {"customer": {"id": 20983999, ...}}

# 3. Create the order using the id from step 1 or 2
curl -s -u "$JUMPSELLER_LOGIN:$JUMPSELLER_AUTH_TOKEN" \
  -X POST "https://api.jumpseller.com/v1/orders.json" \
  -H "Content-Type: application/json" \
  -d '{
    "order": {
      "shipping_method_name": "Standard Shipping",
      "shipping_price": 0,
      "shipping_required": true,
      "customer": { "id": 20983999 },
      "products": [ { "id": 34745971, "qty": 1, "price": 2000.0 } ]
    }
  }'

Ruby (end-to-end)

require 'net/http'
require 'json'
require 'uri'

def api(method, path, token_pair, body = nil)
  uri = URI("https://api.jumpseller.com/v1#{path}")
  request = Net::HTTP.const_get(method.to_s.capitalize).new(uri)
  request.basic_auth(*token_pair)
  if body
    request['Content-Type'] = 'application/json'
    request.body = body.to_json
  end
  JSON.parse(Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(request) }.body)
end

auth = [ENV['JUMPSELLER_LOGIN'], ENV['JUMPSELLER_AUTH_TOKEN']]
email = 'new.customer@example.com'

found = api(:get, "/customers.json?email=#{email}&limit=1", auth)
customer = found.find { |c| c['customer']['email'] == email }&.dig('customer')

customer ||= api(:post, '/customers.json', auth, {
  customer: {
    email: email,
    fullname: 'New Customer',
    status: 'approved',
    shipping_address: {
      name: 'New', surname: 'Customer',
      address: '123 Main St', city: 'Santiago',
      region: '12', country: 'CL', municipality: 'Santiago'
    }
  }
})['customer']

order = api(:post, '/orders.json', auth, {
  order: {
    shipping_method_name: 'Standard Shipping',
    shipping_price: 0,
    shipping_required: true,
    customer: { id: customer['id'] },
    products: [{ id: 34745971, qty: 1, price: 2000.0 }]
    # no "status" key => order is created as "Created" / Open
  }
})

puts order.dig('order', 'status_enum') # => "created"

Python (end-to-end)

import os
import requests

AUTH = (os.environ["JUMPSELLER_LOGIN"], os.environ["JUMPSELLER_AUTH_TOKEN"])
BASE = "https://api.jumpseller.com/v1"
email = "new.customer@example.com"

found = requests.get(f"{BASE}/customers.json", auth=AUTH, params={"email": email, "limit": 1}).json()
customer = next((c["customer"] for c in found if c["customer"]["email"] == email), None)

if customer is None:
    customer = requests.post(
        f"{BASE}/customers.json",
        auth=AUTH,
        json={
            "customer": {
                "email": email,
                "fullname": "New Customer",
                "status": "approved",
                "shipping_address": {
                    "name": "New", "surname": "Customer",
                    "address": "123 Main St", "city": "Santiago",
                    "region": "12", "country": "CL", "municipality": "Santiago",
                },
            }
        },
    ).json()["customer"]

order = requests.post(
    f"{BASE}/orders.json",
    auth=AUTH,
    json={
        "order": {
            "shipping_method_name": "Standard Shipping",
            "shipping_price": 0,
            "shipping_required": True,
            "customer": {"id": customer["id"]},
            "products": [{"id": 34745971, "qty": 1, "price": 2000.0}],
            # no "status" key => order is created as "Created" / Open
        }
    },
).json()["order"]

print(order["status_enum"])  # => "created"

The same three-step shape applies in Node.js and PHP — GET the customer by email, POST to create one if none matches, then POST the order with the resolved id, following the same request format shown in the minimal example below.

Minimal example: creating an Open order

The only difference from a normal order-creation call is what you leave out: no status key anywhere in the payload.

cURL

curl -u "$JUMPSELLER_LOGIN:$JUMPSELLER_AUTH_TOKEN" \
  -X POST "https://api.jumpseller.com/v1/orders.json" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "order": {
      "shipping_method_name": "Standard Shipping",
      "shipping_price": 0,
      "shipping_required": true,
      "customer": { "id": 20983951 },
      "products": [
        { "id": 34745971, "qty": 1, "price": 2000.0 }
      ]
    }
  }'

Ruby

require 'net/http'
require 'json'
require 'uri'

uri = URI('https://api.jumpseller.com/v1/orders.json')
request = Net::HTTP::Post.new(uri)
request.basic_auth(ENV['JUMPSELLER_LOGIN'], ENV['JUMPSELLER_AUTH_TOKEN'])
request['Content-Type'] = 'application/json'

request.body = {
  order: {
    shipping_method_name: 'Standard Shipping',
    shipping_price: 0,
    shipping_required: true,
    customer: { id: 20983951 },
    products: [
      { id: 34745971, qty: 1, price: 2000.0 }
    ]
    # no "status" key => order is created as "Created" / Open
  }
}.to_json

response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(request) }
order = JSON.parse(response.body)
puts order.dig('order', 'status_enum') # => "created"

Python

import os
import requests

response = requests.post(
    "https://api.jumpseller.com/v1/orders.json",
    auth=(os.environ["JUMPSELLER_LOGIN"], os.environ["JUMPSELLER_AUTH_TOKEN"]),
    json={
        "order": {
            "shipping_method_name": "Standard Shipping",
            "shipping_price": 0,
            "shipping_required": True,
            "customer": {"id": 20983951},
            "products": [
                {"id": 34745971, "qty": 1, "price": 2000.0}
            ],
            # no "status" key => order is created as "Created" / Open
        }
    },
)
order = response.json()["order"]
print(order["status_enum"])  # => "created"

Node.js

const auth = Buffer.from(
  `${process.env.JUMPSELLER_LOGIN}:${process.env.JUMPSELLER_AUTH_TOKEN}`
).toString('base64');

const response = await fetch('https://api.jumpseller.com/v1/orders.json', {
  method: 'POST',
  headers: {
    Authorization: `Basic ${auth}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    order: {
      shipping_method_name: 'Standard Shipping',
      shipping_price: 0,
      shipping_required: true,
      customer: { id: 20983951 },
      products: [{ id: 34745971, qty: 1, price: 2000.0 }],
      // no "status" key => order is created as "Created" / Open
    },
  }),
});

const { order } = await response.json();
console.log(order.status_enum); // => "created"

PHP

<?php
$ch = curl_init('https://api.jumpseller.com/v1/orders.json');

$payload = [
    'order' => [
        'shipping_method_name' => 'Standard Shipping',
        'shipping_price' => 0,
        'shipping_required' => true,
        'customer' => ['id' => 20983951],
        'products' => [
            ['id' => 34745971, 'qty' => 1, 'price' => 2000.0],
        ],
        // no "status" key => order is created as "Created" / Open
    ],
];

curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_USERPWD => getenv('JUMPSELLER_LOGIN') . ':' . getenv('JUMPSELLER_AUTH_TOKEN'),
    CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS => json_encode($payload),
]);

$order = json_decode(curl_exec($ch), true)['order'];
echo $order['status_enum']; // "created"

The response includes a checkout_url and a review_url — both usable in your own UI if you want to surface the payment link yourself instead of (or in addition to) the automatic email.

Valid status values for reference

status value you send Resulting status_enum Admin filter
(omitted) created Open
Pending Payment pending_payment Pending
Paid paid Paid
Canceled canceled Canceled
Abandoned abandoned Abandoned

Sending "status": "Open" or "status": "Created" literally returns a 400 Invalid Order Status error — omission is the only way to reach this state through the API. status values are case-sensitive.

The email side-effect

Setting "send_email": false in the request does not suppress the order-received email when the order is created without a status. This is the same “New Manual Order” notification described in Order Emails and Notifications — it’s tied to the manual-order-creation event itself, not to the payment-status transition emails that send_email was designed to control.

In practice: any order your app creates as Created/Open reaches the customer’s inbox, with a live payment link and QR code, regardless of send_email.

If that’s exactly what you want (use cases 1–3 above), no action is needed. If you need to create orders silently — for automated tests, staging environments, or dry runs — you have two options, both store-wide rather than per-request:

  • Turn off the “New Manual Order” toggle under Admin → Settings → General → Emails. This disables the email for every order created without a status, including real ones — only do this if your integration is the sole source of such orders, or is handling notification through another channel itself.
  • In development, route test orders to a customer record you control (a real inbox you own, or an alias), never to a live customer’s email, until you’ve verified the behavior you expect.

Transitioning the order afterwards

Once payment is confirmed (by your own system, a marketplace, or the merchant), move the order forward with a PUT:

curl -u "$JUMPSELLER_LOGIN:$JUMPSELLER_AUTH_TOKEN" \
  -X PUT "https://api.jumpseller.com/v1/orders/2372.json" \
  -H "Content-Type: application/json" \
  -d '{ "order": { "status": "Paid" } }'

Combine this with a webhook subscription on order events if you need to react to status changes made from the Admin Panel as well as from your own app.

FAQ

Can I set status to "Open" directly? No — the literal string is rejected. Omit the status field to land in that state.

Does this work the same for orders created from the Admin Panel’s “Create Order” screen? Yes. The Manual Orders UI produces the same Created status when you choose “send a checkout link” instead of “payment already processed.” See Manual Orders for the point-and-click flow.

Will the customer be charged automatically? No. Created/Open orders are unpaid; the customer must complete checkout (via the email link, QR code, or checkout_url) for payment to be collected.

Is there a way to prevent the checkout-link email per-request? Not currently — send_email: false does not apply to this notification. See The email side-effect above for your store-wide options.

Resources

If you have any further questions, please don’t hesitate to contact us.

Start your journey with us!

Start your free 7-day trial. No credit card required.