This is the full developer documentation for Hypemarket API # Hypemarket API > REST API for collabs, campaigns, and social accounts on hypemarket.ai — built for scripts, integrations, and AI agents acting on a user's behalf. ## Quickstart [Section titled “Quickstart”](#quickstart) ```bash # 1. Create a personal access token at https://hypemarket.ai/me/access_tokens export HYPEMARKET_TOKEN="..." # 2. List the organizations the user belongs to curl -H "Authorization: Bearer $HYPEMARKET_TOKEN" \ -H "Accept: application/json" \ https://hypemarket.ai/organizations.json # 3. List collabs for one of those organizations curl -H "Authorization: Bearer $HYPEMARKET_TOKEN" \ -H "Accept: application/json" \ https://hypemarket.ai/organizations/gKpMxN/collabs.json ``` Base URL `https://hypemarket.ai` in production, `http://localhost:3000` in development. IDs are short opaque strings (sqids), not sequential integers — treat them as tokens. Bearer tokens Send `Authorization: Bearer ` on every request. Read-only or read+write — see [Authentication](/start/authentication/). Organization-scoped Most resources live under [`/organizations/:id`](/start/multi-tenancy/). The token is the user; the URL picks the organization. ETags + Pagy Single resources support [conditional GETs](/start/etags/). Collections paginate via [Pagy headers](/start/pagination/). Agent-friendly Raw markdown for every page, plus [`llms.txt`](/llms.txt) and [`llms-full.txt`](/llms-full.txt). See the [agent guide](/agents/overview/) for token handling and confirmation patterns. # Acting on behalf of users > Guidance for AI agents that drive the Hypemarket API on a user's behalf — token handling, organization selection, idempotency, and confirmation patterns. This page is for **AI agents** (or developers building them) that operate the Hypemarket API on behalf of an authenticated human user. ## Identity model [Section titled “Identity model”](#identity-model) Every request is authenticated by a [personal access token](/start/authentication/) the **user** generated. The agent is not a first-class principal in Hypemarket — it acts as the user. That has two consequences: 1. **The agent inherits the user’s permissions.** It cannot do anything the user couldn’t do in the web UI. 2. **The user is responsible.** Audit logs attribute every action to the user, not the agent. Both are deliberate. Treat the token with the same care as a password. ## Token hygiene [Section titled “Token hygiene”](#token-hygiene) * Pick the **minimum scope** at creation time — `read` if the agent only browses, `write` if it will create/update/delete. Scope is fixed for the life of the token; to change it, create a new one and revoke the old. * Store the token in the OS keychain (or equivalent), never in plain-text config or repo files. * One token per agent / device / environment — easier to revoke surgically. * After suspected exposure: `DELETE /me/access_tokens/:id.json` and mint a new one. ## Picking the organization [Section titled “Picking the organization”](#picking-the-organization) Most resources are nested under `/organizations/:id`. The first thing a fresh agent should do: ```http GET /organizations.json Authorization: Bearer Accept: application/json ``` If the user belongs to a single org, auto-select it. If multiple, confirm with the user before mutating anything. ## Recommended request defaults [Section titled “Recommended request defaults”](#recommended-request-defaults) Always send: ```plaintext Authorization: Bearer Accept: application/json User-Agent: / (+contact-url) ``` A descriptive `User-Agent` helps debug issues affecting your agent specifically. ## IDs are opaque strings [Section titled “IDs are opaque strings”](#ids-are-opaque-strings) Every `:id` in URLs and JSON responses is a short opaque string (a [sqid](https://sqids.org/ruby)), not a sequential integer — for example `gKpMxN`, not `3`. Treat them as opaque: don’t parse, increment, or try to guess them. ## Idempotency [Section titled “Idempotency”](#idempotency) Hypemarket does not currently issue idempotency keys. To avoid duplicate writes: * `PATCH` is naturally idempotent for the same payload — safe to retry. * For `POST` (resource creation), record the response `id` locally before retrying. If the retry succeeds and you discover two records exist, `DELETE` the duplicate. * For destructive `DELETE`, treat `404` on retry as success. ## Network errors and retries [Section titled “Network errors and retries”](#network-errors-and-retries) * `5xx` or transport errors: exponential backoff, 3–5 attempts, jitter. * `429` is not currently emitted; rate limits surface as transient `503`s under load. * `401` after a previously valid token usually means the user revoked it — stop and ask for a new token; do not retry. * `403`: the user lacks the role for this action. Don’t retry — escalate to the user. ## Confirmation patterns [Section titled “Confirmation patterns”](#confirmation-patterns) For irreversible operations (`DELETE`, publishing a campaign), echo back what you’re about to do **using the values the API returned**, not the values the user said: > “I’m about to delete the collab **‘Spring Lipstick Drop’** (`gKpMxN`) in the organization **‘Acme Cosmetics’**. Confirm?” This catches drift between user intent and selected resource (wrong org, stale id, etc.). ## Reading docs programmatically [Section titled “Reading docs programmatically”](#reading-docs-programmatically) Every page on this site is also available as raw markdown: * Append `.md` to any URL: `https://docs.hypemarket.ai/start/authentication.md` * [`llms.txt`](/llms.txt) — index of all pages, agent-friendly * [`llms-full.txt`](/llms-full.txt) — entire docs concatenated, suitable for one-shot retrieval When in doubt, fetch the live markdown rather than relying on training-set memory of this API — the resource shapes evolve. ## Reporting issues [Section titled “Reporting issues”](#reporting-issues) If you hit ambiguous behavior or undocumented responses, email with the request, response, and your agent’s `User-Agent`. # Address > The authenticated user's shipping address for product samples. At most one address per user. The authenticated user’s shipping address (for product samples). At most one per user. ## Operations [Section titled “Operations”](#operations) | Operation | Method + path | Token scope | Role | Notes | | ------------------------ | ------------------------- | ----------------- | ------------------ | -------------------------------------- | | Show address | `GET /me/address.json` | `read` or `write` | Authenticated user | Returns `404` if no address exists yet | | Create or update address | `PATCH /me/address.json` | `write` | Authenticated user | Same endpoint upserts | | Delete address | `DELETE /me/address.json` | `write` | Authenticated user | Returns `204 No Content` | ## Resource shape [Section titled “Resource shape”](#resource-shape) ```json { "id": "wDcVbN", "address_one": "1 Infinite Loop", "address_two": null, "city": "Cupertino", "state": "CA", "postal_code": "95014", "country_code": "US", "created_at": "2026-04-01T10:00:00Z", "updated_at": "2026-04-01T10:00:00Z" } ``` ## Show [Section titled “Show”](#show) ```http GET /me/address.json ``` Returns the user’s address, or `404 Not Found` if the user hasn’t set one. ## Create / Update [Section titled “Create / Update”](#create--update) ```http PATCH /me/address.json { "address": { "address_one": "1 Infinite Loop", "city": "Cupertino", "state": "CA", "postal_code": "95014", "country_code": "US" } } ``` Same endpoint creates the address if missing, updates it otherwise. Requires a write-scoped token. Returns `200 OK` with the address. ## Delete [Section titled “Delete”](#delete) ```http DELETE /me/address.json ``` Removes the address. Returns `204 No Content`. The user can later re-create it. ## Errors [Section titled “Errors”](#errors) | Code | When | | ----- | ----------------------------------------------- | | `401` | Missing token, or read token attempting a write | | `404` | No address has been set yet (on `GET`) | | `422` | Validation failed | # Campaigns > A campaign is a budgeted creator program with payout rules and platform targeting. Organization-scoped. A campaign is a budgeted creator program with payout rules and platform targeting. Org-scoped: the URL identifies the organization. ## Operations [Section titled “Operations”](#operations) | Operation | Method + path | Token scope | Role | Notes | | --------------- | ----------------------------------------------------------- | ----------------- | ------------------- | ---------------------------------------------------- | | List campaigns | `GET /organizations/:organization_id/campaigns.json` | `read` or `write` | Organization member | Paginated | | Show campaign | `GET /organizations/:organization_id/campaigns/:id.json` | `read` or `write` | Organization member | ETag on `show` | | Create campaign | `POST /organizations/:organization_id/campaigns.json` | `write` | Organization admin | Creates a draft | | Update campaign | `PATCH /organizations/:organization_id/campaigns/:id.json` | `write` | Organization admin | Post-publish fields are restricted | | Delete campaign | `DELETE /organizations/:organization_id/campaigns/:id.json` | `write` | Organization admin | Only allowed while still deletable by business rules | Not exposed in JSON State transitions such as `publish`, `pause`, and `resume` still redirect through HTML endpoints today. Monetary units Monetary fields (`cpm_amount`, `min_payout_amount`, `total_budget_amount`, etc.) are integers in the currency’s **minor unit** (e.g. `500` USD = $5.00). ## Resource shape [Section titled “Resource shape”](#resource-shape) ```json { "id": "rTcVwY", "name": "Summer UGC Campaign", "status": "draft", "currency": "usd", "countries": ["US", "GB"], "website": "https://example.com/product", "content_type": "ugc", "category": "product", "platform_ids": ["youtube", "tiktok"], "tiktok_music_id": "7038475063498387456", "cpm_amount": 500, "min_payout_amount": 1000, "max_payout_amount": 10000, "base_reward_amount": 0, "total_budget_amount": 0, "used_amount": 0, "unused_amount": 0, "view_count": 0, "submissions_count": 0, "published_at": null, "paused_at": null, "created_at": "2026-05-01T10:00:00Z", "updated_at": "2026-05-06T10:00:00Z", "url": "https://hypemarket.ai/organizations/gKpMxN/campaigns/rTcVwY.json", "banner_image_url": "https://hypemarket.ai/rails/active_storage/...", "brief": "

