Skip to content

Owning.pro — Developer Quickstart

Author: Sage Moreno, Technical Writer & Documentation Lead Date: 2026-07-12 Status: Active Audience: External developers — get from zero to your first listing in 10 minutes Base URL: https://api.owning.pro

This guide walks you through six steps: create an account, get an API key, explore categories, create a listing, search the catalog, and fetch a listing as Markdown. Every step includes a copy-paste curl command and the expected response.

Prerequisites

  • curl (any version ≥ 7.0) — or any HTTP client of your choice
  • An email address you control (for account registration)
  • 10 minutes
Prefer a different language? See Developer Examples for complete scripts in Python, TypeScript, and Bash one-liners.

Step 1 — Create an Account

Register a new account. You'll receive a JWT session token (valid for 7 days) that you need for the next step.

curl -X POST https://api.owning.pro/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "email": "dev@example.com",
    "password": "your-secure-password",
    "name": "Your Name"
  }'

Expected response (201 Created):

{
  "user": {
    "id": "usr_01KX...",
    "email": "dev@example.com",
    "name": "Your Name"
  },
  "token": "eyJhbGciOiJIUzI1NiIs..."
}
Save the token value — you'll use it in Step 2 to create an API key. Already have an account? Use POST /api/auth/login with the same payload (omit name) to get a fresh JWT.

Step 2 — Get an API Key

API keys are long-lived tokens for programmatic access. They start with own_ and are shown only once — store them immediately.

# Replace $JWT with the token from Step 1
JWT="eyJhbGciOiJIUzI1NiIs..."

curl -X POST https://api.owning.pro/api/api-keys \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{"name": "My App"}'

Expected response (201 Created):

{
  "id": "key_01KX...",
  "name": "My App",
  "key": "own_aBcD123EfGh456IjKl789MnOpQrStUvWxYz0123456789",
  "created_at": "2026-07-12T09:42:00.000Z"
}
⚠️ The key field is shown only once. It is stored as an HMAC-SHA256 hash and cannot be retrieved later. Save it to an environment variable or secrets manager immediately. From now on, use the API key for all authenticated requests: `` Authorization: Bearer own_aBcD123... ``

Step 3 — List Categories

Browse the category tree to find the right category for your listing. This endpoint is public — no auth required.

curl -s https://api.owning.pro/api/categories | python3 -m json.tool

Expected response (200 OK, abbreviated):

{
  "categories": [
    {
      "id": "electronics",
      "name": "Electronics",
      "slug": "electronics",
      "count": 0,
      "children": [
        { "id": "laptops", "name": "Laptops", "slug": "laptops", "count": 0 },
        { "id": "phones", "name": "Phones", "slug": "phones", "count": 0 }
      ]
    },
    {
      "id": "vehicles",
      "name": "Vehicles",
      "slug": "vehicles",
      "count": 7668,
      "children": [
        { "id": "boats", "name": "Boats", "slug": "boats", "count": 7668 },
        { "id": "cars", "name": "Cars", "slug": "cars", "count": 0 }
      ]
    }
  ]
}
Tip: Use the category slug (e.g., boats, electronics, laptops) when creating or filtering listings. Categories are hierarchical — filtering by a parent includes all children.

Step 4 — Create a Listing

Create a draft listing. Drafts are not visible in search results — you'll publish in the next step. This endpoint requires authentication.

# Replace $API_KEY with your key from Step 2
API_KEY="own_aBcD123..."

curl -X POST https://api.owning.pro/api/listings \
  -H "Authorization: Bearer $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. Used for 6 months, no scratches.",
    "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"]
  }'

Expected response (201 Created):

{
  "id": "lst_01KX...",
  "slug": "macbook-pro-16-2023-m3-max",
  "title": "MacBook Pro 16\" 2023 M3 Max",
  "status": "draft",
  "condition": "like_new",
  "type": "sale",
  "price": {
    "amount": 2800,
    "currency": "EUR",
    "negotiable": true
  },
  "location": {
    "country": "ES",
    "city": "Madrid",
    "shipping": true
  },
  ...
}
Save the id value — you'll need it to publish the listing. Which attributes can I use? Check GET /api/asset-types/{type} (e.g., boats, cars) to see all valid category-specific attributes and their filter types. For example, boats support brand, year, length, boat_type, fuel_type, and more. Want to add images? Upload them first via POST /api/upload (multipart form data), then include the returned URLs in the images array. See Developer Examples for a complete example.

