MARKET · SAFE ORDER CREATION

How to Create a Market Order Safely in Screeps

Create one reviewed market order with a one-time Memory request, validate Terminal ownership, calculate the 5% fee and Credit reserve, reject duplicates, snapshot the request, handle ERR_FULL without hard-coding a disputed order limit, and verify the order afterward.

Verification statusChinese source article: Reviewed in full · Official docs: Checked — createOrder(), 5% fee, order activation, return codes, cancelOrder(), changeOrderPrice() and extendOrder() · Documentation conflict: The current API page says 300 maximum orders but its ERR_FULL text still says 50; no numeric limit is hard-coded

VERIFICATION

Evidence and test status

Chinese source article
Reviewed in full
Official docs
Checked — createOrder(), 5% fee, order activation, return codes, cancelOrder(), changeOrderPrice() and extendOrder()
Documentation conflict
The current API page says 300 maximum orders but its ERR_FULL text still says 50; no numeric limit is hard-coded
Execution boundary
OK means the operation was scheduled; it does not prove the order is active or filled
JavaScript syntax
Passed
Offline order review
Passed — invalid values, missing Terminal, fee reserve, duplicate request, disabled request and ready states
Screeps Console test
Pending
Live order creation, activation and market fill test
Pending
Last verified
July 26, 2026

Quick answer

Use Game.market.createOrder() only from an explicit one-time request. Validate the order type, resource, positive price, positive integer amount, owned active Terminal, 5% fee, Credit reserve, and equivalent existing orders. Disable the request before the API call, save the exact snapshot and return code, then confirm the order in Game.market.orders on a later tick.

Calculate the 5% creation fee

The official market charges a fee when an order is placed:

creation fee = price × totalAmount × 0.05
function calculateOrderFee(price, totalAmount) {
  if (
    !Number.isFinite(price)
    || price <= 0
    || !Number.isInteger(totalAmount)
    || totalAmount <= 0
  ) {
    return null;
  }

  return price * totalAmount * 0.05;
}

A sell order does not prepay the full resource value. A buy order also does not spend the full future purchase value at creation. Both pay the 5% placement fee, while later activation and deals depend on available resources or Credits.

Use a one-time request

Memory.market ??= {};
Memory.market.createOrderRequest = {
  enabled: true,
  requestId: 'manual-order-2026-07-26-01',
  type: ORDER_SELL,
  resourceType: RESOURCE_UTRIUM,
  price: 1,
  totalAmount: 10000,
  roomName: 'W1N1',
  creditReserve: 1000000
};

Every number is an example, not a market recommendation. Review the current market, inventory, Terminal, fee, account budget, and intended order direction before enabling the request.

Validate values and Terminal state

function validateOrderRequest(request) {
  if (!request || request.enabled !== true) {
    return { valid: false, reason: 'disabled' };
  }

  if (
    request.type !== ORDER_BUY
    && request.type !== ORDER_SELL
  ) {
    return { valid: false, reason: 'order-type-invalid' };
  }

  if (
    typeof request.resourceType !== 'string'
    || typeof request.roomName !== 'string'
    || request.roomName.length === 0
    || !Number.isFinite(request.price)
    || request.price <= 0
    || !Number.isInteger(request.totalAmount)
    || request.totalAmount <= 0
  ) {
    return { valid: false, reason: 'arguments-invalid' };
  }

  return { valid: true, reason: 'valid' };
}

For ordinary Terminal resources, verify that the requested room is currently visible and contains your active Terminal. Account-bound resources use different room rules and are outside this guide.

Reject equivalent duplicate orders

function findEquivalentOrder(request) {
  return Object.values(Game.market.orders).find(order =>
    order.type === request.type
    && order.resourceType === request.resourceType
    && order.roomName === request.roomName
  ) || null;
}

Equivalent does not necessarily mean identical price or remaining amount. The safer default is to stop and review the existing order, then use the dedicated maintenance APIs rather than creating another order every tick.

Build a testable order plan

function evaluateOrderPlan(input) {
  const validation = validateOrderRequest(input.request);

  if (!validation.valid) {
    return { ready: false, reason: validation.reason };
  }

  if (!input.terminalReady) {
    return { ready: false, reason: 'terminal-not-ready' };
  }

  if (input.duplicateFound) {
    return { ready: false, reason: 'equivalent-order-exists' };
  }

  const fee = calculateOrderFee(
    input.request.price,
    input.request.totalAmount
  );

  if (!Number.isFinite(fee)) {
    return { ready: false, reason: 'fee-invalid' };
  }

  const reserve = Number.isFinite(
    input.request.creditReserve
  )
    ? input.request.creditReserve
    : 0;

  if (input.credits - fee < reserve) {
    return {
      ready: false,
      reason: 'credit-reserve',
      fee
    };
  }

  return {
    ready: true,
    reason: 'ready',
    fee,
    creditsAfterFee: input.credits - fee
  };
}

This function does not call the market. It can be tested with snapshots before any irreversible operation.

Complete one-time createOrder example

State impact: this code writes request status and may schedule one real market order. It intentionally does not auto-retry.

