API Reference

Listings

Search, browse, create, and manage listings on Markidy.

GET /v1/listings/search

Search listings with filters. All parameters are optional.

Query Parameters

ParameterTypeDescription
qstringSearch query (optional)
categorystringCategory key
rolestringRole key (requires category)
countrystringCountry codes, comma-separated (kr,ja,us)
verifiedintegerVerification level (0, 1, 2)
sortstringrecent, trust (default), views, requests
pageintegerPage number (default: 1)
pageSizeintegerPage size (default: 16, max: 50)
meta.{key}stringSELECT field filter
meta.{key}_minstringNUMBER/DATE min value
meta.{key}_maxstringNUMBER/DATE max value
meta.{key}_latnumberGEO field latitude (use GET /v1/geocode to convert addresses)
meta.{key}_lngnumberGEO field longitude
Examples
# Text search with meta filter
curl "https://api.markidy.com/v1/listings/search?q=mentor" \
  -H "Authorization: Bearer mk_your_api_key"

# Browse by category with trust sort
curl "https://api.markidy.com/v1/listings/search?category=jobs&role=employer&sort=trust" \
  -H "Authorization: Bearer mk_your_api_key"

# GEO proximity search (sorted by distance)
curl "https://api.markidy.com/v1/listings/search?category=jobs&role=employer&meta.work-location_lat=37.77&meta.work-location_lng=-122.42" \
  -H "Authorization: Bearer mk_your_api_key"
Response
{
  "listings": [
    {
      "id": "clx...",
      "title": "Hiring Backend Engineer for Seoul Team",
      "description": "Looking for an experienced backend engineer to join our product team.",
      "categoryKey": "jobs",
      "categoryName": "Jobs",
      "roleKey": "employer",
      "roleName": "Employer",
      "verifiedLevel": 1,
      "country": "kr",
      "displayName": "John",
      "distanceKm": 3.2,
      "createdAt": "2026-03-15T10:00:00Z"
    }
  ],
  "total": 45,
  "page": 1,
  "pageSize": 16
}

Response Fields

FieldTypeDescription
idstringListing ID
titlestringDerived from displayTarget: "title" field
descriptionstringFrom displayTarget: "description" field
categoryKeystringCategory identifier
categoryNamestringCategory display name
roleKeystringRole identifier
roleNamestringRole display name
verifiedLevelintegerVerification level (0–2)
countrystringCountry code
displayNamestringDisplay name
createdAtstringISO 8601 timestamp
distanceKmnumber | nullDistance in km when GEO filter is active, null otherwise
GET /v1/listings/{id}

Get detailed listing information including the owner profile user ID, channel availability, and your existing request.

Request
curl https://api.markidy.com/v1/listings/clx123 \
  -H "Authorization: Bearer mk_your_api_key"
Response
{
  "listing": {
    "id": "clx...",
    "categoryKey": "mentoring",
    "roleKey": "mentor",
    "meta": {
      "headline": "Product mentor for early-stage founders",
      "about-me": "<p>I help teams ship faster with better product decisions.</p>",
      "format": "Remote",
      "rate": "$50-$100 / hour"
    },
    "profile": {
      "userId": "usr_123",
      "displayName": "John",
      "description": "Self-introduction...",
      "verifiedLevel": 1,
      "country": "kr",
      "socialLinks": {}
    },
    "channels": [
      { "key": "markidy", "available": true },
      { "key": "telegram", "available": true },
      { "key": "discord", "available": false, "reason": "requester_not_connected" }
    ],
    "myRequest": null,
    "createdAt": "2026-03-15T10:00:00Z"
  }
}
Use listing.profile.userId with GET /v1/profiles/{userId} to inspect the listing owner's public profile detail before sending a request.
channels shows per-requester availability. markidy is Markidy's built-in chat — always available: true when listed. External channels (telegram, discord) require both parties to be connected. myRequest is null if no request has been sent.
channels shows per-requester availability. markidy is Markidy's built-in chat — always available: true when listed. External channels (telegram, discord) require both parties to be connected. See Account → Channel Types for details. myRequest is null if no request has been sent.

Additional Fields (vs Search)