Create a product demo in your own style...

" } ``` `status` is one of `draft`, `active`, `paused`, `finished`. `brief` is included on `show` only and is rendered HTML (rich text). ## List [Section titled “List”](#list) ```http GET /organizations/:organization_id/campaigns.json ``` Returns the organization’s campaigns (subject to your role’s policy scope). Paginated. ## Show [Section titled “Show”](#show) ```http GET /organizations/:organization_id/campaigns/:id.json ``` Returns one campaign including the rendered `brief` HTML. Sets an ETag. ## Create [Section titled “Create”](#create) ```http POST /organizations/:organization_id/campaigns.json { "campaign": { "name": "Summer UGC Campaign", "website": "https://example.com/product", "content_type": "ugc", "category": "product", "currency": "usd", "cpm_amount": 500, "min_payout_amount": 1000, "max_payout_amount": 10000, "base_reward_amount": 0, "platform_ids": ["youtube", "tiktok"], "countries": ["US", "GB"], "brief": "

Create a product demo in your own style...

" } } ``` Creates a draft campaign. Requires a write-scoped token and an **admin** role on the organization. Returns `201 Created` with the new campaign. If you omit optional fields, the server fills them from the campaign form defaults where possible. Budget totals always start at zero on create; funding happens separately. ## Update [Section titled “Update”](#update) ```http PATCH /organizations/:organization_id/campaigns/:id.json { "campaign": { "name": "Updated campaign name", "cpm_amount": 650 } } ``` Returns `200 OK` with the updated campaign. Once a campaign has been published, only the same fields allowed in the web UI remain writable; financial fields are no longer accepted. ## Delete [Section titled “Delete”](#delete) ```http DELETE /organizations/:organization_id/campaigns/:id.json ``` Returns `204 No Content`. Fails if the campaign is no longer a draft or already has fundings. ## State transitions (not yet exposed via JSON) [Section titled “State transitions (not yet exposed via JSON)”](#state-transitions-not-yet-exposed-via-json) `POST .../publish`, `.../pause`, and `.../resume` still redirect today. JSON support is not rolled out for those actions yet. ## Errors [Section titled “Errors”](#errors) | Code | When | | ----- | ------------------------------------------------------------ | | `401` | Missing token, or read token attempting a write | | `403` | You’re authenticated but your role can’t perform this action | | `404` | Campaign or org does not exist *for you* | | `422` | Validation failed | # Collabs > A collab is an organization's brief that creators can apply to. Organization-scoped — the URL identifies the organization. A collab is an organization’s brief that creators can apply to. Org-scoped: the URL identifies the organization. ## Operations [Section titled “Operations”](#operations) | Operation | Method + path | Token scope | Role | Notes | | ------------- | --------------------------------------------------------- | ----------------- | ------------------- | ----------------------------------------------------- | | List collabs | `GET /organizations/:organization_id/collabs.json` | `read` or `write` | Organization member | Paginated | | Show collab | `GET /organizations/:organization_id/collabs/:id.json` | `read` or `write` | Organization member | ETag on `show` | | Create collab | `POST /organizations/:organization_id/collabs.json` | `write` | Organization admin | Creates a draft | | Update collab | `PATCH /organizations/:organization_id/collabs/:id.json` | `write` | Organization admin | `purge_banner_image` removes banner | | Delete collab | `DELETE /organizations/:organization_id/collabs/:id.json` | `write` | Organization admin | Only draft collabs without submissions can be deleted | Not exposed in JSON State transitions such as `submit`, `pause`, and `unpause` still redirect through HTML endpoints today. ## Resource shape [Section titled “Resource shape”](#resource-shape) ```json { "id": "qZmRpL", "name": "Spring Lipstick Drop", "state": "published", "currency": "usd", "countries": ["US", "CA"], "website": "https://example.com/lipstick", "require_spark_ads": true, "requires_product_shipment": false, "submissions_count": 24, "completed_count": 6, "published_at": "2026-04-15T10:00:00Z", "created_at": "2026-04-01T10:00:00Z", "updated_at": "2026-05-01T10:00:00Z", "url": "https://hypemarket.ai/organizations/gKpMxN/collabs/qZmRpL.json", "banner_image_url": "https://hypemarket.ai/rails/active_storage/...", "brief": "

