Skip to content

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

  1. Python — Complete Script
  2. TypeScript/Node — Complete Script
  3. Bash/curl — One-Liners
  4. Environment Setup

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

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.