Owning.pro — Developer Examples
Author: Sage Moreno, Technical Writer & Documentation Lead Date: 2026-07-12 Status: Active Audience: External developers — complete runnable scripts in Python, TypeScript, and Bash Base URL: https://api.owning.pro
Complete, runnable scripts that demonstrate the full Owning API workflow: search listings, create a listing with an image, publish it, and fetch it as Markdown. Copy, paste, and run.
Table of Contents
Environment Setup
All scripts expect these environment variables:
# Required for write operations (create, publish, upload, manage)
export OWNING_API_KEY="own_aBcD123EfGh456IjKl789..."
# Required only for the initial API key creation (one-time setup)
export OWNING_EMAIL="dev@example.com"
export OWNING_PASSWORD="your-secure-password"
Don't have an API key yet? Follow the Developer Quickstart — Steps 1 and 2 take 2 minutes.
1. Python — Complete Script
A complete script that searches listings, creates a new listing with an uploaded image, publishes it, and fetches the result as Markdown.
Prerequisites
pip install requests
Script
#!/usr/bin/env python3
"""
Owning.pro API — Complete example script.
Demonstrates:
1. Search listings (public, no auth)
2. Upload an image (requires API key)
3. Create a listing with the image (requires API key)
4. Publish the listing (requires API key)
5. Fetch the listing as Markdown (public, no auth)
Usage:
export OWNING_API_KEY="own_..."
python3 owning_example.py
"""
import os
import sys
import requests
BASE_URL = "https://api.owning.pro"
API_KEY = os.environ.get("OWNING_API_KEY", "")
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
def check_api_key():
"""Verify the API key is set for write operations."""
if not API_KEY:
print("❌ OWNING_API_KEY environment variable is not set.")
print(" Get one at https://owning.pro/dashboard → API Keys")
print(" Then: export OWNING_API_KEY='own_...'")
sys.exit(1)
if not API_KEY.startswith("own_"):
print("❌ API key should start with 'own_'. Check your value.")
sys.exit(1)
def search_listings(query="bavaria", limit=5):
"""Search for listings — public endpoint, no auth required."""
print(f"\n🔍 Searching for '{query}' (limit={limit})...")
res = requests.get(
f"{BASE_URL}/api/listings",
params={"q": query, "limit": limit},
)
res.raise_for_status()
data = res.json()
results = data["results"]
pagination = data["pagination"]
print(f" Found {pagination['total']} listings (showing {len(results)}):")
for listing in results:
price = listing.get("price", {})
amount = price.get("amount", 0)
currency = price.get("currency", "EUR")
loc = listing.get("location", {})
print(f" • {listing['title']} — {amount:,.0f} {currency} ({loc.get('city', '?')}, {loc.get('country', '?')})")
return results
def upload_image(image_path):
"""Upload an image — returns the URL to use in create_listing."""
print(f"\n📸 Uploading image: {image_path}...")
with open(image_path, "rb") as f:
res = requests.post(
f"{BASE_URL}/api/upload",
headers={"Authorization": f"Bearer {API_KEY}"}, # No Content-Type — multipart
files={"image": f},
)
res.raise_for_status()
data = res.json()
image_url = data["url"]
print(f" ✅ Uploaded: {image_url}")
return {"url": image_url, "alt": os.path.basename(image_path)}
def create_listing(images=None):
"""Create a draft listing — requires API key."""
print("\n📝 Creating a draft listing...")
payload = {
"title": "MacBook Pro 16\" 2023 M3 Max",
"description": (
"Excellent condition MacBook Pro 16 inch with M3 Max chip, "
"64GB unified memory, 2TB SSD. Used for 6 months, no scratches. "
"Original box and charger included."
),
"category": "laptops",
"condition": "like_new",
"type": "sale",
"price": {
"amount": 2800,
"currency": "EUR",
"negotiable": True,
},
"location": {
"country": "ES",
"city": "Madrid",
"shipping": True,
},
"tags": ["macbook", "apple", "laptop"],
}
if images:
payload["images"] = images
res = requests.post(
f"{BASE_URL}/api/listings",
headers=HEADERS,
json=payload,
)
res.raise_for_status()
data = res.json()
listing_id = data["id"]
listing_slug = data["slug"]
print(f" ✅ Created: id={listing_id}, slug={listing_slug}, status={data['status']}")
return data
def publish_listing(listing_id):
"""Publish a draft listing — requires API key."""
print(f"\n🚀 Publishing listing {listing_id}...")
res = requests.post(
f"{BASE_URL}/api/listings/{listing_id}/publish",
headers={"Authorization": f"Bearer {API_KEY}"},
)
res.raise_for_status()
data = res.json()
print(f" ✅ Published! Status: {data['status']}")
return data
def get_listing_markdown(slug):
"""Fetch a listing as Markdown — public endpoint."""
print(f"\n📄 Fetching listing as Markdown: {slug}...")
res = requests.get(f"{BASE_URL}/api/listings/{slug}.md")
res.raise_for_status()
markdown = res.text
# Print first 60 lines for brevity
lines = markdown.split("\n")
print(f" ✅ Got {len(lines)} lines of Markdown:")
print(" " + "\n ".join(lines[:30]))
if len(lines) > 30:
print(f" ... ({len(lines) - 30} more lines)")
return markdown
def main():
print("=" * 60)
print(" Owning.pro API — Complete Example")
print("=" * 60)
# Step 1: Search (public — no API key needed)
results = search_listings(query="bavaria", limit=5)
# Steps 2-4 require an API key
check_api_key()
# Step 2: Upload an image (optional — uncomment if you have a local image)
# images = upload_image("/path/to/photo.jpg")
images = None # Set to None to skip image upload
# Step 3: Create a listing
listing = create_listing(images=images)
listing_id = listing["id"]
listing_slug = listing["slug"]
# Step 4: Publish it
publish_listing(listing_id)
# Step 5: Fetch as Markdown (public)
get_listing_markdown(listing_slug)
print("\n" + "=" * 60)
print(" ✅ All steps completed successfully!")
print("=" * 60)
print(f"\n Listing URL: https://owning.pro/listing/{listing_slug}")
print(f" Markdown URL: https://api.owning.pro/api/listings/{listing_slug}.md")
if __name__ == "__main__":
main()
Expected output
============================================================
Owning.pro API — Complete Example
============================================================
🔍 Searching for 'bavaria' (limit=5)...
Found 42 listings (showing 5):
• Bavaria 2023 Bavaria Cruiser 46 Style — 286,000 EUR (Ören, TR)
• Bavaria 2022 Bavaria C42 — 215,000 EUR (Palma, ES)
• ...
📝 Creating a draft listing...
✅ Created: id=lst_01KX..., slug=macbook-pro-16-2023-m3-max, status=draft
🚀 Publishing listing lst_01KX...
✅ Published! Status: active
📄 Fetching listing as Markdown: macbook-pro-16-2023-m3-max...
✅ Got 45 lines of Markdown:
id: "lst_01KX..."
...
# MacBook Pro 16" 2023 M3 Max
**Price:** €2,800 (negotiable)
...
============================================================
✅ All steps completed successfully!
============================================================
Listing URL: https://owning.pro/listing/macbook-pro-16-2023-m3-max
Markdown URL: https://api.owning.pro/api/listings/macbook-pro-16-2023-m3-max.md
2. TypeScript/Node — Complete Script
A complete script using the native fetch API (Node.js 18+). Same workflow as the Python script.
Prerequisites
# Requires Node.js 18+ (native fetch)
node --version # should be v18 or higher
Script
#!/usr/bin/env node
/**
* Owning.pro API — Complete example script (TypeScript/Node).
*
* Demonstrates:
* 1. Search listings (public, no auth)
* 2. Upload an image (requires API key)
* 3. Create a listing with the image (requires API key)
* 4. Publish the listing (requires API key)
* 5. Fetch the listing as Markdown (public, no auth)
*
* Usage:
* export OWNING_API_KEY="own_..."
* npx tsx owning_example.ts
* # or: node owning_example.mjs (after removing type annotations)
*/
import { readFile } from "node:fs/promises";
const BASE_URL = "https://api.owning.pro";
const API_KEY = process.env.OWNING_API_KEY ?? "";
// ─── Helpers ───────────────────────────────────────────────
interface Listing {
id: string;
slug: string;
title: string;
status: string;
price: { amount: number; currency: string; negotiable: boolean };
location: { country: string; city: string; shipping: boolean };
[key: string]: unknown;
}
async function apiCall(
path: string,
options: RequestInit = {},
): Promise<any> {
const url = `${BASE_URL}${path}`;
const headers: Record<string, string> = {
...(options.headers as Record<string, string>),
};
// Add auth header if we have an API key
if (API_KEY && !headers["Authorization"]) {
headers["Authorization"] = `Bearer ${API_KEY}`;
}
// Add Content-Type for JSON bodies (unless it's FormData)
if (options.body && !(options.body instanceof FormData) && !headers["Content-Type"]) {
headers["Content-Type"] = "application/json";
}
const res = await fetch(url, { ...options, headers });
if (!res.ok) {
const error = await res.json().catch(() => ({}));
throw new Error(
`API ${res.status}: ${error?.error?.message ?? res.statusText}`,
);
}
// Return text for .md endpoints, JSON otherwise
const contentType = res.headers.get("content-type") ?? "";
if (contentType.includes("text/markdown")) {
return res.text();
}
return res.json();
}
// ─── Step functions ────────────────────────────────────────
async function searchListings(query: string, limit = 5) {
console.log(`\n🔍 Searching for '${query}' (limit=${limit})...`);
const params = new URLSearchParams({ q: query, limit: String(limit) });
const data = await apiCall(`/api/listings?${params}`);
console.log(
` Found ${data.pagination.total} listings (showing ${data.results.length}):`,
);
for (const listing of data.results) {
const { amount, currency } = listing.price;
console.log(
` • ${listing.title} — ${amount.toLocaleString()} ${currency} (${listing.location.city}, ${listing.location.country})`,
);
}
return data.results as Listing[];
}
async function uploadImage(imagePath: string) {
console.log(`\n📸 Uploading image: ${imagePath}...`);
const buffer = await readFile(imagePath);
const blob = new Blob([buffer]);
const formData = new FormData();
formData.append("image", blob, imagePath.split("/").pop() ?? "photo.jpg");
const data = await apiCall("/api/upload", {
method: "POST",
body: formData,
// Don't set Content-Type — fetch will set multipart boundary automatically
});
console.log(` ✅ Uploaded: ${data.url}`);
return { url: data.url as string, alt: imagePath.split("/").pop() };
}
async function createListing(images?: Array<{ url: string; alt: string }>) {
console.log("\n📝 Creating a draft listing...");
const payload: Record<string, unknown> = {
title: 'MacBook Pro 16" 2023 M3 Max',
description:
"Excellent condition MacBook Pro 16 inch with M3 Max chip, " +
"64GB unified memory, 2TB SSD. Used for 6 months, no scratches. " +
"Original box and charger included.",
category: "laptops",
condition: "like_new",
type: "sale",
price: { amount: 2800, currency: "EUR", negotiable: true },
location: { country: "ES", city: "Madrid", shipping: true },
tags: ["macbook", "apple", "laptop"],
};
if (images) payload.images = images;
const data = await apiCall("/api/listings", {
method: "POST",
body: JSON.stringify(payload),
});
console.log(
` ✅ Created: id=${data.id}, slug=${data.slug}, status=${data.status}`,
);
return data as Listing;
}
async function publishListing(listingId: string) {
console.log(`\n🚀 Publishing listing ${listingId}...`);
const data = await apiCall(`/api/listings/${listingId}/publish`, {
method: "POST",
});
console.log(` ✅ Published! Status: ${data.status}`);
return data;
}
async function getListingMarkdown(slug: string) {
console.log(`\n📄 Fetching listing as Markdown: ${slug}...`);
const markdown = await apiCall(`/api/listings/${slug}.md`);
const lines = markdown.split("\n");
console.log(` ✅ Got ${lines.length} lines of Markdown:`);
console.log(" " + lines.slice(0, 30).join("\n "));
if (lines.length > 30) {
console.log(` ... (${lines.length - 30} more lines)`);
}
return markdown as string;
}
// ─── Main ──────────────────────────────────────────────────
async function main() {
console.log("=".repeat(60));
console.log(" Owning.pro API — Complete Example (TypeScript)");
console.log("=".repeat(60));
// Step 1: Search (public — no API key needed)
await searchListings("bavaria", 5);
// Steps 2-4 require an API key
if (!API_KEY) {
console.log("\n⚠️ OWNING_API_KEY not set — skipping write operations.");
console.log(" Set it to run the full workflow: export OWNING_API_KEY='own_...'");
return;
}
if (!API_KEY.startsWith("own_")) {
console.log("\n❌ API key should start with 'own_'. Check your value.");
return;
}
// Step 2: Upload an image (optional — uncomment if you have a local image)
// const images = [await uploadImage("./photo.jpg")];
const images = undefined; // Set to undefined to skip image upload
// Step 3: Create a listing
const listing = await createListing(images);
// Step 4: Publish it
await publishListing(listing.id);
// Step 5: Fetch as Markdown (public)
await getListingMarkdown(listing.slug);
console.log("\n" + "=".repeat(60));
console.log(" ✅ All steps completed successfully!");
console.log("=".repeat(60));
console.log(`\n Listing URL: https://owning.pro/listing/${listing.slug}`);
console.log(` Markdown URL: https://api.owning.pro/api/listings/${listing.slug}.md`);
}
main().catch((err) => {
console.error("\n❌ Error:", err.message);
process.exit(1);
});
Running it
# Option 1: Using tsx (recommended)
npx tsx owning_example.ts
# Option 2: Using Node.js directly (strip type annotations first, save as .mjs)
node owning_example.mjs
# Option 3: Using Bun
bun run owning_example.ts
3. Bash/curl — One-Liners
Quick copy-paste commands for common operations. Set your API key first:
export OWNING_API_KEY="own_aBcD123..."
Authentication
# Register a new account
curl -s -X POST https://api.owning.pro/api/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"dev@example.com","password":"secret123","name":"Dev"}' | python3 -m json.tool
# Login (existing account)
curl -s -X POST https://api.owning.pro/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"dev@example.com","password":"secret123"}' | python3 -m json.tool
# Create an API key (requires JWT from register/login)
JWT="eyJhbGci..."
curl -s -X POST https://api.owning.pro/api/api-keys \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{"name":"My App"}' | python3 -m json.tool
# List your API keys
curl -s https://api.owning.pro/api/api-keys \
-H "Authorization: Bearer $OWNING_API_KEY" | python3 -m json.tool
Search listings (public — no auth needed)
# Full-text search
curl -s "https://api.owning.pro/api/listings?q=bavaria&limit=10" | python3 -m json.tool
# Filter by category + price range, sort by price ascending
curl -s "https://api.owning.pro/api/listings?category=boats&min_price=100000&max_price=500000&sort=price_asc" | python3 -m json.tool
# Filter by country
curl -s "https://api.owning.pro/api/listings?country=ES&limit=10" | python3 -m json.tool
# Dynamic attribute filters (boats: sail, min length 10m) — use -g to disable globbing
curl -g -s "https://api.owning.pro/api/listings?category=boats&attr[boat_category]=sail&attr[min_length]=10" | python3 -m json.tool
# Filter by condition
curl -s "https://api.owning.pro/api/listings?condition=like_new&limit=10" | python3 -m json.tool
# Pagination — page 2, 50 results per page
curl -s "https://api.owning.pro/api/listings?page=2&limit=50" | python3 -m json.tool
Get a single listing
# By ID (JSON)
curl -s "https://api.owning.pro/api/listings/lst_01KX8FEV5X12M1ASPYPTBNXJ6Q" | python3 -m json.tool
# By slug (JSON)
curl -s "https://api.owning.pro/api/listings/bavaria-2023-bavaria-cruiser-46-style" | python3 -m json.tool
# As Markdown (machine-readable)
curl -s "https://api.owning.pro/api/listings/bavaria-2023-bavaria-cruiser-46-style.md"
# Save Markdown to a file
curl -s "https://api.owning.pro/api/listings/bavaria-2023-bavaria-cruiser-46-style.md" -o listing.md
Categories
# Full category tree
curl -s https://api.owning.pro/api/categories | python3 -m json.tool
# Flat list
curl -s https://api.owning.pro/api/categories/flat | python3 -m json.tool
# Single category
curl -s https://api.owning.pro/api/categories/boats | python3 -m json.tool
Asset types (discover available attributes)
# Boats asset type — 19 attributes (10 filterable)
curl -s https://api.owning.pro/api/asset-types/boats | python3 -m json.tool
# Cars asset type — 13 attributes
curl -s https://api.owning.pro/api/asset-types/cars | python3 -m json.tool
Create + publish a listing
# Create a draft listing
LISTING=$(curl -s -X POST https://api.owning.pro/api/listings \
-H "Authorization: Bearer $OWNING_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "MacBook Pro 16\" 2023 M3 Max",
"description": "Excellent condition MacBook Pro 16 inch with M3 Max chip, 64GB RAM, 2TB SSD.",
"category": "laptops",
"condition": "like_new",
"type": "sale",
"price": {"amount": 2800, "currency": "EUR", "negotiable": true},
"location": {"country": "ES", "city": "Madrid", "shipping": true},
"tags": ["macbook", "apple", "laptop"]
}')
# Extract the listing ID
LISTING_ID=$(echo $LISTING | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
echo "Listing ID: $LISTING_ID"
# Publish it
curl -s -X POST "https://api.owning.pro/api/listings/$LISTING_ID/publish" \
-H "Authorization: Bearer $OWNING_API_KEY" | python3 -m json.tool
Upload an image
# Standalone upload (returns a URL)
curl -s -X POST https://api.owning.pro/api/upload \
-H "Authorization: Bearer $OWNING_API_KEY" \
-F "image=@/path/to/photo.jpg" | python3 -m json.tool
# Attach to an existing listing
LISTING_ID="lst_01KX..."
curl -s -X POST "https://api.owning.pro/api/listings/$LISTING_ID/images" \
-H "Authorization: Bearer $OWNING_API_KEY" \
-F "image=@/path/to/photo.jpg" | python3 -m json.tool
Manage listings
# Update a listing (owner only)
LISTING_ID="lst_01KX..."
curl -s -X PUT "https://api.owning.pro/api/listings/$LISTING_ID" \
-H "Authorization: Bearer $OWNING_API_KEY" \
-H "Content-Type: application/json" \
-d '{"price": {"amount": 2500, "currency": "EUR", "negotiable": false}}' | python3 -m json.tool
# Change status: active → paused
curl -s -X PATCH "https://api.owning.pro/api/listings/$LISTING_ID" \
-H "Authorization: Bearer $OWNING_API_KEY" \
-H "Content-Type: application/json" \
-d '{"status": "paused"}' | python3 -m json.tool
# Mark as sold
curl -s -X PATCH "https://api.owning.pro/api/listings/$LISTING_ID" \
-H "Authorization: Bearer $OWNING_API_KEY" \
-H "Content-Type: application/json" \
-d '{"status": "sold"}' | python3 -m json.tool
# Delete (soft delete — owner only)
curl -s -X DELETE "https://api.owning.pro/api/listings/$LISTING_ID" \
-H "Authorization: Bearer $OWNING_API_KEY" | python3 -m json.tool
Contact a seller (public — no auth needed)
curl -s -X POST "https://api.owning.pro/api/listings/bavaria-2023-bavaria-cruiser-46-style/contact" \
-H "Content-Type: application/json" \
-d '{
"name": "John Buyer",
"email": "john@example.com",
"message": "Is this boat still available? Can you send more photos?"
}' | python3 -m json.tool
Discovery & schema
# Agent discovery manifest
curl -s https://api.owning.pro/.well-known/ai.json | python3 -m json.tool
# JSON Schema for listings
curl -s https://api.owning.pro/api/schema/listing | python3 -m json.tool
# OpenAPI 3.0 spec
curl -s https://api.owning.pro/api/openapi.json | python3 -m json.tool | head -50
# Health check
curl -s https://api.owning.pro/health | python3 -m json.tool
Related Documentation
| Document | Description |
|---|---|
| Developer Quickstart | 10-minute guide — zero to first listing |
| API Reference | Complete API documentation (19+ endpoints) |
| API Quickstart | One-page copy-paste curl reference |
| MCP Quickstart | Connect Claude Desktop or Cursor to Owning |
| Developer Onboarding | Full step-by-step onboarding guide |
| Agent Onboarding v2 | Extended guide for AI agents |
| Interactive API docs (Scalar) | Explore and test endpoints in the browser |
| Agent discovery | Machine-readable API manifest |
Questions? Email hello@owning.pro or visit /help.