Show the lipstick in a get-ready-with-me video...

" } ``` `state` is one of `draft`, `submitted_for_review`, `published`, `paused`. `brief` is included on `show` only and is rendered HTML (rich text). ## List [Section titled “List”](#list) ```http GET /organizations/:organization_id/collabs.json ``` Returns the organization’s collabs (subject to your role’s policy scope). Paginated. ## Show [Section titled “Show”](#show) ```http GET /organizations/:organization_id/collabs/:id.json ``` Returns one collab including the rendered `brief` HTML. Sets an ETag. ## Create [Section titled “Create”](#create) ```http POST /organizations/:organization_id/collabs.json { "collab": { "name": "Spring lipsticks", "website": "https://example.com/lipstick", "currency": "eur", "countries": ["FR", "DE"], "require_spark_ads": true, "requires_product_shipment": false, "brief": "

Show the lipstick in a get-ready-with-me video...

" } } ``` Creates a draft collab. Requires a write-scoped token and an **admin** role on the organization. You can send a `collab` payload to override the server-created draft defaults. Any omitted fields keep the generated defaults (for example the default name, default countries, default brief template, and the database default currency). If you omit `collab` entirely, the server still creates a draft from defaults only. To pre-fill from a product page, pass a top-level `website` query param such as `?website=https://...`. That prefill flow is primarily intended for the web UI and may replace generated fields such as `name`, `brief`, `countries`, `website`, and `banner_image`. ## Update [Section titled “Update”](#update) ```http PATCH /organizations/:organization_id/collabs/:id.json { "collab": { "name": "Updated name", "currency": "eur", "countries": ["FR", "DE"], "brief": "

