MeetyyAPI
Documentation / API Reference / Global Search

Global Search API

One search box, four result groups. A single endpoint searches posts, people, communities and courses, returning either a short preview of every group (the "All" tab) or a longer list for one group.

  • Base URL: /api/v1
  • Auth: required โ€” Authorization: Bearer {token}. There is no public search.
  • Pagination: none. Results are capped lists; there is no page / per_page and no paginator envelope. Use limit to ask for more rows.

Replaces GET /api/v1/feed/search, which no longer exists. Post search moved here unchanged โ€” same post object, same visibility rules.


Response envelope

Success { "status": "success", "message": "โ€ฆ", "data": { } } Error { "status": "error", "message": "โ€ฆ", "data": { } }

Status codes

Code Meaning
200 OK
401 Missing/invalid token
422 Validation error (no term, both terms, unknown tab, limit over 50, h with a non-post tab)
500 Server error

The endpoint

GET /api/v1/search
GET /api/v1/search?q=laravel                        โ†’ all four tabs, 5 rows each
GET /api/v1/search?q=laravel&limit=10               โ†’ all four tabs, 10 rows each
GET /api/v1/search?q=laravel&tab=user               โ†’ people only, 20 rows
GET /api/v1/search?q=laravel&tab=post&sort=recent   โ†’ posts only, newest first
GET /api/v1/search?q=laravel&tab=course&scope=member โ†’ only courses the caller enrolled in
GET /api/v1/search?h=laravel                        โ†’ posts carrying #laravel
GET /api/v1/search?h=%23laravel                     โ†’ same (a leading '#' is ignored)

Parameters

Param Type Required Notes
q string one of Free-text term, 1โ€“100 chars. Matched with LIKE
h string one of Hashtag, with or without #, 1โ€“100 chars. Posts only
tab enum no post ยท user ยท community ยท course. Omit in q mode to search all four
limit int no Rows per tab. Default 5 without tab, 20 with one. Max 50
sort enum no Post tab only โ€” top (engagement-ranked, default) ยท recent (newest first)
scope enum no Course & community tabs only โ€” all (default) ยท created ยท member ยท public

q and h are mutually exclusive: sending both is a 422, sending neither is a 422.


The two modes

q โ€” free-text

Matches with LIKE '%term%', per tab:

Tab Columns matched
post post body
user full_name, username, bio, and the headline on either role profile
community name, tagline
course title, subtitle, description

Wildcard characters in the term (%, _) are escaped, so they match literally rather than widening the search.

h โ€” hashtag

