Creating a Manual Order
Add and create a manual order by adding products to the cart and entering the billing and shipping details of the customer.
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.
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.
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.
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.
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.
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.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.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.shipping_required gets decided when you don’t send itIf 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 false → 200 OK, no address needed |
| 1 appointment product only | (omitted) | Derived as false → 200 OK, no address needed |
| 1 physical product only | (omitted) | Derived as true → 400 Customer without shipping address
|
| 1 physical + 1 digital product (mixed) | (omitted) | Derived as true → 400 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.
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").id (or variant_id for variants), not by SKU.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.
email filter on GET /v1/customers.json can be unreliable — always check that the email field on the returned record actually matches before reusing an id. When in doubt, treat a non-matching result as "not found" and create the customer. 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.
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.
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.
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:
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.
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.
If you have any further questions, please don’t hesitate to contact us.
Start your free 7-day trial. No credit card required.