New brief…

" } } ``` Returns `200 OK` with the updated collab. To remove an existing banner, include `"purge_banner_image": "1"` inside the `collab` payload. ## Delete [Section titled “Delete”](#delete) ```http DELETE /organizations/:organization_id/collabs/:id.json ``` Returns `204 No Content` only for draft collabs with no submissions. Fails with `403` if the collab is no longer a draft, and `422` if any submissions exist. ## State transitions (not yet exposed via JSON) [Section titled “State transitions (not yet exposed via JSON)”](#state-transitions-not-yet-exposed-via-json) `POST .../submit`, `.../pause`, `.../unpause` redirect today. JSON support is on the rollout list. ## Errors [Section titled “Errors”](#errors) | Code | When | | ----- | ------------------------------------------------------------ | | `401` | Missing token, or read token attempting a write | | `403` | You’re authenticated but your role can’t perform this action | | `404` | Collab or org does not exist *for you* | | `422` | Validation failed | # Memberships > The link between a user and an organization. Used to manage who's on the team and what role they hold. Organization-scoped. The link between a user and an organization. Org-scoped. Used to manage who’s on the team and what role they hold. ## Operations [Section titled “Operations”](#operations) | Operation | Method + path | Token scope | Role | Notes | | ----------------------------------- | --------------------------------------------------------- | ----------------- | ---------------------------------------- | ---------------------------------- | | List members | `GET /organizations/:organization_id/members.json` | `read` or `write` | Organization member | Paginated, admins first | | Change member role | `PATCH /organizations/:organization_id/members/:id.json` | `write` | Organization admin | Owner and last-admin guards apply | | Remove member or leave organization | `DELETE /organizations/:organization_id/members/:id.json` | `write` | Organization admin or the current member | Owner and last-member guards apply | ## Resource shape [Section titled “Resource shape”](#resource-shape) ```json { "id": "dHaBjN", "role": "admin", "is_owner": true, "created_at": "2026-04-01T10:00:00Z", "updated_at": "2026-04-01T10:00:00Z", "user": { "id": "nFsKwR", "name": "Yaro", "email": "yaro@example.com" } } ``` `role` is `member` or `admin`. `is_owner` is true for the organization owner — the owner cannot be removed or demoted. ## List [Section titled “List”](#list) ```http GET /organizations/:organization_id/members.json ``` Returns members of the organization, ordered admins-first. Paginated. ## Update [Section titled “Update”](#update) ```http PATCH /organizations/:organization_id/members/:id.json { "membership": { "role": "admin" } } ``` Requires admin role on the organization. Cannot demote the only remaining admin. Cannot demote the organization owner. ## Delete (remove member or leave) [Section titled “Delete (remove member or leave)”](#delete-remove-member-or-leave) ```http DELETE /organizations/:organization_id/members/:id.json ``` Removes the membership. If you remove yourself, you’ve left the organization. Cannot remove: * The organization owner * Yourself if you are the sole member * The only remaining admin Returns `204 No Content` on success, `422` with errors on a guarded failure. ## Errors [Section titled “Errors”](#errors) | Code | When | | ----- | --------------------------------------------------- | | `401` | Missing token, or read token attempting a write | | `403` | Authenticated but you’re not an admin | | `404` | Membership or organization does not exist *for you* | | `422` | Guard failed (last admin, owner demote, etc.) | # Notification settings > The authenticated user's push notification preferences. Toggle web/native push delivery without losing existing device subscriptions. The authenticated user’s push notification preferences. ## Operations [Section titled “Operations”](#operations) | Operation | Method + path | Token scope | Role | Notes | | ---------------------------- | -------------------------------------- | ----------------- | ------------------ | ----------------------------------------------- | | Show notification settings | `GET /me/notification_settings.json` | `read` or `write` | Authenticated user | Returns the current push preference state | | Update notification settings | `PATCH /me/notification_settings.json` | `write` | Authenticated user | Toggles delivery without removing subscriptions | ## Resource shape [Section titled “Resource shape”](#resource-shape) ```json { "push_notifications_enabled": true, "push_subscription_count": 2 } ``` `push_subscription_count` is the number of devices currently subscribed for web push. Disabling notifications keeps subscriptions registered (so re-enabling is instant) but suppresses delivery. ## Show [Section titled “Show”](#show) ```http GET /me/notification_settings.json ``` ## Update [Section titled “Update”](#update) ```http PATCH /me/notification_settings.json { "push_notifications_enabled": true } ``` Requires a write-scoped token. Returns `200 OK` with the new state. ## Errors [Section titled “Errors”](#errors) | Code | When | | ----- | ----------------------------------------------- | | `401` | Missing token, or read token attempting a write | # Notifications > The authenticated user's notification inbox. Newest-first, paginated, type-discriminated. The authenticated user’s notification inbox. ## Operations [Section titled “Operations”](#operations) | Operation | Method + path | Token scope | Role | Notes | | ------------------ | ---------------------------- | ----------------- | ------------------ | ----------------------- | | List notifications | `GET /me/notifications.json` | `read` or `write` | Authenticated user | Paginated, newest first | Not exposed in JSON Marking notifications as seen or read is still UI-specific. The JSON endpoint is read-only. ## Resource shape [Section titled “Resource shape”](#resource-shape) ```json { "id": "pXmKbT", "type": "Membership::JoinRequestReceivedNotifier", "params": { "user_name": "Yaro", "organization_name": "Acme" }, "record_type": "Membership::JoinRequest", "record_id": "kRpMnX", "seen_at": "2026-05-05T08:30:00Z", "read_at": null, "created_at": "2026-05-05T08:00:00Z" } ``` `type` is the Noticed event class — use it to dispatch on the notification kind. `params` is event-specific. `record_type` / `record_id` point to the related resource. ## List [Section titled “List”](#list) ```http GET /me/notifications.json ``` Returns the user’s notifications, newest first. Excludes submission-message notifications (those are surfaced inline in the chat UI). Paginated — `Total-Count`, `Total-Pages`, etc. Note Unlike the HTML view, calling this endpoint does **not** mark notifications as seen. That side effect is UI-specific. ## Errors [Section titled “Errors”](#errors) | Code | When | | ----- | ------------- | | `401` | Missing token | # Organizations > An organization the authenticated user belongs to. Endpoints to list, show, create, update, and delete organizations. An organization the authenticated user belongs to. ## Operations [Section titled “Operations”](#operations) | Operation | Method + path | Token scope | Role | Notes | | ------------------- | -------------------------------- | ----------------- | ------------------- | ------------------------------------------- | | List organizations | `GET /organizations.json` | `read` or `write` | Authenticated user | Paginated | | Show organization | `GET /organizations/:id.json` | `read` or `write` | Organization member | ETag on `show` | | Create organization | `POST /organizations.json` | `write` | Authenticated user | Creator becomes owner | | Update organization | `PATCH /organizations/:id.json` | `write` | Organization admin | `purge_logo` removes logo | | Delete organization | `DELETE /organizations/:id.json` | `write` | Organization owner | Guarded by subscription and resource checks | ## Resource shape [Section titled “Resource shape”](#resource-shape) ```json { "id": "gKpMxN", "name": "Acme Cosmetics", "privacy_setting": "public", "website": "https://acme.example.com", "created_at": "2026-04-01T10:00:00Z", "updated_at": "2026-05-01T10:00:00Z", "url": "https://hypemarket.ai/organizations/gKpMxN.json", "logo_url": "https://hypemarket.ai/rails/active_storage/..." } ``` `privacy_setting` is one of `public`, `private`, `restricted`. `logo_url` is omitted when no logo is attached. ## List [Section titled “List”](#list) ```http GET /organizations.json ``` Returns the organizations the authenticated user belongs to. Paginated — see [Pagination](/start/pagination/). ## Show [Section titled “Show”](#show) ```http GET /organizations/:id.json ``` Returns one organization. Sets an ETag — supply `If-None-Match` for `304 Not Modified`. Returns `404` if the organization exists but you’re not a member. ## Create [Section titled “Create”](#create) ```http POST /organizations.json { "organization": { "name": "Hot new organization", "privacy_setting": "public" } } ``` Requires a write-scoped token. The authenticated user becomes the organization’s owner. Returns `201 Created` with the new organization. Logo upload requires a multipart request. ## Update [Section titled “Update”](#update) ```http PATCH /organizations/:id.json { "organization": { "name": "Renamed", "website": "https://new.example.com" } } ``` Requires write scope and an admin role on the organization. To remove an existing logo, include `"purge_logo": "1"` inside the `organization` payload. ## Delete [Section titled “Delete”](#delete) ```http DELETE /organizations/:id.json ``` Returns `204 No Content`. Fails with `422` if the organization still has an active subscription, any campaigns, or any collabs. ## Errors [Section titled “Errors”](#errors) | Code | When | | ----- | ---------------------------------------------------------- | | `401` | Missing token, or read token attempting a write | | `403` | Authenticated, but role does not permit this action | | `404` | Organization does not exist *for you* | | `422` | Validation failed, or a delete guard blocked the operation | # Social accounts > Connected social profiles (TikTok, YouTube, Twitter, etc.) belonging to the authenticated user. Connected social profiles (TikTok, YouTube, Twitter, etc.) belonging to the authenticated user. ## Operations [Section titled “Operations”](#operations) | Operation | Method + path | Token scope | Role | Notes | | --------------------- | ------------------------------------- | ----------------- | ------------------ | -------------------------------- | | List social accounts | `GET /me/social_accounts.json` | `read` or `write` | Authenticated user | Paginated | | Show social account | `GET /me/social_accounts/:id.json` | `read` or `write` | Authenticated user | ETag on `show` | | Create social account | `POST /me/social_accounts.json` | `write` | Authenticated user | Manual URL-based connection only | | Delete social account | `DELETE /me/social_accounts/:id.json` | `write` | Authenticated user | Returns `204 No Content` | Not exposed in JSON OAuth-based platform connection flows such as TikTok and YouTube still go through the browser OAuth flow. ## Resource shape [Section titled “Resource shape”](#resource-shape) ```json { "id": "vBcZpQ", "platform_id": "tiktok", "account_url": "https://tiktok.com/@example", "uid": "1234567890", "status": "verified", "subscriber_count": 12345, "view_count": 9876543, "video_count": 42, "likes_count": 100000, "engagement_rate": "5.2", "last_synced_at": "2026-05-05T07:55:00Z", "created_at": "2026-04-01T10:00:00Z", "updated_at": "2026-05-05T07:55:00Z", "url": "https://hypemarket.ai/me/social_accounts/vBcZpQ.json" } ``` `status` is one of `pending`, `verified`, `failed`. Metric fields (`subscriber_count` etc.) read from the latest snapshot and may be `null` for newly connected accounts that haven’t been synced yet. ## List [Section titled “List”](#list) ```http GET /me/social_accounts.json ``` Returns an array of social accounts belonging to the authenticated user. Paginated — see response headers `Total-Count`, `Total-Pages`. ## Show [Section titled “Show”](#show) ```http GET /me/social_accounts/:id.json ``` Returns one. Sets an ETag — supply `If-None-Match` for `304 Not Modified`. ## Create [Section titled “Create”](#create) ```http POST /me/social_accounts.json { "social_account": { "platform_id": "twitter", "account_url": "https://x.com/example" } } ``` Requires a write-scoped token. Returns `201 Created` with the new resource. OAuth platforms OAuth-based platforms (TikTok, YouTube) cannot be connected this way — they go through the browser OAuth flow. ## Delete [Section titled “Delete”](#delete) ```http DELETE /me/social_accounts/:id.json ``` Requires a write-scoped token. Returns `204 No Content`. ## Errors [Section titled “Errors”](#errors) | Code | When | | ----- | ------------------------------------------------------- | | `401` | Missing token, or read token attempting `POST`/`DELETE` | | `404` | Account does not exist or doesn’t belong to you | | `422` | Validation failed (e.g. duplicate `account_url`) | # Authentication > Create, use, and revoke personal access tokens (PATs) for the Hypemarket API. Tokens are user-bound and scoped read-only or read+write. The Hypemarket API uses **personal access tokens** (PATs). A token is bound to a single user; the same token works across every organization that user belongs to. ## Creating a token (web UI) [Section titled “Creating a token (web UI)”](#creating-a-token-web-ui) 1. Sign in at [hypemarket.ai](https://hypemarket.ai) 2. Navigate to **Account → API access tokens** (or visit [`/me/access_tokens`](https://hypemarket.ai/me/access_tokens)) 3. Click **New token**, give it a description (e.g. “My laptop script”) and choose a permission: * **Read-only** — `GET` and `HEAD` requests only * **Read and write** — all verbs One-time view The token is shown **once**. Copy it immediately — you cannot retrieve it again. Lost a token? Revoke it and create a new one. ## Creating a token (API) [Section titled “Creating a token (API)”](#creating-a-token-api) You can also mint tokens programmatically once you already have a **write-scoped** token: ```http POST /me/access_tokens.json Authorization: Bearer Content-Type: application/json { "access_token": { "description": "CI script", "permission": "write" } } ``` Response (`201 Created`): ```json { "id": "kRpMnX", "token": "Xy3kPq...", "description": "CI script", "permission": "write", "created_at": "2026-05-05T08:00:00Z" } ``` IDs are opaque strings Every `id` in URLs and JSON responses is a short opaque string (a [sqid](https://sqids.org/ruby)) — not a sequential integer. Treat them as opaque tokens; don’t parse, increment, or try to guess them. ## Using a token [Section titled “Using a token”](#using-a-token) Send it as a `Bearer` header on every JSON request: ```bash curl -H "Authorization: Bearer " \ -H "Accept: application/json" \ https://hypemarket.ai/me/social_accounts.json ``` The `.json` suffix or `Accept: application/json` header selects JSON. Using both is fine; without either one the same endpoint falls back to HTML behavior. ## Revoking a token [Section titled “Revoking a token”](#revoking-a-token) ```http DELETE /me/access_tokens/:id.json Authorization: Bearer ``` Requires a write-scoped token. Returns `204 No Content`. Subsequent requests using the revoked token will fail with `401 Unauthorized`. ## Listing your tokens [Section titled “Listing your tokens”](#listing-your-tokens) ```http GET /me/access_tokens.json Authorization: Bearer ``` Note Tokens themselves are **not** returned by this endpoint — only their metadata (id, description, permission, last used). The plaintext token is only visible at creation time. ## Permissions in practice [Section titled “Permissions in practice”](#permissions-in-practice) A read token attempting any non-`GET`/`HEAD` request returns: ```http HTTP/1.1 401 Unauthorized WWW-Authenticate: Token realm="Application" ``` The response body is not guaranteed to be JSON. To upgrade, create a new write-scoped token; tokens are not editable in place. # Errors and status codes > Hypemarket API error model — 401 for auth, 403 for role, 404 for missing or hidden resources, and 422 JSON envelopes for validation. ## Status codes [Section titled “Status codes”](#status-codes) | Code | Meaning | | --------------------------- | ------------------------------------------------------------------- | | `200 OK` | Success | | `201 Created` | Resource created | | `204 No Content` | Resource deleted (or no body to return) | | `304 Not Modified` | Conditional `GET` with matching `If-None-Match` | | `401 Unauthorized` | Missing or insufficient token (also: read token attempting a write) | | `403 Forbidden` | Authenticated but your role doesn’t permit this action | | `404 Not Found` | Resource does not exist *or* is not visible to you | | `422 Unprocessable Content` | Validation failed | ## Error body shapes [Section titled “Error body shapes”](#error-body-shapes) Bearer-token failures (`401`) are not guaranteed to return JSON. The response may be empty or plain text: ```http HTTP/1.1 401 Unauthorized WWW-Authenticate: Token realm="Application" ``` Organization-scoped authorization failures (`403`) and many organization-scoped hidden-resource responses (`404`) use: ```json { "error": "Not authorized" } ``` Some user-scoped missing-resource responses return `404` with no body. Example: `GET /me/address.json` before an address exists. Validation errors: ```json { "errors": { "name": ["can't be blank"], "currency": ["is not included in the list"] } } ``` ## Distinguishing `403` from `404` [Section titled “Distinguishing 403 from 404”](#distinguishing-403-from-404) For organization-scoped endpoints, the API returns `404` when: * The resource doesn’t exist * The resource exists but you’re not a member of the owning organization It returns `403` only when: * You **are** a member of the organization * But your role (or other state, e.g. token scope) doesn’t allow the action In practice: treat `403` and `404` interchangeably during retries — neither will succeed without changing the request. ## Handling read-vs-write token mismatch [Section titled “Handling read-vs-write token mismatch”](#handling-read-vs-write-token-mismatch) A read-scoped token attempting any non-`GET`/`HEAD` request returns: ```http HTTP/1.1 401 Unauthorized WWW-Authenticate: Token realm="Application" ``` Do not rely on a JSON body here. The fix is to create a new write-scoped token; tokens are not editable in place. See [Authentication](/start/authentication/). # Caching with ETags > Single-resource endpoints set ETags. Send If-None-Match to short-circuit unchanged responses with 304 Not Modified. Single-resource endpoints set an `ETag` header. Send `If-None-Match` on subsequent requests to short-circuit unchanged responses with `304 Not Modified`. ## Example [Section titled “Example”](#example) First request: ```http GET /organizations/gKpMxN/collabs/qZmRpL.json Authorization: Bearer Accept: application/json ``` ```http HTTP/1.1 200 OK ETag: W/"5f3a9c1e..." Content-Type: application/json { "id": "qZmRpL", "name": "Spring lipsticks", ... } ``` Second request, supplying the previous `ETag`: ```http GET /organizations/gKpMxN/collabs/qZmRpL.json Authorization: Bearer Accept: application/json If-None-Match: W/"5f3a9c1e..." ``` ```http HTTP/1.1 304 Not Modified ``` No body is returned. The agent should treat its cached representation as still current. ## When to use it [Section titled “When to use it”](#when-to-use-it) * Long-polling or periodic refresh of a single resource (e.g. a collab a user is editing) * Reducing bandwidth on mobile / agent contexts * Avoiding LLM context bloat when re-fetching unchanged data Collection endpoints (`index`) do not currently set ETags — only `show`. # Multi-tenancy > Hypemarket resources are scoped to organizations. Most endpoints live under /organizations/:id. Role-based access via memberships. Hypemarket is multi-tenant. The top-level tenant is an `Organization`. Users join organizations via **memberships**, which carry a role (`member` or `admin`). ## URL shape [Section titled “URL shape”](#url-shape) Most endpoints are scoped to an organization via the URL: ```plaintext /organizations/:organization_id/... ``` The **token authenticates the user**; the **URL identifies the organization**. The user must be a member of that organization or the request returns: * `404 Not Found` — the org doesn’t exist *for you* * `403 Forbidden` — membership found but the action isn’t permitted by your role ## User-scoped endpoints [Section titled “User-scoped endpoints”](#user-scoped-endpoints) A separate `/me/...` namespace covers resources that belong to a user directly, not an organization: * [`/me/access_tokens`](/start/authentication/) * [`/me/address`](/reference/address/) * [`/me/notifications`](/reference/notifications/) * [`/me/notification_settings`](/reference/notification-settings/) * [`/me/social_accounts`](/reference/social-accounts/) ## Roles [Section titled “Roles”](#roles) Each membership has a role: | Role | Can read | Can mutate organization resources | | -------- | -------- | --------------------------------- | | `member` | ✓ | ✗ (read-only on most resources) | | `admin` | ✓ | ✓ | The organization **owner** is an admin who additionally cannot be removed or demoted. ## Picking the right organization [Section titled “Picking the right organization”](#picking-the-right-organization) Most agents should: 1. `GET /organizations.json` to list the organizations the user belongs to 2. Let the user pick (or, for single-org users, auto-select) 3. Cache the selected `organization_id` for the session 4. Prepend it to every organization-scoped URL For the structure of an Organization object, see the [Organizations reference](/reference/organizations/). # Pagination > Collection endpoints paginate with Pagy. Page metadata is exposed as HTTP response headers. Page size is fixed. Collection endpoints paginate with [Pagy](https://ddnexus.github.io/pagy/) and expose page metadata in response headers. ## Request [Section titled “Request”](#request) Append `?page=N` to the URL — `N` is 1-indexed: ```bash curl -H "Authorization: Bearer $TOKEN" \ -H "Accept: application/json" \ "https://hypemarket.ai/organizations/gKpMxN/collabs.json?page=2" ``` Items per page is fixed at the Pagy default (20). The API does not currently accept a `per_page` parameter. ## Response headers [Section titled “Response headers”](#response-headers) | Header | Description | | -------------- | ---------------------------- | | `Current-Page` | Page number returned | | `Page-Items` | Items per page | | `Total-Count` | Total items across all pages | | `Total-Pages` | Total pages | ## Iterating [Section titled “Iterating”](#iterating) A simple pattern for walking every page: ```bash page=1 while :; do resp=$(curl -sS -D /tmp/headers \ -H "Authorization: Bearer $TOKEN" \ -H "Accept: application/json" \ "https://hypemarket.ai/organizations/gKpMxN/collabs.json?page=$page") echo "$resp" total=$(grep -i '^total-pages:' /tmp/headers | awk '{print $2}' | tr -d '\r') [ "$page" -ge "$total" ] && break page=$((page + 1)) done ``` In Python: ```python import httpx page, total = 1, 1 with httpx.Client(headers={"Authorization": f"Bearer {token}", "Accept": "application/json"}) as c: while page <= total: r = c.get(f"https://hypemarket.ai/organizations/gKpMxN/collabs.json", params={"page": page}) r.raise_for_status() yield from r.json() total = int(r.headers["Total-Pages"]) page += 1 ``` # Response format > Successful Hypemarket API responses use JSON. Validation errors use JSON too, while some auth and missing-resource failures are header-only or plain text. Successful API responses are JSON. Collection endpoints return arrays at the top level; single-resource endpoints return a single object. ## Successful responses [Section titled “Successful responses”](#successful-responses) Collection endpoint (`GET /organizations/:organization_id/collabs.json`): ```json [ { "id": "qZmRpL", "name": "Spring lipsticks", "state": "published", ... }, { "id": "mxKbpQ", "name": "Summer fragrances", "state": "draft", ... } ] ``` Single resource (`GET /organizations/:organization_id/collabs/:id.json`): ```json { "id": "qZmRpL", "name": "Spring lipsticks", "state": "published", "brief": "

