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 thetokenvalue — you'll use it in Step 2 to create an API key. Already have an account? UsePOST /api/auth/loginwith the same payload (omitname) 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"
}
⚠️ Thekeyfield 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 categoryslug(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 theidvalue — you'll need it to publish the listing. Which attributes can I use? CheckGET /api/asset-types/{type}(e.g.,boats,cars) to see all valid category-specific attributes and their filter types. For example, boats supportbrand,year,length,boat_type,fuel_type, and more. Want to add images? Upload them first viaPOST /api/upload(multipart form data), then include the returned URLs in theimagesarray. 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 returns400withcode: "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, tryattr[brand]=bavaria,attr[boat_category]=sail,attr[min_year]=2020. UseGET /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.toolto pretty-print JSON responses - Use
-gwithattr[...]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?
- 📖 Full API Reference — all 19+ endpoints documented
- 📋 API Quickstart — one-page copy-paste reference
- 🐍 Developer Examples — complete Python, TypeScript, and Bash scripts
- 🤖 MCP Quickstart — connect Claude Desktop or Cursor to Owning
- 🔍 Interactive API docs (Scalar) — explore and test in the browser
- 🤖 Agent discovery — machine-readable API manifest
- ✉️ Email
hello@owning.proor visit /help
Questions? Email hello@owning.pro or visit /help.