Natural: Build a Sandbox Invoice Agent
Create and track sandbox invoice requests with a Python payment agent.
Introduction
30 Second Summary
An invoice can look complete while still leaving important questions unanswered. A reliable payment workflow shows who acted on it plus what happened next.
In this project, you will build a Python command-line agent that creates a $25 invoice-style request in Natural Sandbox. You will use a funded synthetic payer to move the request from OPEN to COMPLETED without moving real money.
What You'll Build
Run your finished agent to watch a $25 sandbox invoice move through its complete lifecycle in a terminal demo.
By the end of this project, you'll have:
- A working invoice collection agent that creates a visible $25 payment request for Natural's funded sandbox payer.
- An auditable agent run that identifies the acting agent plus the specific run behind each request.
- An end-to-end sandbox demonstration where you can fulfill an invoice before retrieving its final lifecycle state.
- Secret Mission: Extend the payer driver so it can decline a fresh invoice before the agent confirms the DECLINED state.
Are there any prerequisites?
You need a Mac plus access to the email address or phone number used for Natural signup. Natural may require organization verification before Sandbox access becomes available. The hands-on work takes about 55 minutes after that access is ready.
Before We Start
Before any setup begins, this checkpoint locks in what you are building and why it matters. You are committing to a sandbox-only Python agent that requests and tracks test money through the payment-request lifecycle without moving real money.
Set Up the Sandbox and Python Workspace
Your agent cannot send its first sandbox request until its identity, credential, and local runtime are ready. Natural Sandbox also needs any required organization verification to be complete.
You will prepare Python 3.14.8 plus Visual Studio Code 1.141 on your Mac. These pinned versions give the later commands a predictable environment.
You will also create an agent credential for the current Terminal session. An isolated virtual environment will contain the Natural Python SDK version 1.6.33.
In this step, get ready to:
- Prepare the pinned Python runtime and code editor.
- Create a Natural Sandbox agent with a session credential.
- Build an isolated workspace with the pinned SDK.
Install Python and Visual Studio Code
Python runs your invoice agent. Visual Studio Code gives you one place to edit the project while its command-line launcher opens the correct folder.
- Press Cmd+Space to open Spotlight on your Mac.
- Type Terminal into Spotlight.
- Press Enter to open macOS Terminal.
- Check your current Python version by running this command:
python3 --version
What does this command check?
The command asks the Python 3 executable to print its installed version. Use the tabs below to match the result shown in your Terminal.
✔️ I see version 3.14.8
Python 3.14.8 is ready for the project.
ⓧ I see an older version
An older Python installation can create a virtual environment with the wrong interpreter. Installing the pinned release keeps this project on version 3.14.8.
- Open the official Python macOS downloads page.
- Download the macOS installer for Python 3.14.8.
- Open the downloaded installer.
- Complete the installer prompts.
- Close the current Terminal window after installation.
- Start a fresh Terminal session through Spotlight.
- Confirm the updated Python version by running this command:
python3 --version
Why start a fresh Terminal?
A fresh shell reloads the command locations configured by the installer. The version check should now report Python 3.14.8.
ⓧ Command not found
Your shell cannot currently locate a Python 3 installation. The official macOS installer adds the runtime needed for this project.
- Open the official Python macOS downloads page.
- Download the macOS installer for Python 3.14.8.
- Open the downloaded installer.
- Complete the installer prompts.
- Start a fresh Terminal session through Spotlight.
- Confirm the Python installation by running this command:
python3 --version
What should you see?
Your new Terminal session should print Python 3.14.8.
Still seeing the wrong Python version?
- Close every Terminal window before starting a fresh session.
- Check that the Python 3.14.8 installer completed successfully.
- Help me diagnose my Python version setup.
Visual Studio Code needs its own version check because the desktop application also provides the code command used later to open your project folder.
- Check the Visual Studio Code command-line version by running this command:
code --version
What does this command check?
The first output line identifies the installed Visual Studio Code release. Use the tabs below to choose the path that matches your Terminal.
✔️ I see version 1.141.x
Visual Studio Code 1.141.x is installed. The code command is also available in your shell.
ⓧ I see an older version
The installed editor is older than the pinned release. Updating it gives you the version used throughout this walkthrough.
- Open the official Visual Studio Code download page.
- Download Visual Studio Code 1.141 for macOS.
- Open the downloaded .dmg file.
- Drag Visual Studio Code.app into Applications.
- Press Cmd+Space to open Spotlight.
- Type Visual Studio Code into Spotlight.
- Press Enter to open the updated application.
- Open the Command Palette from the top menu.
- Run Shell Command: Install 'code' command in PATH from the Command Palette.
- Return to Terminal.
- Confirm the updated editor version by running this command:
code --version
What should you see?
The first output line should begin with 1.141.
ⓧ Command not found
The editor or its shell launcher is missing. Reinstalling the current application gives you both pieces.
- Open the official Visual Studio Code download page.
- Download Visual Studio Code 1.141 for macOS.
- Open the downloaded .dmg file.
- Drag Visual Studio Code.app into Applications.
- Press Cmd+Space to open Spotlight.
- Type Visual Studio Code into Spotlight.
- Press Enter to open the application.
- Open the Command Palette from the top menu.
- Run Shell Command: Install 'code' command in PATH from the Command Palette.
- Return to Terminal.
- Confirm the command-line launcher by running this command:
code --version
What should you see?
The first output line should begin with 1.141.
Still unable to use the code command?
- Close Terminal after installing the shell command.
- Start a fresh Terminal session through Spotlight.
- Help me restore the Visual Studio Code command.
Create your Natural Sandbox agent
Natural uses an agent key to identify the agent making each API request. Sandbox keeps these actions isolated from production without moving real money.
Confirm the account terms first
Natural's public pricing page does not explicitly state a price for sandbox account access. Review the current account terms before signing up.
Keep the Sandbox toggle selected throughout this project. Sandbox credentials do not work in another environment.
- Review Natural's current account terms.
- Open the Natural website.
- Create your Natural account.
- Complete any organization verification Natural requests.
- Open the account menu in the bottom-left corner of the dashboard.
- Turn on the Sandbox toggle.
Organization verification can extend beyond the hands-on setup time. Continue once the dashboard lets you use Sandbox.
- Select Agents in the dashboard sidebar.
- Create an agent named Invoice Collection Agent.
- Open the new agent card.
- Use its credentials area to issue an agent key.
The full agent key appears only once. Keeping it out of project files protects the credential from accidental sharing.
- Copy the one-time secret beginning with ak_ntl_sandbox_.
- Store the copied secret in your password manager.
- Return to the current Terminal session.
- Export the copied secret by replacing your-api-key-here in this command:
export NATURAL_API_KEY='your-api-key-here'
Why export the key?
The Natural SDK reads the credential from NATURAL_API_KEY in the current Terminal session. Your Python files can authenticate without containing the secret.
The exported value disappears when this Terminal session ends. You must export it again in a new session.
Key missing from this Terminal?
- Run the export command in the same Terminal session used for the project.
- Check that the pasted key begins with the sandbox prefix.
- Help me check my sandbox environment variable safely.
Build the isolated Python workspace
A virtual environment keeps this project's SDK separate from packages used by other Python projects. The requirements.txt file records the exact SDK release needed to reproduce the setup.
- Create ~/Projects/natural-invoice-agent with its virtual environment by running these commands:
mkdir -p ~/Projects/natural-invoice-agent
cd ~/Projects/natural-invoice-agent
printf 'naturalpay==1.6.33\n' > requirements.txt
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txt
code .
What do these commands create?
- The mkdir command creates the natural-invoice-agent folder inside ~/Projects.
- The cd command makes that folder the active Terminal location.
- The printf command writes the pinned SDK dependency to requirements.txt.
- The venv command creates the isolated .venv environment.
- The source command activates that environment in the current Terminal.
- The final commands install the dependency and open the project folder in Visual Studio Code.
That is the setup backbone complete. Visual Studio Code is now open at ~/Projects/natural-invoice-agent while the original Terminal keeps your virtual environment and sandbox key active.
- Select requirements.txt in the Visual Studio Code Explorer sidebar.
You should see one dependency pin for naturalpay version 1.6.33.
Workspace command did not finish?
- Check that Terminal still shows the sandbox key export in its current session.
- Confirm that your internet connection is available while the SDK installs.
- Help me fix my Natural Python workspace setup.
✔️ Awesome, I've got everything!
Your dependency file contains the exact SDK pin required for the invoice agent.
ⓧ I'd like to double check the full code
Compare your requirements.txt file with this complete reference.
naturalpay==1.6.33
Before you run the final check, which command do you expect to prove that your credential belongs to Sandbox without printing the secret?
- Verify the active environment and sandbox credential by running these commands in the original Terminal:
python --version
code --version
python -c "import naturalpay; print(naturalpay.__version__)"
python -c "import os; print(os.environ['NATURAL_API_KEY'].startswith('ak_ntl_sandbox_'))"
What should you see?
- The Python check should print version 3.14.8.
- The Visual Studio Code check should begin with 1.141.x.
- The package check should print 1.6.33.
- The credential-prefix check should print True without exposing the key.
One of the setup checks failed?
- Check that your Terminal prompt shows the active .venv environment.
- Run the workspace setup commands again if the SDK version check fails.
- Export the agent key again if the prefix check does not print True.
- Help me diagnose my final environment checks.
Your sandbox identity and isolated Python workspace are ready. Next, you will run the first invoice request and learn what Natural requires from an agent-attributed payment action.
Let the First Invoice Request Fail
Your Natural Sandbox workspace is ready. Your isolated Python environment can use the agent key from the current Terminal session.
Now you can test whether an authenticated agent has enough context to make an auditable payment request. The first API response answers that question.
In this step, get ready to:
- Create a minimal Python script that points the Natural client at Sandbox.
- Submit a $25 invoice request to the funded sandbox payer.
- Interpret Natural's first response through its agent-run audit rules.
Create the first invoice script
An SDK client holds the connection settings that each request reuses. This client points every request at Natural Sandbox.
- Select the file creation control at the top of the VS Code Explorer sidebar.
- Type invoice_agent.py in the filename field.
- Press Enter to create the file inside natural-invoice-agent.
- Add the imports and sandbox client by pasting this code into invoice_agent.py:
import uuid
from naturalpay import Natural
client = Natural(
base_url="https://api.sandbox.natural.com",
)
What Does This Code Do?
- The uuid module supplies unique values for the request later in the file.
- The Natural class creates the SDK client that sends the request.
- The base_url value directs the client to https://api.sandbox.natural.com.
- Press Cmd+S (macOS) or Ctrl+S (Windows) to save invoice_agent.py.
- Confirm the Explorer sidebar lists invoice_agent.py beside requirements.txt.
Your first script is in place with a client aimed safely at Natural Sandbox.
Can't See the New File?
- Confirm the Explorer sidebar is showing the natural-invoice-agent folder.
- Check that the filename is exactly invoice_agent.py.
- Help me find or create invoice_agent.py in VS Code.
Natural expresses USD amounts in integer minor units. The value 2500 represents $25.00.
An idempotency key gives this creation attempt a unique identifier. A fresh UUID supplies that value.
- Place your cursor after the closing ) in the client block.
- Press Enter twice to leave one blank line.
- Add the payment request and response printout by pasting this code:
request = client.payment_requests.create(
amount=2500,
currency="USD",
description="Invoice 7",
payer_name="Sandbox Payer",
payer={
"type": "email",
"value": "payment-request-payer@sandbox.natural.test",
},
idempotency_key=str(uuid.uuid4()),
)
print(request.to_json())
How Does the Request Work?
- The amount and currency values define a $25.00 USD request.
- The payer value targets Natural's always-funded sandbox payer at payment-request-payer@sandbox.natural.test.
- The idempotency_key value assigns a fresh UUID to this creation attempt.
- The to_json() call serializes the API response for the Terminal.
- Press Cmd+S (macOS) or Ctrl+S (Windows) to save invoice_agent.py.
- Confirm the unsaved dot disappears from the invoice_agent.py tab.
The complete first-pass script is saved. It is ready to send its request from your activated environment.
Seeing Editor Warnings?
- Check that each request argument is indented by four spaces inside client.payment_requests.create().
- Confirm every opening parenthesis or brace has a matching closing character.
- Help me compare my invoice_agent.py code with the expected first-pass script.
✔️ Awesome, I've got everything!
Your saved file now contains the sandbox client and the complete first payment request.
ⓧ I'd like to double check the full code
Compare your file with the complete first-pass version of invoice_agent.py below.
import uuid
from naturalpay import Natural
client = Natural(
base_url="https://api.sandbox.natural.com",
)
request = client.payment_requests.create(
amount=2500,
currency="USD",
description="Invoice 7",
payer_name="Sandbox Payer",
payer={
"type": "email",
"value": "payment-request-payer@sandbox.natural.test",
},
idempotency_key=str(uuid.uuid4()),
)
print(request.to_json())
Run the planned failure
The script now has enough code to make a sandbox API call. Its first response reveals whether agent identity supplies enough audit context.
- Switch back to the existing VS Code Terminal that still has .venv activated.
Before you run the script, do you think the agent key gives Natural enough information to audit this request?
- Test the first request by running this command:
python invoice_agent.py
What Should You See?
The Terminal ends with an HTTP 400 error containing missing_instance_id.
The agent key identifies Invoice Collection Agent. Natural still needs X-Instance-ID to attribute agent-controlled money movement to one logical run.
The current Natural constructor only has base_url. Natural rejects the request before creating an invoice.
Seeing a Different Result?
- Confirm the Terminal prompt still shows the activated .venv environment.
- Confirm the current Terminal session still contains the NATURAL_API_KEY value exported earlier.
- Compare your Natural constructor with the full-code tab. The first-pass constructor contains only base_url.
- Help me diagnose why invoice_agent.py did not produce missing_instance_id.
Your script reaches Natural. Next, you'll add the missing run context so the same invoice can open successfully.
Add Audit Context and Create the Invoice
Your first invoice reached Natural Sandbox. Natural rejected it with 400 missing_instance_id.
A unique instance ID closes the audit gap for this run. The finished script prints the request ID before it pauses for the payer.
In this step, get ready to:
- Attach a unique instance ID to the Natural client.
- Add the request lifecycle tracking flow.
- Create an OPEN invoice that waits for payer action.
Attach an instance ID to the client
The agent key establishes the agent identity behind the request. The instance ID connects that request to one logical agent run.
- Return to invoice_agent.py in VS Code. Find this line inside the client = Natural( block:
base_url="https://api.sandbox.natural.com",
Why Did the Previous Version Fail?
The client points to Natural Sandbox. The request carries no run attribution.
Natural therefore rejects the agent-attributed request with 400 missing_instance_id.
- Insert the instance_id line above base_url so the block matches this version:
instance_id=f"invoice-run-{uuid.uuid4()}",
base_url="https://api.sandbox.natural.com",
What Does This Change Do?
- The uuid.uuid4() call generates a fresh value for each run.
- The invoice-run- prefix makes the value recognizable in audit records.
- The base_url value keeps every request inside Natural Sandbox.
Add the request tracking flow
The create call returns a response containing the payment request ID. Storing that ID lets the same agent retrieve the request after payer action.
- Locate the current output line at the bottom of invoice_agent.py:
print(request.to_json())
What Does the Current Ending Do?
The current ending serializes the response before the script exits. It does not retain the payment request ID for a later lookup.
- Replace that line with the complete request tracking flow below:
print("Created payment request:")
print(request.to_json())
payment_request_id = request.data.id
print(f"PAYMENT_REQUEST_ID={payment_request_id}")
input("Fulfill this request as the sandbox payer, then press Return: ")
updated_request = client.payment_requests.get(payment_request_id)
print("Updated payment request:")
print(updated_request.to_json())
How Does the Tracking Flow Work?
- The first two lines label the newly created response before serializing it as JSON.
- The request.data.id value holds the new payment request ID.
- The PAYMENT_REQUEST_ID output gives you a copyable ID for the payer simulation.
- The input() call keeps this Terminal waiting while the payer acts elsewhere.
- The client.payment_requests.get() call retrieves the same request after you press Return.
- Save invoice_agent.py by pressing Cmd+S (macOS) or Ctrl+S (Windows).
Create the waiting invoice
Natural represents USD amounts in integer minor units. The existing amount of 2500 represents $25.00.
This $25 request stays inside Natural Sandbox. It cannot move real money.
Before you run the agent, predict whether Natural accepts the request now that the instance ID is present.
- Create the sandbox invoice by running this command in the activated environment:
python invoice_agent.py
What Happens During This Run?
- The client authenticates with the agent key stored in NATURAL_API_KEY.
- The instance ID attributes the request to this logical run.
- The unique idempotency key protects this creation attempt from accidental duplicate processing.
- The funded sandbox payer address receives the invoice without real-money movement.
You'll see Created payment request: above JSON containing a prq_ request ID. The status is OPEN.
The final line displays Fulfill this request as the sandbox payer, then press Return:.
- Leave the first Terminal waiting at that prompt without pressing Return.
That's the obstacle cleared. Your auditable agent run now has an OPEN invoice waiting for its payer.
Still Seeing the Instance Error?
- Confirm that instance_id=f"invoice-run-{uuid.uuid4()}" appears inside the Natural( constructor.
- Check that you saved invoice_agent.py before rerunning the script.
- Confirm that you used the Terminal session where NATURAL_API_KEY is exported.
- Ask for help diagnosing the Natural request.
✔️ Awesome, I've got everything!
Your invoice_agent.py file now contains the full requester flow. The first Terminal is waiting for the sandbox payer.
ⓧ I'd like to double check the full code
- Compare your saved invoice_agent.py file with the complete version below:
import uuid
from naturalpay import Natural
client = Natural(
instance_id=f"invoice-run-{uuid.uuid4()}",
base_url="https://api.sandbox.natural.com",
)
request = client.payment_requests.create(
amount=2500,
currency="USD",
description="Invoice 7",
payer_name="Sandbox Payer",
payer={
"type": "email",
"value": "payment-request-payer@sandbox.natural.test",
},
idempotency_key=str(uuid.uuid4()),
)
print("Created payment request:")
print(request.to_json())
payment_request_id = request.data.id
print(f"PAYMENT_REQUEST_ID={payment_request_id}")
input("Fulfill this request as the sandbox payer, then press Return: ")
updated_request = client.payment_requests.get(payment_request_id)
print("Updated payment request:")
print(updated_request.to_json())
What Should Match?
The client includes the instance ID before the sandbox base URL. The final section prints the new request before pausing at the payer prompt.
After payer action, the last three lines retrieve the same payment request before printing its updated state.
Your first Terminal is now holding an OPEN invoice. Next, you'll act as the funded sandbox payer. Then the agent reads the completed lifecycle.
Fulfill and Track the Sandbox Invoice
Your invoice agent now creates an OPEN request in Natural Sandbox. The first Terminal is waiting with its unique prq_ request ID.
Creating a request proves only the requester side. A funded sandbox payer now completes the other half of the lifecycle without moving real money.
In this step, get ready to:
- Build a sandbox payer driver that accepts one payment request ID.
- Fulfill the open invoice through the funded payer simulation.
- Retrieve the request again to confirm its completed lifecycle.
Build the sandbox payer driver
The payer driver acts as a separate counterparty. It validates the request ID before building the sandbox fulfillment URL.
- Create sandbox_payer.py inside the open ~/Projects/natural-invoice-agent folder with this starter code:
import json
import os
import sys
import uuid
import urllib.request
if len(sys.argv) != 2:
raise SystemExit("Usage: python sandbox_payer.py prq_...")
payment_request_id = sys.argv[1]
url = (
"https://api.sandbox.natural.com/simulations/payment-requests/"
f"{payment_request_id}/fulfill"
)
What does this code do?
- The imports provide tools for reading environment variables. They also provide tools for sending the request.
- The argument check requires exactly one payment request ID. It stops the script with usage guidance when the ID is missing.
- The payment_request_id variable stores the prq_ ID supplied in the command.
- The url targets the fulfillment simulation for that specific request.
- Save sandbox_payer.py in VS Code.
- Use VS Code's Terminal menu to open a second Terminal.
You now have a second command prompt while the first Terminal keeps the invoice agent waiting.
- Activate the existing virtual environment in the second Terminal by running this command:
source .venv/bin/activate
What does this command do?
This activates the same .venv environment used by the waiting invoice agent. Your second Terminal now runs the project with the same Python setup.
You should see (.venv) at the start of the second Terminal prompt.
Virtual environment did not activate?
- Check that the second Terminal opened inside ~/Projects/natural-invoice-agent.
- Confirm that the .venv folder still exists in the VS Code file sidebar.
- Help me activate the existing virtual environment in my second Terminal.
Before you test the input guard, what do you expect the script to do when no request ID is provided?
- Test the input guard by running the script without an ID:
python sandbox_payer.py
Why does the script stop?
The command deliberately omits the required request ID. The guard stops execution with Usage: python sandbox_payer.py prq_... before any sandbox request is sent.
Good. The driver now rejects an incomplete command before it can target the wrong payment request.
Do you see a different result?
- Confirm that you saved the argument check in sandbox_payer.py.
- Check that the second Terminal is using the natural-invoice-agent folder.
- Help me debug the input check in my sandbox payer script.
Complete and run the payer simulation
The remaining code sends an authenticated HTTP POST request. Bearer authentication identifies the sandbox agent.
A unique idempotency key protects this payer action from accidental duplication. A separate instance ID makes the simulated payer run auditable.
- Add the request and response logic below the url block in sandbox_payer.py with one blank line before it:
request = urllib.request.Request(
url=url,
headers={
"Authorization": f"Bearer {os.environ['NATURAL_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
"X-Instance-ID": f"sandbox-payer-{uuid.uuid4()}",
},
method="POST",
)
with urllib.request.urlopen(request) as response:
result = json.load(response)
print(json.dumps(result, indent=2))
How does the payer action work?
- The Authorization header reads NATURAL_API_KEY from the Terminal environment. The secret stays out of the Python file.
- The Idempotency-Key header receives a fresh UUID for this simulation request.
- The X-Instance-ID header attributes the action to one payer-driver run.
- The urlopen call sends the POST request. The final line formats the response as readable JSON.
- Save sandbox_payer.py.
Your agent key stays in the Terminal environment. The payer driver reads it at runtime without printing it.
- Re-enter the NATURAL_API_KEY export from setup in the second Terminal if it did not inherit your active sandbox key.
- Copy the prq_ request ID printed after PAYMENT_REQUEST_ID= in the waiting first Terminal.
- Replace paste-the-prq-id-here in the command below with the copied request ID.
Before you run the payer driver, do you expect the open request to remain untouched or receive a funded sandbox payment?
- Fulfill the open invoice from the second Terminal by running these commands:
source .venv/bin/activate
export PAYMENT_REQUEST_ID='paste-the-prq-id-here'
python sandbox_payer.py "$PAYMENT_REQUEST_ID"
What do these commands do?
- The first command confirms that the second Terminal uses the existing virtual environment.
- The second command stores the copied request ID in PAYMENT_REQUEST_ID for this Terminal session.
- The final command passes that ID to the payer driver. The driver asks Natural's funded sandbox payer to fulfill the request.
You should see a formatted response describing the sandbox payment. The immediate response may show PROCESSING while the payment finishes.
That is the counterparty side working. Your funded sandbox payer has now acted on the exact invoice created in the first Terminal.
Payer request did not succeed?
- Check that PAYMENT_REQUEST_ID contains the full prq_ ID from the waiting agent.
- Re-enter the active NATURAL_API_KEY export from setup if the second Terminal reports an authentication problem.
- Help me troubleshoot the Natural Sandbox fulfillment request.
✔️ Awesome, I've got everything!
Your sandbox_payer.py file is saved. Your existing invoice_agent.py file remains unchanged.
ⓧ I'd like to double check the full code
Compare your saved sandbox_payer.py file with this complete version.
import json
import os
import sys
import uuid
import urllib.request
if len(sys.argv) != 2:
raise SystemExit("Usage: python sandbox_payer.py prq_...")
payment_request_id = sys.argv[1]
url = (
"https://api.sandbox.natural.com/simulations/payment-requests/"
f"{payment_request_id}/fulfill"
)
request = urllib.request.Request(
url=url,
headers={
"Authorization": f"Bearer {os.environ['NATURAL_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
"X-Instance-ID": f"sandbox-payer-{uuid.uuid4()}",
},
method="POST",
)
with urllib.request.urlopen(request) as response:
result = json.load(response)
print(json.dumps(result, indent=2))
Retrieve the completed request
The payer response proves that the simulation accepted the fulfillment action. The waiting invoice agent now retrieves the same request to reveal its final lifecycle state.
Before you continue, which status do you expect the refreshed request to contain?
- Switch back to the first Terminal that is waiting at the payer prompt.
- Press Return to retrieve the payment request again.
You should see Updated payment request: followed by the refreshed JSON. Its status should now be COMPLETED.
What changed in the lifecycle?
The original response showed an OPEN invoice waiting for its payer. The refreshed response shows COMPLETED after the funded sandbox payer fulfilled it.
Both outputs share the same prq_ ID. That matching ID proves you tracked one request across the full sandbox lifecycle.
Request still processing?
- Confirm that the payer response finished before you pressed Return in the first Terminal.
- Create a fresh request with the invoice agent if you need to repeat the lifecycle after a transient PROCESSING result.
- Help me understand why my refreshed sandbox request has not reached COMPLETED.
You completed the full requester-to-payer loop. Your agent can now create an invoice-style request and track its sandbox outcome without moving real money.
Secret mission
Handle a Declined Invoice
Extend the sandbox payer driver with a decline action. You will create a fresh invoice, refuse it as the synthetic payer, and retrieve its final declined state without moving real money.
Clean Up Your Resources
Clean Up Your Resources
Natural Sandbox does not move real money. Decide whether to keep your workspace available, pause local access, or delete the reusable resources.
Cost warning
Natural's pricing page does not explicitly state whether sandbox account access is free. Keep every key, URL, and action in Sandbox.
- Choose Pause or Delete if you do not plan to continue under your current account terms.
Resources you used:
- The local ~/Projects/natural-invoice-agent folder containing .venv, requirements.txt, invoice_agent.py, and sandbox_payer.py.
- The Natural Sandbox agent named Invoice Collection Agent with its active sandbox agent key.
- The NATURAL_API_KEY environment variable in both active Terminal sessions.
Keep everything running
No action is needed. Choose this option if you plan to create more sandbox invoices or keep testing payment request states.
- Keep ~/Projects/natural-invoice-agent available in Visual Studio Code for future runs.
- Keep Invoice Collection Agent active for later Natural Sandbox projects.
- Keep the sandbox agent key in secure storage for future sessions.
Pause - I'll come back to this later
Pause your local session access while keeping the project files and sandbox agent ready for later.
- Switch back to the first Visual Studio Code Terminal.
- Clear its session credential and leave the virtual environment by running these commands:
unset NATURAL_API_KEY
deactivate
What Do These Commands Do?
- The first command removes NATURAL_API_KEY from the current Terminal session.
- The second command exits the project virtual environment.
- Both changes apply only to the Terminal session where you run them.
- Repeat these commands in the second Visual Studio Code Terminal.
- Confirm each Terminal prompt no longer indicates an active virtual environment.
Delete - I don't want to use this again
Deleting the agent and folder can feel drastic. This option permanently removes your reusable sandbox setup.
- Return to the Natural dashboard with Sandbox selected.
- Select Agents in the sidebar.
- Open the Invoice Collection Agent card.
- Revoke the active sandbox agent key from the credentials area.
- Delete the Invoice Collection Agent sandbox agent from its open card.
- Confirm Invoice Collection Agent no longer appears under Agents.
- Return to the first Visual Studio Code Terminal.
- Clear its exported key and leave the virtual environment by running these commands:
unset NATURAL_API_KEY
deactivate
What Do These Commands Remove?
These commands remove the sandbox credential from the current shell. They also leave the project virtual environment.
- Repeat these commands in the second Visual Studio Code Terminal.
- Confirm each Terminal prompt no longer indicates an active virtual environment.
- In the Visual Studio Code window, confirm that the project you intend to remove is ~/Projects/natural-invoice-agent.
- Remove the project folder by running this command:
rm -rf ~/Projects/natural-invoice-agent
What Does This Delete?
This command permanently removes the named project folder. It also removes the .venv environment with its installed Natural SDK.
- Open ~/Projects in Finder.
- Confirm natural-invoice-agent is no longer listed.
Still See the Project Folder?
- Compare the folder location in Finder with ~/Projects/natural-invoice-agent.
- Help me safely remove the local project folder.
Nice Work!
Nice Work!
Strong finish! Your Python agent now demonstrates an auditable payment request lifecycle in Natural Sandbox without moving real money.
You've learned how to:
- Built a Python invoice collection agent with the official Natural Python SDK. Kept the sandbox agent key in NATURAL_API_KEY instead of placing it in source code.
- Triggered the planned 400 missing_instance_id error. Added an instance ID to identify each agent run. Added a unique idempotency key to protect each request from accidental duplication.
- Created a $25 sandbox invoice with an initial OPEN state. Fulfilled it through the funded payer simulation. Retrieved the final COMPLETED state from the agent.
- Secret Mission: Extended the payer driver with a strict action allowlist. Declined a fresh invoice. Verified the final DECLINED state without moving money.
Ready to quiz yourself?