Returns posts carrying exactly this hashtag. Tag names are normalised the same way the feed stores them (leading # stripped, lowercased), so #Laravel, Laravel and laravel are one and the same tag.

h always describes the post tab โ€” it may be sent with tab=post or with no tab at all, but pairing it with any other tab is a 422.

The two modes are not interchangeable. A post whose body reads "devlife is a state of mind" is a q=devlife hit but not an h=devlife one โ€” it never carried the tag. A post reading "shipping today #devlife" is a hit in both, because the tag text is part of the body.


Response shape

data.results is always an object keyed by tab โ€” all four keys when no tab was given, exactly one when there was. Clients parse one shape either way.

All tabs โ€” GET /api/v1/search?q=laravel

{
  "status": "success",
  "message": "Search results retrieved successfully.",
  "data": {
    "mode": "q",
    "query": "laravel",
    "tab": null,
    "results": {
      "post":      [ /* post objects,      up to `limit` */ ],
      "user":      [ /* user objects,      up to `limit` */ ],
      "community": [ /* community objects, up to `limit` */ ],
      "course":    [ /* course objects,    up to `limit` */ ]
    }
  }
}

One tab โ€” GET /api/v1/search?h=laravel

{
  "status": "success",
  "message": "Search results retrieved successfully.",
  "data": {
    "mode": "h",
    "query": "laravel",
    "tab": "post",
    "results": {
      "post": [ /* post objects, up to `limit` */ ]
    }
  }
}
Field Notes
mode q or h โ€” which parameter drove the search
query The term that was searched, trimmed. For h, as the client sent it
tab The single tab searched, or null when all four were
results Object keyed by tab; each value is a plain array of rows

What each tab returns

post

The same post object as the feed โ€” see Public Feed for its full field list โ€” and the same visibility rules: the caller's own posts and their connections' posts (any visibility) plus all public posts. A stranger's connections-only post never appears.

Ordered by sort: top ranks on the engagement hot_score, recent on created_at.

user

{
  "id": "9b1cโ€ฆ-uuid",
  "name": "Ada Lovelace",
  "username": "ada",
  "avatar_url": "https://โ€ฆ/avatar.jpg",
  "connection_status": "connected",
  "is_connected": true,
  "bio": "Analytical engines, mostly.",
  "headline": "Senior Engineer @ Acme",
  "role": "mentor"
}

connection_status is the caller's state toward this person โ€” none, pending_outgoing, pending_incoming, or connected โ€” so a result row can render a Connect button directly. headline comes from whichever role profile the person has.

Deactivated and banned accounts are never returned, and the caller never matches themselves. Ranking puts an exact username/name match first, then a prefix match, then everything else (a hit found only in the bio or headline).

Searching mentors by expertise area, price, rating, language or availability is a different surface โ€” that is the mentee-facing mentor listing (GET /api/v1/mentee/mentors) with its filter panel. Global search finds a person by who they are; it is not a filtered catalogue browse.

community

The community summary object โ€” id, name, slug, tagline, status, pricing_type (+ label), billing_interval, price, currency, member_count, cover_url, icon_url, mentor โ€” plus two fields describing the caller's relationship:

{
  "id": "9b1cโ€ฆ-uuid",
  "name": "The Laravel Guild",
  "slug": "the-laravel-guild",
  "tagline": "Ship faster, together",
  "status": "published",
  "pricing_type": "subscription",
  "pricing_type_label": "Subscription",
  "billing_interval": "monthly",
  "price": "19.00",
  "currency": "USD",
  "member_count": 412,
  "cover_url": "https://โ€ฆ/cover.jpg",
  "icon_url": null,
  "mentor": { "id": "โ€ฆ", "name": "Ada Lovelace", "username": "ada", "headline": "โ€ฆ", "average_rating": "4.90", "avatar_url": "โ€ฆ" },
  "is_creator": false,
  "member_role": "member"
}
Field Notes
is_creator The caller owns this community
member_role owner ยท member, or null when the caller has no active membership

Ordered by member_count descending.

course

The standard course object โ€” see Courses โ€” plus:

Field Notes
is_creator The caller authored this course
is_enrolled The caller holds an enrollment on it

Ordered by enrollment_count descending.


Visibility: what the caller can find

Search never leaks anything. Beyond the post rules above:

People โ€” active, non-banned accounts only; the caller is excluded from their own results.

Courses โ€” the union of:

Arm Rule
public status = published and visibility = public
created authored by the caller โ€” any status, drafts included
enrolled the caller holds an enrollment โ€” any status or visibility, so a community_only course they paid for is findable

Communities โ€” the union of:

Arm Rule
public status = published
created owned by the caller โ€” any status, drafts included
joined the caller holds an active membership โ€” any status

Another mentor's draft course or unpublished community is never returned.

Narrowing with scope

scope restricts the course and community tabs to a single arm โ€” the tab UI for "My courses" / "My communities" needs no separate endpoint:

Value Returns
all (default) The full union above
created Only what the caller authored/owns
member Only what the caller enrolled in / joined
public Only the published, publicly-listed rows

scope is ignored by the post and user tabs. An arm the caller has no claim to (e.g. scope=created for someone who is not a mentor) returns an empty list, never everything.


Errors

Request Result
(no q, no h) 422 โ€” Provide either a search term (q) or a hashtag (h).
?q=a&h=b 422 โ€” Search by term (q) or by hashtag (h), not both.
?q=a&tab=posts 422 โ€” unknown tab (note the tabs are singular: post, not posts)
?q=a&limit=51 422 โ€” limit may not exceed 50
?h=tag&tab=user 422 โ€” A hashtag search only covers the post tab.
(no token) 401

Notes for clients

  • Tab counts. The response carries no totals โ€” a capped list of limit rows is all you get. If a tab returns exactly limit rows, assume there may be more and re-request that tab with a larger limit (up to 50).
  • Two calls, not five. Render the All view from one ?q= call, then re-request a single tab with ?tab= when the user opens it. Do not fan out one call per tab.
  • Tapping a hashtag in any post should search ?h={tag}, not ?q=#{tag} โ€” the former finds posts that carry the tag, the latter finds posts that merely mention the text.