FieldTypeDescription
metaobjectDynamic field values
profile.userIdstringProfile owner user ID for GET /v1/profiles/{userId}
profile.displayNamestringDisplay name
profile.descriptionstringSelf-introduction
profile.verifiedLevelintegerVerification level (0–2)
profile.countrystringCountry code
profile.socialLinksobjectPublic social account URLs when configured
channels[].keystringChannel identifier
channels[].availablebooleanWhether this channel can be used for request
channels[].reasonstring | nullowner_disconnected — listing owner's channel is inactive. requester_not_connected — your API key's account hasn't connected this channel.
myRequestobject | nullExisting request from current user
myRequest.idstringRequest ID
myRequest.statusstringPENDING, ACCEPTED, REJECTED
GET /v1/listings/mine

List your own listings with optional status filter.

Query Parameters

ParameterTypeDescription
statusstringFilter: ACTIVE, PENDING, PAUSED, REJECTED
pageintegerPage number (default: 1)
pageSizeintegerPage size (default: 16)
Response
{
  "listings": [
    {
      "id": "clx...",
      "status": "ACTIVE",
      "categoryKey": "mentoring",
      "roleKey": "mentor",
      "meta": { "headline": "Product mentor for early-stage founders", "format": "Remote" },
      "createdAt": "2026-03-15T10:00:00Z"
    }
  ],
  "total": 3,
  "page": 1,
  "pageSize": 16
}

Response Fields

FieldTypeDescription
idstringListing ID
statusstringACTIVE, PENDING, PAUSED, REJECTED
categoryKeystringCategory identifier
roleKeystringRole identifier
metaobjectDynamic field values
createdAtstringISO 8601 timestamp
POST /v1/listings

Create a new listing. API-created listings always start as PENDING and require admin approval. Call GET /v1/categories/{key} first and follow each field's required, acceptedValueFormats, valueEncoding, and valueShape.

Request Body

FieldTypeRequiredDescription
categoryKeystringCategory key
roleKeystringRole key
metaobjectDynamic field values per role
channelsstring[]Channels to receive requests (must be connected)
webhookIdsstring[]Webhook IDs to trigger on match requests
Request Body
{
  "categoryKey": "jobs",
  "roleKey": "headhunter",
  "meta": {
    "headline": "Cross-border recruiter for backend engineers",
    "about-me": "<p>I help Korean engineers find Japan-based product teams.</p>",
    "firm-url": ["https://example.com"],
    "service-area": {
      "placeId": "seoul",
      "name": "Seoul",
      "address": "Seoul, South Korea",
      "lat": 37.5665,
      "lng": 126.978
    },
    "portfolio": [
      {
        "url": "https://cdn.example.com/banner.jpg",
        "link": "https://example.com/candidates"
      }
    ]
  },
  "channels": ["markidy"],
  "webhookIds": ["wh_abc123"]
}
Response (201)
{
  "id": "clx...",
  "status": "PENDING",
  "createdAt": "2026-03-18T12:00:00Z"
}
Listing creation is rate-limited to 5 per hour per API key. Verified Level 2 accounts get 10 per hour.
Complex field values can be sent as structured JSON when the field contract allows it. Examples: location = { "placeId": "...", "name": "Seoul", "address": "Seoul, South Korea", "lat": 37.5665, "lng": 126.9780 }, photos = [{ "url": "https://cdn.example.com/photo.jpg", "link": "https://example.com" }], company-url = ["https://example.com"]. Legacy JSON strings are still accepted, and the server normalizes both forms to the canonical valueEncoding/valueShape.
PATCH /v1/listings/{id}

Update a listing (partial update). Only include fields you want to change.

FieldTypeDescription
metaobjectUpdated field values, merged with existing
channelsstring[]Replaced entirely
webhookIdsstring[]Replaced entirely
Updating a listing resets its status to PENDING for re-approval.
PATCH /v1/listings/{id}/status

Pause or resume a listing.

StatusDescription
PAUSEDHides from search (only ACTIVE listings)
ACTIVEResumes a paused listing
Request Body
{ "status": "PAUSED" }
DELETE /v1/listings/{id}

Permanently delete a listing.

Response
{ "id": "clx...", "deleted": true }