the "aura market" HTTP API behind usegiggles.com, as observed from the outside. facts last verified 2026-09-23. this is not official documentation.
2. authentication
Identity is a Supabase GoTrue session. The market API accepts the session's access token as a bearer token and reads the sub claim as the caller's user id. There is no API key for the market API itself.
the publishable ("anon") key
Supabase projects expose a publishable key (historically the "anon" key). It identifies the project to GoTrue and PostgREST; it is compiled into the official client and is not a secret. It grants nothing by itself: PostgREST rows are filtered by row-level security against the user JWT, and GoTrue endpoints only issue tokens after a credential (OTP code, OAuth grant, refresh token) is presented.
| call | headers |
GoTrue (/auth/v1/*) | apikey: <publishable-key>; on /otp and /token the client also sends Authorization: Bearer <publishable-key>; on /verify it sends apikey only. Both forms were accepted. |
PostgREST (/rest/v1/*) | apikey: <publishable-key> and Authorization: Bearer <access-token> (the user's JWT, never the publishable key, or RLS returns nothing). |
| market API | No apikey. Bearer only. |
access tokens
A JWT signed by the project. Clients decode the payload (base64url, second segment) without verifying it; the platform is the authority.
| claim | type | meaning |
sub | uuid | The user id used in every /users/{id} path. |
exp | integer, epoch seconds | Expiry. Observed lifetime ≈ 24 h (1438 minutes on a fresh token). |
email | string | Present for email and OAuth accounts. Required when updating the profile (PUT /api/user/profile). |
phone | string | Present for phone accounts; used as phone_number in the profile update. inferred |
app_metadata.provider | string | google, apple, or email. |
An expired token yields 401 from the market API. There is no password grant for OAuth accounts; a session comes only from the flows below.
refresh tokens
Every session includes a refresh token: an opaque string, not a JWT. Exchanging it returns a new access token and a new refresh token (rotation). Store the returned pair; the client code in the gigglantir repository overwrites both on every exchange.
POST {SB}/auth/v1/token?grant_type=refresh_token
apikey: <publishable-key>
Authorization: Bearer <publishable-key>
Content-Type: application/json
{"refresh_token": "<refresh-token>"}
200 OK
{"access_token": "<access-token>", "refresh_token": "<refresh-token>", "expires_in": ..., "user": {...}}
- Failure is a 4xx with
{"error_description": "..."} (or msg). Common causes: the refresh token was already rotated, or the session was signed out.
- Behaviour on reuse of an already-exchanged refresh token is unverified. GoTrue's documented default is to reject reuse outside a short grace window; treat a rejected exchange as "session lost, log in again".
- When to refresh: the gigglantir keep-alive refreshes any token with ≤ 3 h left and treats a token with < 60 s left as absent. Refreshing early is safe; there is no observed cost.
- Only the account's own refresh token can renew it. Tokens obtained by pasting a redirect URL without its
refresh_token fragment cannot be renewed and must be re-issued by a fresh login.
email OTP
Two calls. No phone number is involved.
POST {SB}/auth/v1/otp
apikey: <publishable-key>
Authorization: Bearer <publishable-key>
Content-Type: application/json
{"email": "[email protected]", "create_user": false}
POST {SB}/auth/v1/verify
apikey: <publishable-key>
Content-Type: application/json
{"type": "email", "email": "[email protected]", "token": "123456"}
→ 200 {"access_token": "...", "refresh_token": "...", ...}
create_user: false restricts the code to existing accounts. Whether create_user: true produces an account with a provisioned username and wallet is unverified.
- The project may require a captcha on
/otp; the gigglantir connect page detects an error containing captcha and directs the user to OAuth instead. Whether this is on for all callers is unverified.
/verify works only if the project's email template sends a 6-digit code rather than a magic link. Error bodies use msg or error_description.
Google and Apple OAuth
GET {SB}/auth/v1/authorize?provider=google&redirect_to=<redirect-url>
→ 302 to the provider
→ 302 back to <redirect-url>#access_token=...&refresh_token=...&expires_in=...&token_type=bearer
provider is google or apple.
- Tokens are returned in the URL fragment, so they never reach a server unless the page or the user forwards them. A script cannot complete this flow headlessly; a browser must.
- Redirect allow-list: GoTrue only honours a
redirect_to that the project has pre-registered; any other value falls back to the project's site URL. http://localhost:3000 is on the list (the gigglantir connect page relies on it: the browser lands on a dead localhost page and the user copies the address bar). The full list is unverified.
- Native-app device attestation, if any, gates token issuance in the app, not this web flow: a JWT obtained here is a normal session.
keeping a session alive from a script
- Obtain one session by OTP (scriptable) or OAuth (browser once; paste the fragment).
- Persist
access_token and refresh_token encrypted at rest. Never log either.
- Before each batch of calls, decode
exp. If less than a few hours remain, exchange the refresh token and persist the new pair before using it.
- On
401 from the market API, exchange the refresh token once; if that fails, stop and require a new login. Do not loop.
- Run exactly one client per token. See sharing a token.
# decode exp without verifying (python)
import base64, json, time
def claims(tok):
b = tok.split(".")[1]; b += "=" * (-len(b) % 4)
return json.loads(base64.urlsafe_b64decode(b))
ttl_s = claims(ACCESS_TOKEN)["exp"] - time.time()
3. concepts
posts as pots
Every post is a pot of aura. There is no order book and no counterparty: a buy adds aura to the pot and mints shares; a redeem burns the caller's shares and pays out of the pot. The pot is the only thing that pays.
floor and level
The floor is a fixed seed amount that sets the curve's origin. It is not part of the pot and is not redeemable by holders. Native posts carry a floor of 1000. Imported posts (isImport: true, media imported from another platform) carry a smaller floor, reported as 100 — unverified; read floor from the market response rather than assuming it.
level = floor + pot
The level is the number the app displays as the post's price. A buy of I raises the level by exactly I; a redeem that pays R lowers it by exactly R.
shares and ownership
A buy of bet into a post with pot pot mints
shares = 1e9 · ln((floor + pot + bet) / (floor + pot))
and the resulting ownership fraction of the pool is
frac = bet / (floor + pot + bet)
A redeem pays frac × (floor + pot_now), i.e. the holder's fraction of the current level. Because the fraction is fixed at entry, every aura added later is worth frac of itself to an existing holder, and every aura removed later costs the same. Ownership is concave in size: a larger single buy purchases proportionally less of the pool. Observed density is roughly 140k–193k shares per aura, rising slightly with the pot.
maxInvest and the position cap
maxInvest is the largest single buy the post accepts from the caller right now. It implements a position cap: an account's position value may never exceed half the level.
maxInvest = level − 2 × (own position value)
- Empty native post: level 1000, own 0 →
maxInvest = 1000.
- After a 1000 buy into that post: level 2000, own value 1000 →
maxInvest = 0. A second buy from the same account returns 400 POSITION_CAP. The first entrant holds 50 % and cannot add.
- A large stake can only sit on top of other people's aura:
bet ≤ floor + others' pot.
maxInvest: 0 means "no further buy accepted", not "unknown". A buy above maxInvest is rejected whole; it is not clipped.
redeem is all-or-nothing
The redeem call sells the caller's entire position in a post. The amount field in the body is ignored (verified 2026-09-21: amount: 0.5 redeemed 100 % of shares). There are no partial exits.
exit impact and max loss
Because a redeem pays out of the pot, a holder's exit removes their fraction of the level from the pot. On posts with one dominant holder the pot typically falls by 98–99 % on that holder's exit. A full unwind by all holders pays out exactly the pot; sell order only redistributes it.
max loss on entry = I × pot / (floor + pot + I)
Entering an empty pot therefore has a maximum loss near zero; entering a large pot risks nearly the whole stake.
fees
| fee | rate | where it appears |
| creator fee | ≈ 0.1 % of a redemption | creatorFee in the redeem response; accrues to the post's creator (creator_fees on market). |
| sell fee | 0.101 % flat | Deducted from the redemption. |
| card-trade aura fee | 1 % | limits.auraFeePct on trade-with. |
sell locks and timers
The market response carries sell_locked_until and sell_timer_s (also seen as sellLockedUntil / sellTimerSeconds). They indicate a window during which a redeem is refused. When the lock is applied, and what a locked redeem returns, is unverified; a client should read the fields and not redeem before sell_locked_until.
deleted posts
A creator can delete a post. Afterwards market returns 404 and the post vanishes from lists, but redeem still works and refunds the position (reported as POST_GONE). The refund is normally near break-even; if the pot was drained before deletion the refund reflects the loss. postDeleted on portfolio positions flags this state.
feed embargo and fresh slots
The feed is not an index of live posts. Measured on two independent datasets (2026-09-16 and 2026-09-23):
- Embargo: no post younger than ~15.1 minutes appears in the feed (hard edge at 900 s after
created_at, then 10–100 s of jitter).
- Fresh slots: newly listed posts occupy two fixed positions per page — around rank 3–7 and rank 37–48 — for roughly 1.5–3.5 minutes, then leave the feed. Nothing between 18 and 60 minutes old was observed.
- The rest of a page is drawn from a pool of ~130–200 posts ordered mainly by pot size (rank vs pot Spearman −0.78). Roughly 200 distinct posts are ever surfaced.
- Every request is a fresh random draw from that pool, regardless of
cursor; nextCursor has always been 52. Paging does not enumerate the pool.
- Trading is open before listing. A post is visible from insertion on its creator's posts list, and 40 % of posts receive a buy within 60 s of creation, before the feed shows them.
- At the 15-minute mark every fresh post shows 0 likes, views, comments and shares; social counts cannot distinguish fresh posts.
the realtime tape
The app receives a websocket stream of trades from a Centrifugo server. The endpoint URL, channel names and subscription handshake are unverified here. What the stream carries, as consumed by a stdlib websocket client:
| field | type | meaning |
potAfter | number | Pot after the trade. The only authoritative pot source outside the market endpoint. |
levelAfter | number | Level after the trade; floor = levelAfter − potAfter. |
points, shares, side, postId, userId, username | — | Trade identity. Sells carry no username. |
- Trades under ~10 aura are not published. A pot can move by several aura with no tape event.
- The tape carries trades only. A new or empty post never appears on it; there is no post-created event.
- The same trade can appear twice (once with
pot 0→0, once populated). De-duplicate on (post, user, side, points, timestamp).
- Replaying market/trades never reproduces the pot: it is capped at 100 rows and omits sub-10 trades.
4. endpoints
One entry per endpoint. "Auth: user" means a bearer access token of any account; "Auth: owner" means the token's own account is the subject. Response tables list the fields observed; unlisted fields may exist. Examples use placeholders.
feed
GET /api/v3/feed verified
One page of the ranked recommendation feed: ~50 posts with flat market fields and engagement counts.
Request
Auth: user. Standard headers.
Parameters
| name | in | type | description |
limit | query | integer | Page size. Clients use 50; larger values are unverified. |
cursor | query | integer | Value of a previous nextCursor. Does not enumerate a stable list (see feed embargo). |
sort | query | string | top is sent by the gigglantir wrapper for its top-videos ranking. Effect on ordering is inferred. |
Response
| field | type | description |
success | boolean | |
posts | array | Post objects, flat (below). |
nextCursor | integer | Observed constant 52. |
hasMore | boolean | |
degraded | boolean | Set when the ranker fell back. Semantics unverified. |
Post object (also returned by users/{id}/posts):
| field | type | description |
id | uuid | Post id. |
user_id | uuid | Creator. |
username | string | Creator's handle. |
user | object | Creator summary; includes creatorverified. |
post_description | string | Caption. |
created_at | ISO 8601 | Creation time. Always ≥ ~15 min before the response (embargo). |
hls | url | HLS manifest (master.m3u8). |
media, carousel_media, video_thumbnail | url / array / url | Media locations. |
is_photo | boolean | |
hashtag | string | |
source_platform | string | Origin for imported media. inferred |
market_pot | number | Pot at ranking time. |
market_volume | number | Cumulative traded aura. |
market_holders | integer | |
market_trades | integer | |
share_count, comment_count, bookmark_count | integer | Engagement counts. |
overallengagementscore | number | Ranker score. |
like_count, views, completionrate | — | Present but not populated (always 0/absent) in feed responses. |
friend_investors_count | integer | inferred |
Errors
401 expired or missing token.429 budget exhausted.
Notes
- Market numbers in a feed row are as of ranking; they were measured to be fresh (no post ever showed a pot older than the last trade in 8,386 checks).
- Roughly 200 distinct posts are surfaced in total; deeper paging repeats.
- Reading three pages every few seconds gives near-certain sight of every fresh slot; polling faster buys nothing.
Example
curl -s "https://api.usegiggles.com/api/v3/feed?limit=50" \
-H "Authorization: Bearer <access-token>" \
-H "User-Agent: okhttp/4.12.0"
posts
GET /api/v3/posts/<post-uuid>/market verified
Current market state of one post. The reference read before any buy.
Request
Auth: user. No body.
Parameters
| name | in | type | description |
id | path | uuid | Post id. |
Response
| field | type | description |
pot | number, aura | Redeemable pool. |
floor | number, aura | Seed; 1000 on native posts. |
level | number, aura | floor + pot. |
maxInvest | number, aura | Largest buy the caller may place now (position cap). Caller-specific. |
holders | integer | Accounts with a position. |
trades | integer | Total trade count, including trades the tape and /market/trades omit. |
volume | number, aura | Cumulative traded aura. |
status | string | Lifecycle state. Values unverified. |
change24h | number | 24-hour level change. Unit (aura vs percent) unverified. |
creator_fees | number, aura | Fees accrued to the creator. Also seen as creatorFeesEarned. |
listed_at | ISO 8601 | Listing time. Also seen as listedAt. |
isImport | boolean | Imported media. inferred |
sell_locked_until | ISO 8601 / null | See sell locks. Also seen as sellLockedUntil. |
sell_timer_s | integer / null | Also seen as sellTimerSeconds. |
Errors
404 post deleted or unknown. A UUID that is a user, not a post, also 404s.401, 429.
Notes
pot < 1 with holders == 0 is the practical definition of an empty post. Posts a previous holder fully exited show pot ≈ 1.5, holders 0, trades ≥ 2.
maxInvest depends on the caller; the same post reports different values to different accounts.
Example
curl -s "https://api.usegiggles.com/api/v3/posts/<post-uuid>/market" \
-H "Authorization: Bearer <access-token>" -H "User-Agent: okhttp/4.12.0"
# {"pot":0,"level":1000,"floor":1000,"maxInvest":1000,"holders":0,"trades":0,"volume":0,...}
GET /api/v3/posts/<post-uuid>/market/trades verified
The most recent trades on a post, newest first. Incomplete by design.
Request
Auth: user.
Parameters
| name | in | type | description |
id | path | uuid | Post id. |
limit | query | integer | Rows requested. Values above 100 clamp to 100. |
offset, page, before, cursor | query | — | Accepted and ignored. There is no paging. |
Response
| field | type | description |
postId | uuid | |
trades | array | Up to 100 rows. |
trades[].type | string | buy or sell. |
trades[].points | number, aura | Aura in (buy) or out (sell). |
trades[].shares | number | |
trades[].userId | uuid | |
trades[].username | string | May contain zero-width characters; strip U+200B before comparing. |
trades[].photoUrl | url | |
trades[].at | ISO 8601 or epoch | Both string and numeric (seconds or milliseconds) forms have been handled by clients; parse defensively. |
Errors
404 deleted post.401, 429.
Notes
- Hard cap of 100 rows. A post with
market.trades = 2883 returns 100 rows and no way to page.
- Trades under ~10 aura are omitted (16 rows returned against
trades = 156 on one post). Use market.trades for the true count.
- Never derive the pot from this list; it cannot be reconstructed. Use market or the tape.
- The list lags the pot by seconds under load.
Example
curl -s "https://api.usegiggles.com/api/v3/posts/<post-uuid>/market/trades?limit=100" \
-H "Authorization: Bearer <access-token>" -H "User-Agent: okhttp/4.12.0"
POST /api/v3/posts/<post-uuid>/invest verified
Buy into a post. Adds points to the pot and mints shares to the caller.
Request
Auth: owner. Body: JSON.
Parameters
| name | in | type | description |
id | path | uuid | Post id. |
points | body | number, aura | Amount to invest. Must be ≤ the caller's maxInvest and ≤ aura_spendable. |
Response
| field | type | description |
pot | number, aura | Pot after the buy. Equals points exactly when the pot was empty before. |
shares | number | Shares minted to the caller. |
| others | — | Further fields exist; clients read only the two above. inferred |
Errors
| status / code | body | meaning |
400 POSITION_CAP | {"code":"POSITION_CAP","maxPoints":N} | points exceeds the cap. maxPoints is the amount that would be accepted; 0 means none. |
400 | — | points above maxInvest or above spendable balance. |
404 | — | Deleted post. |
401, 429 | — | Token expired; budget exhausted (the buy did not happen). |
Notes
- A rejected buy is rejected whole. To buy the maximum, re-issue with
points = maxPoints.
- Round trip is ~0.8 s mean; 1–14 % of calls exceed 2 s depending on platform load.
- Buys are accepted before a post is listed in the feed.
Example
curl -s -X POST "https://api.usegiggles.com/api/v3/posts/<post-uuid>/invest" \
-H "Authorization: Bearer <access-token>" -H "User-Agent: okhttp/4.12.0" \
-H "Content-Type: application/json" -d '{"points": 100}'
POST /api/v3/posts/<post-uuid>/redeem verified
Sell the caller's entire position in a post.
Request
Auth: owner. Body: JSON.
Parameters
| name | in | type | description |
id | path | uuid | Post id. |
amount | body | number | Ignored. Clients send 1. Any value redeems 100 % of shares. |
Response
| field | type | description |
postId | uuid | |
redeemed | number, aura | Aura paid out, net of fees. |
shares | number | Shares burned. |
realizedPnl | number, aura | redeemed − invested. |
creatorFee | number, aura | Fee paid to the creator (≈ 0.1 %). |
pot | number, aura | Pot after the redeem. |
level | number, aura | Level after the redeem. |
counter | — | Meaning unverified. |
spendable | number, aura | Caller's liquid balance after the redeem. |
Errors
POST_GONE: the post was deleted; the position is refunded anyway (see deleted posts). Exact status and body shape inferred.
401, 429. A sell-lock refusal (see locks) has an unverified shape.
- Redeeming with no position: unverified.
Notes
- All-or-nothing. Two accounts redeeming the same post in sequence each receive their fraction of the level at the moment of their call; order does not change the group total.
- Round trip ≈ 0.36 s in a burst of sequential redeems.
Example
curl -s -X POST "https://api.usegiggles.com/api/v3/posts/<post-uuid>/redeem" \
-H "Authorization: Bearer <access-token>" -H "User-Agent: okhttp/4.12.0" \
-H "Content-Type: application/json" -d '{"amount": 1}'
users
GET /api/v3/users/search verified
Username search. Returns up to 20 users.
Request
Auth: user.
Parameters
| name | in | type | description |
q | query | string | Name prefix or fragment. URL-encode. |
Response
| field | type | description |
success | boolean | |
count | integer | ≤ 20. |
users[].id | uuid | |
users[].username | string | Do an exact, case-insensitive match client-side; results are fuzzy. |
users[].display_name, users[].bio, users[].profile_photo, users[].created_at | string | |
users[].creator_verified, users[].creator_verified_gold, users[].has_wallet | boolean | inferred from a tolerant reader. |
Errors
Notes
- The only way to resolve a username to an id without a local table; the leaderboard covers only ranked users.
Example
curl -s "https://api.usegiggles.com/api/v3/users/search?q=someone" \
-H "Authorization: Bearer <access-token>" -H "User-Agent: okhttp/4.12.0"
GET /api/v3/users/<user-uuid>/profile verified
Public profile of any user, including balances.
Request
Auth: user. Works for any id, including the caller's own.
Parameters
| name | in | type | description |
id | path | uuid | User id (sub). |
Response
| field | type | description |
user | object | Wrapper object; all fields below are inside it. |
user.username | string | |
user.display_name, user.bio, user.college, user.profile_photo | string | |
user.social_links | array | |
user.aura | number, aura | Total net worth: spendable plus the value of open positions. Matches the leaderboard's networth. Stable under heavy trading. |
user.aura_spendable | number, aura | Liquid balance. Holdings = aura − aura_spendable. |
user.creatorverified, user.creatorverifiedgold | boolean | Verification badges. |
user.followers_count, user.leaderboard_rank | integer | inferred (read by the gigglantir connect page). |
user.follower_count, user.following_count, user.activity_public | integer / boolean | inferred (read by the wrapper). |
user.graduation_year, user.profile_color | — | Passed through by the profile updater. inferred |
Errors
404 unknown user (a post UUID also 404s here).401, 429.
Notes
- Cheapest balance read: one call, two numbers.
Example
curl -s "https://api.usegiggles.com/api/v3/users/<user-uuid>/profile" \
-H "Authorization: Bearer <access-token>" -H "User-Agent: okhttp/4.12.0"
GET /api/v3/users/<user-uuid>/stats verified
Trading statistics for any user.
Request
Auth: user.
Parameters
| name | in | type | description |
id | path | uuid | User id. |
Response
| field | type | description |
netWorth | number, aura | |
rank | integer | Leaderboard rank. |
totalVolume | number, aura | |
tradeCount | integer | |
creatorVerified | boolean | |
networth, realizedPnl, winRate | number | inferred from the wrapper's reader; may be absent. |
Errors
Notes
Example
curl -s "https://api.usegiggles.com/api/v3/users/<user-uuid>/stats" \
-H "Authorization: Bearer <access-token>" -H "User-Agent: okhttp/4.12.0"
GET /api/v3/users/<user-uuid>/posts verified
A creator's live posts with flat market fields, newest first.
Request
Auth: user.
Parameters
| name | in | type | description |
id | path | uuid | Creator's user id. |
Response
| field | type | description |
posts | array | Flat post objects: id, created_at, hashtag, is_photo, media, carousel_media, post_description, market_pot, market_volume, market_holders, market_trades. Same shape as feed rows; market (nested) is absent. |
Errors
Notes
- Live posts only. Deleted posts disappear.
- A post appears here from the moment of insertion — roughly 15 minutes before the feed shows it. This is the earliest public surface for a new post, and the only one before listing.
- No paging observed; the count returned for prolific creators is unverified.
Example
curl -s "https://api.usegiggles.com/api/v3/users/<user-uuid>/posts" \
-H "Authorization: Bearer <access-token>" -H "User-Agent: okhttp/4.12.0"
GET /api/v3/users/<user-uuid>/transactions verified
A user's trade ledger, newest first. Readable for any user.
Request
Auth: user.
Parameters
| name | in | type | description |
id | path | uuid | User id. |
limit | query | integer | Clients use 100. |
cursor | query | string | Paging cursor. inferred from the owner form below. |
Response
| field | type | description |
transactions | array | Ledger rows. (A bare array and a data key have also been tolerated by readers.) |
transactions[].id | string | Stable row id; use for de-duplication. |
transactions[].type | string | buy / sell (casing varies; compare case-insensitively). |
transactions[].postId | uuid | Also seen as post_id. |
transactions[].points | number, aura | |
transactions[].realizedPnl | number, aura | On sells. |
transactions[].createdAt | ISO 8601 | Also seen as created_at. |
Errors
Notes
- Any account's trading is public through this route; a user's own history is not private on this platform.
Example
curl -s "https://api.usegiggles.com/api/v3/users/<user-uuid>/transactions?limit=100" \
-H "Authorization: Bearer <access-token>" -H "User-Agent: okhttp/4.12.0"
GET /api/v3/transactions verified
The caller's own full trade ledger. Same rows as the user-scoped route.
Request
Auth: owner.
Parameters
| name | in | type | description |
limit | query | integer | Clients use 100. |
cursor | query | string | Empty for the first page. |
Response
As users/{id}/transactions.
Errors
Notes
- The cheapest source of an account's own history.
Example
curl -s "https://api.usegiggles.com/api/v3/transactions?limit=100&cursor=" \
-H "Authorization: Bearer <access-token>" -H "User-Agent: okhttp/4.12.0"
GET /api/v3/users/<user-uuid>/portfolio verified
Open and closed positions of any user, 50 per page.
Request
Auth: user. Public for any id.
Parameters
| name | in | type | description |
id | path | uuid | User id. |
offset | query | integer | From a previous nextOffset. Omit for page one. |
Response
| field | type | description |
positions | array | Open positions. |
positions[].postId | uuid | |
positions[].invested | number, aura | Cost basis. |
positions[].value | number, aura | Mark: frac × level. |
positions[].shares | number | |
positions[].unrealizedPnl, positions[].unrealizedPnlPct, positions[].realizedPnl, positions[].totalPnl | number | |
positions[].creatorId | uuid | Also recoverable from the thumbnail path /posts/thumbnails/<creator>/…. |
positions[].postDeleted | boolean | Post deleted; redeem still refunds. |
positions[].isImport | boolean | |
positions[].description, positions[].thumbnail | string | |
closedPositions | array | Rows: openedAt, closedAt, invested, returned, realizedPnl, trades. |
hasMore | boolean | |
nextOffset | integer | |
current | object | netWorth, holdings, spendable. netWorth intermittently returns holdings only; holdings + spendable is reliable, and profile.aura is steadier still during rapid trading. |
pnl24h | object | pnl, percentChange, rank. |
totalPnl, totalPnlPercent | number | Lifetime. |
Errors
- A non-200 for a real user indicates a private profile (inferred; status unverified).
401, 429.
Notes
- Page until
hasMore is false or nextOffset repeats; clients cap at 6–30 pages.
value lags a fresh buy by a moment; spendable drops instantly. Net-worth computed as holdings + spendable can dip transiently.
Example
curl -s "https://api.usegiggles.com/api/v3/users/<user-uuid>/portfolio?offset=50" \
-H "Authorization: Bearer <access-token>" -H "User-Agent: okhttp/4.12.0"
GET /api/v3/portfolio verified
The caller's own portfolio. Same shape and paging as the user-scoped route.
Request
Auth: owner.
Parameters
| name | in | type | description |
offset | query | integer | From nextOffset. |
Response
As users/{id}/portfolio.
Errors
Notes
- Exercised continuously by client code; not listed in the operator's catalog. Prefer this route for one's own account.
Example
curl -s "https://api.usegiggles.com/api/v3/portfolio" \
-H "Authorization: Bearer <access-token>" -H "User-Agent: okhttp/4.12.0"
GET /api/v3/users/<user-uuid>/cards verified
Any user's card collection.
Request
Auth: user.
Parameters
| name | in | type | description |
id | path | uuid | User id. |
Response
Card objects; field names as in trade-with theirs.cards (inferred).
Errors
Notes
- Read-only view; trade composition uses trade-with.
Example
curl -s "https://api.usegiggles.com/api/v3/users/<user-uuid>/cards" \
-H "Authorization: Bearer <access-token>" -H "User-Agent: okhttp/4.12.0"
PUT /api/user/profile verified
Update the caller's profile. Full replacement: send every field.
Request
Auth: owner. Body: JSON. Not under /api/v3.
Parameters
| name | in | type | description |
username, display_name, bio, college | body | string | Current values, re-sent. |
social_links | body | array | Current value; [] if none. |
profile_photo | body | url | Public URL from presign. GIF allowed. |
email or phone_number | body | string | Required; taken from the JWT's email / phone claim. |
graduation_year, profile_color | body | — | Optional pass-through. |
Response
200 with success: true or a body without error. inferred
Errors
POST /api/user/profile/create is onboarding-only and returns 409 for an existing profile. Do not use it for updates.
401, 429.
Notes
- Fetch profile first and pass every existing field through, or fields are wiped.
Example
curl -s -X PUT "https://api.usegiggles.com/api/user/profile" \
-H "Authorization: Bearer <access-token>" -H "User-Agent: okhttp/4.12.0" \
-H "Content-Type: application/json" \
-d '{"username":"…","bio":"…","display_name":"…","social_links":[],"college":null,
"profile_photo":"https://…/<key>","email":"[email protected]"}'
leaderboard
GET /api/v3/leaderboard verified
Net-worth ranking, 50 per page, with the caller's own row.
Request
Auth: user.
Parameters
| name | in | type | description |
period | query | string | 24h is verified. 7d, 30d, all are forwarded by the wrapper; acceptance upstream is inferred. Default is the all-time ranking. |
cursor | query | string | From nextCursor. Walks stop at about page 120 (6,000 rows). |
Response
| field | type | description |
success | boolean | |
period | string | |
leaderboard | array | 50 rows. |
leaderboard[].userId | uuid | |
leaderboard[].username, leaderboard[].displayName, leaderboard[].photoUrl | string | |
leaderboard[].verified, leaderboard[].verifiedGold | boolean | |
leaderboard[].networth | number, aura | Total wealth including open positions (equals profile.aura). |
leaderboard[].rank, leaderboard[].delta, leaderboard[].percentChange | number | Present on period rankings. inferred |
me | object | The caller's row (userId, username, …). |
hasMore, nextCursor | boolean / string | |
Errors
401, 429 (a full walk on one token has hit 429).
Notes
- Networth decays to 0 by roughly rank 3,000–6,000; the top few thousand rows are effectively the whole money supply.
- Non-trade changes in
networth are usually mark-to-market of open positions, not transfers.
Example
curl -s "https://api.usegiggles.com/api/v3/leaderboard?period=24h" \
-H "Authorization: Bearer <access-token>" -H "User-Agent: okhttp/4.12.0"
chat and direct messages
GET /api/v3/chat verified
The global chat room.
Request
Auth: user.
Parameters
| name | in | type | description |
cursor | query | string | From nextCursor. inferred |
Response
| field | type | description |
success | boolean | |
chatterCount | integer | ≈ 13k observed. |
messages | array | |
messages[].id, messages[].kind | string | kind: user observed. |
messages[].authorUserId, messages[].username, messages[].avatarUrl, messages[].authorGold, messages[].clan | — | Author. |
messages[].text, messages[].gifUrl, messages[].replyTo | — | Content. |
messages[].reactions, messages[].reactionCount, messages[].meta, messages[].gameId, messages[].createdAt | — | |
hasMore, nextCursor | — | |
Errors
Notes
/chat/global does not exist; this route is the global room.
Example
curl -s "https://api.usegiggles.com/api/v3/chat" \
-H "Authorization: Bearer <access-token>" -H "User-Agent: okhttp/4.12.0"
POST /api/v3/chat verified
Post a message to the global chat.
Request
Auth: owner. Body: JSON.
Parameters
| name | in | type | description |
text | body | string | Message text. |
Response
{"success": true, "message": {…}} with the created message object.
Errors
Notes
- Not gated on creator verification; an unverified account posted successfully.
Example
curl -s -X POST "https://api.usegiggles.com/api/v3/chat" \
-H "Authorization: Bearer <access-token>" -H "User-Agent: okhttp/4.12.0" \
-H "Content-Type: application/json" -d '{"text": "hello"}'
Direct messages do not go through api.usegiggles.com. They are rows in the Supabase database, read and written over PostgREST at {SB}/rest/v1/ with apikey + the user's bearer token. Row-level security scopes every query to the caller: a user sees only chats they participate in and only their own chat_participants row. PostgREST filter syntax applies (eq., neq., in.(…), select=, order=, limit=).
GET {SB}/rest/v1/chat_participants verified
The caller's conversation list: one row per chat the caller belongs to.
Request
Auth: user JWT + apikey.
Parameters
| name | in | type | description |
user_id | query filter | uuid | eq.<user-uuid> — the caller's own id (RLS returns nothing else). |
is_hidden | query filter | boolean | eq.false to exclude hidden chats. |
select | query | string | Column list, e.g. chat_id,unread_count,last_read_at. |
Response
| column | type | description |
chat_id | uuid | |
user_id | uuid | |
joined_at, last_read_at | timestamp | |
unread_count | integer | |
is_muted, is_pinned, is_hidden | boolean | |
Errors
401 bad or expired JWT; an empty array when RLS excludes everything.
Notes
- The other party of a chat is not on this row. Resolve it from chat_messages (a sender ≠ self) or from
chats.last_message_by. For an empty chat the other party is unknowable.
Example
curl -s "{SB}/rest/v1/chat_participants?user_id=eq.<user-uuid>&is_hidden=eq.false&select=chat_id,unread_count,last_read_at&limit=200" \
-H "apikey: <publishable-key>" -H "Authorization: Bearer <access-token>"
GET {SB}/rest/v1/chats verified
Chat headers: last message preview and timestamps.
Request
Auth: user JWT + apikey.
Parameters
| name | in | type | description |
id | query filter | uuid list | in.(a,b,c); clients batch 40 ids per call. |
select | query | string | e.g. id,last_message,last_message_time,last_message_by. |
Response
| column | type | description |
id | uuid | |
created_at, last_message_time | timestamp | |
last_message | string | Preview text. |
last_message_by | uuid | |
last_message_id | uuid | |
Errors
Notes
- A sending client updates this row itself after inserting a message (
PATCH chats?id=eq.<chat-uuid> with the four last_message* columns, Prefer: return=minimal); the server does not maintain it.
Example
curl -s "{SB}/rest/v1/chats?id=in.(<chat-uuid>,<chat-uuid>)&select=id,last_message,last_message_time,last_message_by" \
-H "apikey: <publishable-key>" -H "Authorization: Bearer <access-token>"
GET {SB}/rest/v1/chat_messages verified
Messages in a chat.
Request
Auth: user JWT + apikey.
Parameters
| name | in | type | description |
chat_id | query filter | uuid | eq.<chat-uuid> or in.(…). |
user_id | query filter | uuid | neq.<user-uuid> to find the other party. |
order | query | string | created_at.desc. |
limit | query | integer | |
Response
| column | type | description |
id, chat_id, user_id | uuid | |
text | string | A clan invite is the text clan_invite:<id> plus a row in clan_invites. |
message_type | enum | text, media, post, gift, market_event, system. |
media, post_id, reply_to_message_id, reactions, meta, read_by | — | |
client_id | uuid | Client-generated idempotency id. |
created_at | timestamp | |
Errors
Notes
- Mark as read by patching the caller's participant row (below).
Example
curl -s "{SB}/rest/v1/chat_messages?chat_id=eq.<chat-uuid>&select=id,user_id,text,message_type,media,post_id,meta,created_at,reply_to_message_id&order=created_at.desc&limit=100" \
-H "apikey: <publishable-key>" -H "Authorization: Bearer <access-token>"
POST {SB}/rest/v1/chat_messages verified
Send a direct message.
Request
Auth: user JWT + apikey. Headers: Content-Type: application/json, Prefer: return=representation to receive the inserted row.
Parameters
| name | in | type | description |
chat_id | body | uuid | Existing chat the caller belongs to. |
user_id | body | uuid | The caller's own id. |
text | body | string | |
message_type | body | string | text. |
client_id | body | uuid | Fresh UUID v4. |
meta | body | object | Optional; e.g. {"trade_offer_id": …} to attach a card offer. |
Response
201 with a one-element array containing the row (under return=representation).
Errors
401; 403/4xx from RLS when the caller is not a participant.
Notes
- Creating a new chat (rows in
chats and two chat_participants) is unverified; only sending into existing chats has been exercised.
- After the insert, patch
chats so the conversation list preview updates (see chats).
Example
curl -s -X POST "{SB}/rest/v1/chat_messages" \
-H "apikey: <publishable-key>" -H "Authorization: Bearer <access-token>" \
-H "Content-Type: application/json" -H "Prefer: return=representation" \
-d '{"chat_id":"<chat-uuid>","user_id":"<user-uuid>","text":"hi","message_type":"text","client_id":"<uuid4>"}'
PATCH {SB}/rest/v1/chat_participants verified
Mark a chat read, or hide it. Only the caller's own row is writable.
Request
Auth: user JWT + apikey. Prefer: return=minimal.
Parameters
| name | in | type | description |
chat_id, user_id | query filter | uuid | eq. both, or chat_id=in.(…) for bulk. |
last_read_at | body | timestamp | Mark read: now, with unread_count: 0. |
is_hidden | body | boolean | true hides the chat from the caller's list. There is no delete. |
Response
204 (or 200) with no body.
Errors
401. A filter matching another user's row updates nothing and returns success.
Notes
- Hiding is per participant; the other party's view is unchanged.
Example
curl -s -X PATCH "{SB}/rest/v1/chat_participants?chat_id=eq.<chat-uuid>&user_id=eq.<user-uuid>" \
-H "apikey: <publishable-key>" -H "Authorization: Bearer <access-token>" \
-H "Content-Type: application/json" -H "Prefer: return=minimal" \
-d '{"is_hidden": true}'
cards, offers and gifts
Cards are a parallel collectible economy that shares the aura wallet. Trades are offers between two users containing cards and/or aura on each side. Because an offer must contain at least one card, an aura gift is expressed as an offer of one card plus N aura against 1 aura.
GET /api/v3/cards/trade-with/<user-uuid> verified
Everything needed to compose an offer to one user: both inventories, spendable aura, limits.
Request
Auth: owner.
Parameters
| name | in | type | description |
id | path | uuid | Counterparty user id. |
Response
| field | type | description |
other | object | Counterparty summary. |
mine.cards, mine.cosmetics | array | Caller's tradeable items. |
mine.spendableAura | number, aura | |
theirs.cards | array | Counterparty's cards. |
cards[].cardId | string | Catalog id. |
cards[].userCardId | uuid | The instance id used in offers. |
cards[].name, cards[].tier, cards[].editionNo, cards[].imageUrl, cards[].animationUrl | — | |
cards[].instaSellAura | number, aura | Instant-sell value. |
cards[].tradeable, cards[].lockedReason | boolean / string | Reliable here (not in inventory). |
limits.maxItemsPerSide | integer | 6. |
limits.openOutgoingMax | integer | 1000. |
limits.offersToThisUserMaxPerDay | integer | 25. |
limits.auraFeePct | number | 1. |
Errors
404 unknown user; 401, 429.
Notes
- The authoritative composer; use it instead of
/cards/inventory before creating an offer.
Example
curl -s "https://api.usegiggles.com/api/v3/cards/trade-with/<user-uuid>" \
-H "Authorization: Bearer <access-token>" -H "User-Agent: okhttp/4.12.0"
POST /api/v3/cards/trades verified
Create a trade offer.
Request
Auth: owner. Body: JSON.
Parameters
| name | in | type | description |
receiverId | body | uuid | Counterparty. |
offeredItems | body | array | [{"itemType":"card","id":"<userCardId>"}]. itemType is lowercase card (also sticker); id holds the userCardId. |
requestedItems | body | array | Same shape, from theirs.cards. |
offeredAura | body | number | |
requestedAura | body | number | |
Response
| field | type | description |
ok | boolean | |
offerId | uuid | |
expiresAt | timestamp | ≈ 7 days. |
Errors
| status / code | body | meaning |
400 empty_side | — | A side has no items and 0 aura. |
400 no_card_in_trade | — | Neither side contains a card. Pure-aura transfers are refused. |
400 Invalid items | — | Wrong item key or id. Checked after the two above. |
401, 429 | — | |
Notes
- Validation order is
empty_side → no_card_in_trade → Invalid items.
- Aura gift: offer
[one card] + N aura, request 1 aura. The recipient nets ≈ N minus the 1 % fee plus a card.
- Gift counts and cooldowns are not exposed.
Example
curl -s -X POST "https://api.usegiggles.com/api/v3/cards/trades" \
-H "Authorization: Bearer <access-token>" -H "User-Agent: okhttp/4.12.0" \
-H "Content-Type: application/json" \
-d '{"receiverId":"<user-uuid>","offeredItems":[{"itemType":"card","id":"<userCardId>"}],
"requestedItems":[],"offeredAura":100,"requestedAura":1}'
POST /api/v3/cards/trades/<offer-uuid>/{accept|decline|cancel} verified
Resolve an offer. accept and decline by the receiver; cancel by the sender.
Request
Auth: owner. No body.
Parameters
| name | in | type | description |
offerId | path | uuid | From offerId on creation or from the list. |
action | path | enum | accept, decline, cancel. |
Response
{"ok": true, …}. inferred
Errors
4xx when the caller is the wrong party or the offer expired. Shapes unverified.
Notes
- Accepting settles both sides atomically, including aura, less the 1 % fee.
Example
curl -s -X POST "https://api.usegiggles.com/api/v3/cards/trades/<offer-uuid>/accept" \
-H "Authorization: Bearer <access-token>" -H "User-Agent: okhttp/4.12.0"
GET /api/v3/cards/trades verified
The caller's offers by status.
Request
Auth: owner.
Parameters
| name | in | type | description |
status | query | enum | pending, accepted, incoming. |
Response
{"offers": [ … ]}; offer fields inferred.
Errors
Notes
incoming lists offers made to the caller.
Example
curl -s "https://api.usegiggles.com/api/v3/cards/trades?status=incoming" \
-H "Authorization: Bearer <access-token>" -H "User-Agent: okhttp/4.12.0"
GET /api/v3/cards/inventory inferred
The caller's own cards.
Request
Auth: owner.
Parameters
None.
Response
Card objects. The tradeable flag here is unreliable; use trade-with.
Errors
Notes
- Listed in the operator's catalog without a live mark.
Example
curl -s "https://api.usegiggles.com/api/v3/cards/inventory" \
-H "Authorization: Bearer <access-token>" -H "User-Agent: okhttp/4.12.0"
GET /api/v3/cards/catalog inferred
The card catalog: 57 cards across 6 tiers (including "contraband").
Request
Auth: user.
Parameters
None.
Response
Catalog entries; field names unverified.
Errors
Notes
Example
curl -s "https://api.usegiggles.com/api/v3/cards/catalog" \
-H "Authorization: Bearer <access-token>" -H "User-Agent: okhttp/4.12.0"
GET /api/v3/cards/boxes inferred
Loot-box state for the caller.
Request
Auth: owner.
Parameters
None.
Response
| field | type | description |
spendable | number, aura | |
keyBalance | integer | |
pendingBoxes | array | |
Errors
Notes
- Box prices: 1★ = 50 aura … 5★ = 2500 aura. The purchase/open route is unverified.
Example
curl -s "https://api.usegiggles.com/api/v3/cards/boxes" \
-H "Authorization: Bearer <access-token>" -H "User-Agent: okhttp/4.12.0"
GET /api/v3/cards/market/auctions · /api/v3/cards/market/lots inferred
The card marketplace: auctions and fixed-price lots.
Request
Auth: user.
Parameters
unverified.
Response
unverified.
Errors
Notes
- Listed by the operator's catalog; never exercised by client code.
Example
curl -s "https://api.usegiggles.com/api/v3/cards/market/lots" \
-H "Authorization: Bearer <access-token>" -H "User-Agent: okhttp/4.12.0"
GET /api/v3/rewards/daily inferred
Daily reward state. Resets at 16:00 UTC.
Request
Auth: owner.
Parameters
None.
Response
unverified.
Errors
Notes
- Whether a claim is a separate POST is unverified.
Example
curl -s "https://api.usegiggles.com/api/v3/rewards/daily" \
-H "Authorization: Bearer <access-token>" -H "User-Agent: okhttp/4.12.0"
profile photo and media uploads
Uploads are three steps: presign, PUT the bytes to object storage, then reference the public URL from the profile (or a post). The flow was recovered from the client bundle and is exercised end-to-end by the gigglantir profile-photo tool.
POST /api/uploads/presign verified
Obtain a presigned PUT URL for an image.
Request
Auth: owner. Body: JSON. Not under /api/v3.
Parameters
| name | in | type | description |
kind | body | enum | profile-image or post-media. |
contentType | body | string | image/jpeg, image/png, image/gif, image/webp. |
contentLength | body | integer | Exact byte length of the upload. |
Response
| field | type | description |
uploadUrl | url | Presigned PUT (Cloudflare R2). |
key | string | Object key. |
publicUrl | url | The URL to store on the profile. |
expiresIn | integer, seconds | 300. |
requiredHeaders | object | Headers the PUT must carry (at least Content-Type). |
Errors
4xx for an unsupported type or size; shape unverified. 401.
Notes
- The gigglantir tool caps uploads at 8 MB client-side; the platform's limit is unverified.
Example
curl -s -X POST "https://api.usegiggles.com/api/uploads/presign" \
-H "Authorization: Bearer <access-token>" -H "User-Agent: okhttp/4.12.0" \
-H "Content-Type: application/json" \
-d '{"kind":"profile-image","contentType":"image/png","contentLength":12345}'
PUT {uploadUrl} verified
Upload the raw bytes to the presigned URL.
Request
No bearer token: the URL is self-authorising. Headers: exactly requiredHeaders from presign. Body: the file bytes.
Parameters
None.
Response
200, 201 or 204, empty body.
Errors
403 after expiresIn or on a header/length mismatch.
Notes
Example
curl -s -X PUT "<uploadUrl>" -H "Content-Type: image/png" --data-binary @avatar.png
auth (Supabase GoTrue)
POST {SB}/auth/v1/otp verified
Email a one-time code (or magic link) to an address.
Request
Headers: apikey, Authorization: Bearer <publishable-key>, Content-Type: application/json.
Parameters
| name | in | type | description |
email | body | string | |
create_user | body | boolean | false: existing accounts only. true: unverified whether the resulting account is provisioned. |
Response
200, empty object.
Errors
4xx with msg / error_description / error; a message containing captcha means the project demands a captcha token this call did not supply.
Notes
- Rate-limited by GoTrue per address; limits unverified.
Example
curl -s -X POST "{SB}/auth/v1/otp" \
-H "apikey: <publishable-key>" -H "Authorization: Bearer <publishable-key>" \
-H "Content-Type: application/json" -d '{"email":"[email protected]","create_user":false}'
POST {SB}/auth/v1/verify verified
Exchange the emailed code for a session.
Request
Headers: apikey, Content-Type: application/json.
Parameters
| name | in | type | description |
type | body | string | email. |
email | body | string | |
token | body | string | The 6-digit code. |
Response
| field | type | description |
access_token | JWT | |
refresh_token | string | |
expires_in, token_type, user | — | Standard GoTrue session fields. inferred |
Errors
4xx with msg / error_description: invalid or expired code.
Notes
- Codes are single-use and short-lived.
Example
curl -s -X POST "{SB}/auth/v1/verify" \
-H "apikey: <publishable-key>" -H "Content-Type: application/json" \
-d '{"type":"email","email":"[email protected]","token":"123456"}'
GET {SB}/auth/v1/authorize verified
Start an OAuth login in a browser.
Request
Browser navigation; no headers.
Parameters
| name | in | type | description |
provider | query | enum | google, apple. |
redirect_to | query | url | Must be on the project's allow-list. |
Response
302 chain ending at redirect_to#access_token=…&refresh_token=….
Errors
- A non-listed
redirect_to lands on the project's default site URL instead.
Notes
- The fragment is not sent to the redirect host; read it from the address bar or page script.
Example
open "{SB}/auth/v1/authorize?provider=google&redirect_to=http%3A%2F%2Flocalhost%3A3000"
POST {SB}/auth/v1/token?grant_type=refresh_token verified
Rotate a session.
Request
Headers: apikey, Authorization: Bearer <publishable-key>, Content-Type: application/json.
Parameters
| name | in | type | description |
grant_type | query | string | refresh_token. |
refresh_token | body | string | The current refresh token. |
Response
access_token, refresh_token (new), expires_in, user. See refresh tokens.
Errors
4xx with error_description: token already used, revoked, or malformed.
Notes
- Persist the new pair before using it. Keep one writer per account.
Example
curl -s -X POST "{SB}/auth/v1/token?grant_type=refresh_token" \
-H "apikey: <publishable-key>" -H "Authorization: Bearer <publishable-key>" \
-H "Content-Type: application/json" -d '{"refresh_token":"<refresh-token>"}'
wallet and realtime
GET /api/v2/wallet inferred
Wallet capability probe.
Request
Auth: owner.
Parameters
None.
Response
Includes hasWallet: false: aura is not backed by a payment wallet and is not withdrawable.
Errors
Notes
- The only v2 route referenced.
/api/v3/wallet and /api/v3/balance do not exist.
Example
curl -s "https://api.usegiggles.com/api/v2/wallet" \
-H "Authorization: Bearer <access-token>" -H "User-Agent: okhttp/4.12.0"
WS realtime trade tape inferred
Push stream of trades over Centrifugo.
Request
Websocket. Endpoint, token exchange and channel names are unverified in the sources for this page.
Parameters
—
Response
Per-trade events with potAfter and levelAfter; see the realtime tape.
Errors
—
Notes
- ~10-aura publish floor; sells lack
username; duplicates occur.
- No post-creation events. Empty posts are invisible on the stream.
Example
—
6. the gigglantir wrapper (/api/v1)
A read-only, CORS-open JSON wrapper served at https://earlybirdgetstheaura.com/api/v1. It reshapes upstream responses into stable field names and adds a few datasets of its own. GET /api/v1 (or /api/v1/health) returns the index as JSON, no auth.
two families
| family | auth | data source | caching |
| token | Authorization: Bearer <access-token> — the caller's own Giggles JWT (?token= works for a quick test). Validated locally (three segments, unexpired exp), then forwarded unchanged. Never stored or logged. | api.usegiggles.com | Shared across callers by path: market 12 s, search 30 s, portfolio 30 s, profile/stats/posts 45 s, leaderboard 45 s, other 20 s. N callers asking the same public question cost one upstream call. |
| public | none | The wrapper's own database: the aurachain ledger, live-pool round totals, post trajectories, market-pulse samples | 10 s |
Rules: GET only; allow-listed paths; ids must be UUIDs (else 400); no path can move aura or read a specific private account; the token family spends the caller's rate budget.
envelope and errors
Every response is an object with ok. Failures: {"ok": false, "error": "<message>"}, plus upstreamStatus when an upstream call failed.
| status | meaning |
400 | Bad parameter (unknown period/by, q too short, non-UUID id, non-integer limit). |
401 | Missing, malformed or expired bearer token (message says which). |
403 | Upstream rejected the token (passed through). |
404 | No such endpoint; see GET /api/v1. |
429 | Upstream rate limit (passed through; it is the caller's budget). |
502 | Upstream unreachable or returned an unexpected status. |
503 | /public/pulse on a host with no sampler data. |
token endpoints
| endpoint | params | returns |
GET /api/v1/leaderboard | period = 24h (default) | 7d | 30d | all | {ok, period, count, leaderboard[{rank, userId, username, displayName, networth, delta, percentChange, verified}]} |
GET /api/v1/top-videos | by = volume (default) | pot | holders | trades | shares | comments | bookmarks | engagement; limit 1–50 (default 20) | {ok, rankedBy, count, videos[{rank, …post}]}. Ranks one page of the top feed (≤ 50 posts), not history. Aliases /top_videos, /top. |
GET /api/v1/search | q ≥ 2 chars | {ok, query, count, members[{id, username, displayName, bio, photo, verified, hasWallet}]}. Aliases /members, /member-search. |
GET /api/v1/feed | limit 1–50; cursor from nextCursor | {ok, count, nextCursor, posts[…post]} — one feed page in the app's own order. |
GET /api/v1/posts/<uuid>/market | — | {ok, post, market{pot, floor, level, maxInvest, holders, trades, volume, status, change24h, creatorFees, listedAt, isImport, sellLockedUntil, sellTimerSeconds}} |
GET /api/v1/posts/<uuid>/trades | limit 1–100 | {ok, post, count, note, trades[{at, type, userId, username, points, shares}]}. Upstream cap of 100 and the sub-10 omission apply. |
GET /api/v1/users/<uuid>/profile | — | {ok, user{id, username, displayName, bio, photo, aura, auraSpendable, verified, followers, following, activityPublic}} |
GET /api/v1/users/<uuid>/stats | — | {ok, user, stats{rank, tradeCount, networth, realizedPnl, winRate}} |
GET /api/v1/users/<uuid>/posts | — | {ok, user, count, posts[…post]} — the creator's live posts, newest first; the earliest public surface for a new post. |
The shared post shape: {id, userId, username, description, media, thumbnail, createdAt, market{pot, volume, holders, trades}, engagement{shares, comments, bookmarks, score}}.
public endpoints
| endpoint | params | returns |
GET /api/v1/public/chain | limit 1–200; before, after (seq); user (pseudonym) | {ok, rows[…]} — the aurachain: a hash-chained, append-only ledger of closed trades, fully pseudonymised. |
GET /api/v1/public/chain/stats | — | Ledger totals, win rate, head hash. |
GET /api/v1/public/chain/verify | — | {ok, checked, head} or {ok:false, broken_at} — recomputes every hash from genesis. |
GET /api/v1/public/chain/users | q (pseudonym prefix), limit | Pseudonymised accounts on the chain. |
GET /api/v1/public/live/history | limit 1–500 (default 50) | {ok, count, rounds[{round, members, invested, returned, pnl, pnlPct, enteredAt, exitedAt, minutes}]} — closed live-pool rounds, totals only: no post ids, no names. |
GET /api/v1/public/posts/<uuid>/trajectory | — | {ok, post, count, intervalSeconds: 30, minAgeSeconds: 3600, points[{ts, pot, volume, holders, trades}]} — logged snapshots; rows younger than 1 h are never served; ≤ 2000 rows. |
GET /api/v1/public/pulse | hours 1–336 (default 24); keys comma list | {ok, hours, since, series{key: [[ts, value], …]}}. |
GET /api/v1/public/pulse/keys | — | {ok, count, keys[]}. |
GET /api/v1/public/stats | — | {ok, ledger{…}}. |
GET /api/v1/public/dataset | — | Pointer to the CC-BY-4.0 giggles-market dataset: {ok, name, license, tables[], note, url}. |
examples
curl -s https://earlybirdgetstheaura.com/api/v1 | jq .endpoints
curl -s "https://earlybirdgetstheaura.com/api/v1/public/live/history?limit=5" | jq .
curl -s "https://earlybirdgetstheaura.com/api/v1/public/chain/verify" | jq .
curl -s -H "Authorization: Bearer <access-token>" \
"https://earlybirdgetstheaura.com/api/v1/posts/<post-uuid>/market" | jq .market
curl -s -H "Authorization: Bearer <access-token>" \
"https://earlybirdgetstheaura.com/api/v1/users/<user-uuid>/posts" | jq '.posts[0]'
// browser (CORS is open)
const r = await fetch("https://earlybirdgetstheaura.com/api/v1/leaderboard?period=7d",
{ headers: { Authorization: "Bearer " + ACCESS_TOKEN } });
const { ok, leaderboard } = await r.json();