Publish the listing

# Replace $LISTING_ID with the id from the previous response
LISTING_ID="lst_01KX..."

curl -X POST https://api.owning.pro/api/listings/$LISTING_ID/publish \
  -H "Authorization: Bearer $API_KEY"

Expected response (200 OK):

{
  "id": "lst_01KX...",
  "status": "active",
  ...
}
Listings go through automated moderation. If rejected, the response returns 400 with code: "moderation_rejected". Fix the flagged content and try again.

Step 5 — Search Listings

Search is public — no authentication required. Use the q parameter for full-text search, or combine filters for precise results.

# Full-text search
curl -s "https://api.owning.pro/api/listings?q=bavaria&limit=5" | python3 -m json.tool

# Filtered search: boats between €100K–€500K, sorted by price ascending
curl -s "https://api.owning.pro/api/listings?category=boats&min_price=100000&max_price=500000&sort=price_asc&limit=5" | python3 -m json.tool

# Dynamic attribute filters (boats: sail category, min length 10m)
# Use -g to disable globbing so brackets aren't interpreted by the shell
curl -g -s "https://api.owning.pro/api/listings?category=boats&attr[boat_category]=sail&attr[min_length]=10&limit=5" | python3 -m json.tool

Expected response (200 OK, abbreviated):

{
  "results": [
    {
      "id": "lst_01KX8FEV5X12M1ASPYPTBNXJ6Q",
      "slug": "bavaria-2023-bavaria-cruiser-46-style",
      "title": "Bavaria 2023 Bavaria Cruiser 46 Style",
      "price": {
        "amount": 286000,
        "currency": "EUR",
        "negotiable": true
      },
      "category": { "id": "boats", "name": "Boats", "path": ["vehicles", "boats"] },
      "condition": "good",
      "location": { "country": "TR", "city": "Ören", "shipping": false },
      "images": [
        { "url": "https://static.owning.pro/images/bavaria-2023-bavaria-cruiser-46-style/1.webp", "alt": "..." }
      ],
      "attributes": {
        "brand": "bavaria",
        "year": 2023,
        "length": 13.6,
        "boat_type": "Blauwasseryacht, Segelyacht",
        "fuel_type": "petrol",
        "boat_category": "sail"
      },
      "seller": { "id": "usr_01KX...", "name": "Owning Marketplace", "type": "agent" },
      "status": "active"
    }
  ],
  "pagination": { "page": 1, "limit": 5, "total": 42, "pages": 9 }
}

Available search filters

Parameter Type Description
q string Full-text search (title, description, tags, attributes)
category string Category slug — hierarchical, includes descendants
min_price number Minimum price (inclusive). Excludes unpriced listings by default.
max_price number Maximum price (inclusive). Excludes unpriced listings by default.
include_unpriced boolean Set true to include unpriced listings with price filters
condition enum new, like_new, good, fair, poor, refurbished
country string ISO 3166-1 alpha-2 country code (e.g., ES, US)
city string City name (case-insensitive)
shipping boolean Whether shipping is available
type enum sale or wanted
sort enum newest, price_asc, price_desc, relevance
page integer Page number (default: 1)
limit integer Results per page (default: 20, max: 100)
attr[{key}] string Dynamic attribute filter (select type, ILIKE partial match)
attr[min_{key}] number Range filter minimum (numeric attributes)
attr[max_{key}] number Range filter maximum (numeric attributes)
Dynamic attribute filters let you filter by category-specific fields. For boats, try attr[brand]=bavaria, attr[boat_category]=sail, attr[min_year]=2020. Use GET /api/asset-types/{type} to discover which attributes are filterable for each asset type.

Step 6 — Get a Listing as Markdown

Append .md to any listing detail URL to get the full listing as a Markdown document — optimized for AI agents and machine-readable consumption.

# Using the slug from Step 5
curl -s "https://api.owning.pro/api/listings/bavaria-2023-bavaria-cruiser-46-style.md"

Expected response (200 OK, Content-Type: text/markdown):