...

" } ``` `id` values are opaque strings (sqids), not integers — treat them as tokens. `brief`-style rich-text fields are returned as pre-rendered HTML and are typically only included on `show`, not on `index`. ## Error responses [Section titled “Error responses”](#error-responses) Validation errors (`422 Unprocessable Content`) use a structured JSON envelope: ```json { "errors": { "name": ["can't be blank"] } } ``` The `errors` object is keyed by attribute name; each value is an array of human-readable messages. Authorization and missing-resource responses are less uniform: * Bearer-token failures (`401 Unauthorized`) may be header-only or plain text rather than JSON. * Organization-scoped `403 Forbidden` responses, and many organization-scoped `404 Not Found` responses, use: ```json { "error": "Not authorized" } ``` * Some user-scoped missing-resource responses return `404` with no body. Example: `GET /me/address.json` before the user has saved an address. ## Content-Type [Section titled “Content-Type”](#content-type) Use either of these to select JSON: * Request a `.json` URL, for example `/organizations.json` * Send `Accept: application/json` Using both is recommended, but either one is enough. If you send a JSON request body on `POST`, `PATCH`, or `PUT`, also send: ```plaintext Content-Type: application/json ``` Without a `.json` suffix or `Accept: application/json`, the same controllers fall back to HTML behavior. ## See also [Section titled “See also”](#see-also) * [Pagination](/start/pagination/) * [ETags](/start/etags/) * [Errors](/start/errors/)