module.exports.loop = function () {
  const request = Memory.market?.createOrderRequest;
  const validation = validateOrderRequest(request);

  if (!validation.valid) {
    return;
  }

  const room = Game.rooms[request.roomName];
  const terminal = room?.terminal ?? null;
  const terminalReady = Boolean(
    room
    && terminal
    && terminal.my === true
    && terminal.isActive() === true
  );
  const duplicate = findEquivalentOrder(request);
  const plan = evaluateOrderPlan({
    request,
    credits: Game.market.credits,
    terminalReady,
    duplicateFound: Boolean(duplicate)
  });

  request.lastCheckedAt = Game.time;
  request.lastStatus = plan.reason;

  if (!plan.ready) {
    request.preview = {
      fee: plan.fee ?? null,
      duplicateOrderId: duplicate?.id ?? null,
      currentOrderCount:
        Object.keys(Game.market.orders).length
    };
    return;
  }

  request.enabled = false;
  request.status = 'submitted';
  request.submittedAt = Game.time;
  request.snapshot = {
    requestId: request.requestId ?? null,
    type: request.type,
    resourceType: request.resourceType,
    price: request.price,
    totalAmount: request.totalAmount,
    roomName: request.roomName,
    fee: plan.fee,
    creditsBefore: Game.market.credits,
    currentOrderCount:
      Object.keys(Game.market.orders).length
  };

  const result = Game.market.createOrder({
    type: request.type,
    resourceType: request.resourceType,
    price: request.price,
    totalAmount: request.totalAmount,
    roomName: request.roomName
  });

  request.result = result;
  request.resultAt = Game.time;
  request.status = result === OK
    ? 'accepted-pending-verification'
    : 'failed-review-required';

  console.log(JSON.stringify({
    type: 'market-create-order-result',
    requestId: request.requestId ?? null,
    roomName: request.roomName,
    resourceType: request.resourceType,
    orderType: request.type,
    price: request.price,
    totalAmount: request.totalAmount,
    fee: plan.fee,
    result
  }));
};

Disable before the irreversible call

The request is disabled before createOrder(). A transient error, changed Terminal, invalid arguments, or order-cap failure must not cause another automatic order on the next tick. Review the snapshot and return code, then create a new request ID if another attempt is justified.

Do not hard-code the disputed order limit

As of the verification date, the official API page says the maximum order count is 300 in the method description, while the ERR_FULL table still says more than 50 cannot be created. Because those statements conflict, this guide logs:

const currentOrderCount = Object.keys(
  Game.market.orders
).length;

It does not reject at 50 or 300. The actual API result remains authoritative, and ERR_FULL requires manual review rather than an automatic loop.

Verify the order after OK

function findOrderAfterRequest(snapshot) {
  return Object.values(Game.market.orders).find(order =>
    order.type === snapshot.type
    && order.resourceType === snapshot.resourceType
    && order.roomName === snapshot.roomName
    && order.price === snapshot.price
    && order.totalAmount === snapshot.totalAmount
  ) || null;
}

A matching order confirms that the order now exists in your account snapshot. Check its active, amount, remainingAmount, and totalAmount separately. Existence does not prove a trade has occurred.

Change, extend, or cancel the existing order

  • changeOrderPrice() changes price; raising it charges 5% on the price difference multiplied by remaining amount.
  • extendOrder() adds capacity and charges 5% on the added value.
  • cancelOrder() removes the order; the original 5% fee is not returned.

Frequent cancel-and-recreate cycles can lose Credits unnecessarily.

Handle return codes

CodeInterpretationReview
OKOperation scheduledFind the order afterward
ERR_NOT_OWNERTerminal or room ownership condition failedRoom and Terminal
ERR_NOT_ENOUGH_RESOURCESInsufficient Credits for the feeFee and reserve
ERR_FULLNo more orders acceptedCurrent orders and current API behavior
ERR_INVALID_ARGSInvalid request argumentsType, resource, price, amount, room

Debugging checklist

  • Require an explicit one-time request ID.
  • Validate type, resource, room, price, and integer amount.
  • Check an owned active Terminal for ordinary resources.
  • Calculate the 5% fee.
  • Preserve a Credit reserve.
  • Reject an equivalent existing order.
  • Disable before calling the API.
  • Save the exact snapshot and return code.
  • Handle ERR_FULL without hard-coded 50 or 300 checks.
  • Verify order existence and activity later.
  • Never describe order creation as a completed trade.

Scope and next steps

This guide does not implement automatic pricing, market forecasting, profit calculations, account-bound resources, multi-shard strategy, automatic retries, or fill prediction. Continue with a reviewed one-time deal.

Frequently asked questions

Is price 1 a recommended Utrium price?

No. It is an example input.

Why check duplicates before calling?

Repeated main-loop calls can create several equivalent orders and pay several fees.

Does cancellation refund the fee?

No. The official documentation says the 5% fee is not returned.

Can I trust one documented order-limit number?

Not while the current API page contains conflicting 300 and 50 statements. Handle the return code.

Official documentation

SOURCE AND SCOPE

Review the source, evidence, or next system

This English guide is rewritten for a focused search intent while preserving the technical scope and verification boundaries of its Chinese source. Live-room evidence is claimed only when the verification record says it exists.