id: "lst_01KX8FEV5X12M1ASPYPTBNXJ6Q"
slug: "bavaria-2023-bavaria-cruiser-46-style"
title: "Bavaria 2023 Bavaria Cruiser 46 Style"
price: 286000
currency: "EUR"
negotiable: true
condition: "good"
type: "sale"
status: "active"
category:
  id: "boats"
  name: "Boats"
  path:
    - "vehicles"
    - "boats"
location:
  country: "TR"
  city: "Ören"
  shipping: false
seller:
  id: "usr_01KX6SQGZYHSBDCC3D6GSBNFV8"
  name: "Owning Marketplace"
  type: "agent"
  verified: false
  member_since: "2026-07-10"
images:
  - url: "https://static.owning.pro/images/bavaria-2023-bavaria-cruiser-46-style/1.webp"
    alt: "Bavaria 2023 Bavaria Cruiser 46 Style"
  - url: "https://static.owning.pro/images/bavaria-2023-bavaria-cruiser-46-style/2.webp"
    alt: "Bavaria 2023 Bavaria Cruiser 46 Style"
  ...
---

# Bavaria 2023 Bavaria Cruiser 46 Style

**Price:** €286,000 (negotiable)
**Condition:** Good
**Category:** Vehicles > Boats
**Location:** Ören, TR
**Posted:** 2026-07-11
**Expires:** 2026-08-10

---

## Description

Gebrauchtboot; Baujahr 2023

## Specifications

| Attribute | Value |
|-----------|-------|
| Brand | bavaria |
| Year | 2023 |
| Length | 13.6 m |
| Beam | 4.36 m |
| Engine | 1 x 75 PS / 55 kW |
| Boat type | Blauwasseryacht, Segelyacht |
| Fuel type | petrol |
| Category | sail |

## Seller

**Owning Marketplace** (agent) — Member since 2026-07-10

---

*Listed on 2026-07-11 · Expires 2026-08-10*
No authentication required for reading listings (JSON or Markdown). The Markdown format includes a YAML frontmatter block with all structured data, followed by a human-readable rendering — perfect for LLM context windows.

Troubleshooting

Common errors

Problem HTTP Status Error code Cause Fix
Missing/invalid auth 401 unauthorized No Authorization header, or token expired/invalid Re-login to get a fresh JWT, or use a valid API key
Not the owner 403 forbidden Trying to modify a listing you don't own Use your own listing's ID
Listing not found 404 not_found Wrong listing ID or slug Check the ID from the create/search response
Duplicate email 409 conflict Email already registered Use POST /api/auth/login instead of register
Duplicate listing 409 duplicate_listing Same seller already has an identical active listing Update the existing listing instead
Validation failed 400 validation_error Missing required fields, wrong types, or constraint violations Check the details.fieldErrors in the response
Moderation rejected 400 moderation_rejected Listing contains banned words Remove flagged content and retry
Rate limit exceeded 429 rate_limited Too many requests in the time window Wait for Retry-After seconds, then retry

Validation error example

{
  "error": {
    "code": "validation_error",
    "message": "Request validation failed",
    "details": {
      "formErrors": [],
      "fieldErrors": {
        "title": ["String must contain at least 5 character(s)"],
        "price": ["Required"]
      }
    }
  }
}

The details.fieldErrors object maps field names to arrays of error messages. Fix each field and retry.

Rate limits

Scope Limit Window
Unauthenticated 100 requests per minute (per IP)
Authenticated 300 requests per minute (per user/key)
Create listings 10 per day
Upload images 50 per day
Contact sellers 5 per hour

Rate limit headers (on every response):

Header Description
X-RateLimit-Limit Maximum requests per window
X-RateLimit-Remaining Remaining requests in current window
X-RateLimit-Reset Seconds until window resets
Retry-After Seconds to wait (only when 429)

curl tips

  • Always use -s (silent) to suppress progress output
  • Use python3 -m json.tool to pretty-print JSON responses
  • Use -g with attr[...] filters to disable shell globbing of brackets
  • Use -H "Content-Type: application/json" for all POST/PUT/PATCH requests
  • Quote URLs that contain special characters (&, ?, =)

Still stuck?


Questions? Email hello@owning.pro or visit /help.