This is the full developer documentation for Hypemarket API # Hypemarket API > Programmatic access to brands, collabs, campaigns, and creator submissions on Hypemarket. Built for AI agents that act on behalf of real users. ## What this API is [Section titled “What this API is”](#what-this-api-is) The Hypemarket API is the same surface that powers the [Hypemarket](https://hypemarket.ai) web application, served as JSON when the request asks for it. Anything a user can do in the UI is — in principle — doable via the API. Resources documented in the **[Resources](/reference/organizations/)** section are guaranteed to render JSON today. The rest are being rolled out one resource at a time. Bearer tokens [Personal access tokens](/start/authentication/) sent as `Authorization: Bearer ` headers. Read-only and read+write scopes. Organization-scoped Most resources are nested under [`/organizations/:id`](/start/multi-tenancy/). The token authenticates the user; the URL identifies the brand. ETags + Pagy Single resources support [conditional GETs](/start/etags/). Collections paginate via [Pagy headers](/start/pagination/). AI-agent friendly Every page is also available as raw markdown, plus [`llms.txt`](/llms.txt) and [`llms-full.txt`](/llms-full.txt) for retrieval. ## Quickstart [Section titled “Quickstart”](#quickstart) ```bash # 1. Create a token at https://hypemarket.ai/me/access_tokens (one-time view) 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 an organization curl -H "Authorization: Bearer $HYPEMARKET_TOKEN" \ -H "Accept: application/json" \ https://hypemarket.ai/organizations/3/collabs.json ``` ## Base URL [Section titled “Base URL”](#base-url) ```plaintext https://hypemarket.ai ``` For local development, `http://localhost:3000`. ## What’s next [Section titled “What’s next”](#whats-next) [Authentication ](/start/authentication/)Create, use, and revoke personal access tokens. [Multi-tenancy ](/start/multi-tenancy/)How organization-scoped URLs and roles work. [Acting on behalf of users (for AI agents) ](/agents/overview/)Guidance for building agents that drive Hypemarket on a user's behalf. # 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) * Use **read-only** tokens for browsing / planning steps; only escalate to a **write** token when about to mutate. * Store tokens in OS keychain or equivalent, never in plain-text config. * Rotate after suspected exposure: `DELETE /me/access_tokens/:id.json` and mint a new one. * One token per agent / device — easier to revoke surgically. ## 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 Hypemarket support debug rate-limit issues for your agent specifically. ## 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 **#9 ‘Spring Lipstick Drop’** in the brand **‘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, open an issue at [github.com/yshmarov/hypemarket](https://github.com/yshmarov/hypemarket) 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. ## Resource shape [Section titled “Resource shape”](#resource-shape) ```json { "id": 9, "address_one": "1 Infinite Loop", "address_two": null, "city": "Cupertino", "state": "CA", "postal_code": "95014", "country_code": "US", "latitude": "37.331820", "longitude": "-122.030189", "google_place_id": "ChIJ...", "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. `latitude`, `longitude`, and `google_place_id` are optional but recommended for verified addresses. ## 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 brand. 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": 4, "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/3/campaigns/4.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 brand’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 brand. 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 a brand's brief that creators can apply to. Organization-scoped — the URL identifies the brand. A collab is a brand’s brief that creators can apply to. Org-scoped: the URL identifies the brand. ## Resource shape [Section titled “Resource shape”](#resource-shape) ```json { "id": 9, "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/3/collabs/9.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 brand’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 brand. You can send a `collab` payload to override the brand defaults. Any omitted fields fall back to the brand’s defaults (for example default countries and brief template). 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, pass `purge_banner_image: "1"`. ## Delete [Section titled “Delete”](#delete) ```http DELETE /organizations/:organization_id/collabs/:id.json ``` Returns `204 No Content`. Fails if the collab has approved submissions. ## 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 a brand. Used to manage who's on the team and what role they hold. Organization-scoped. The link between a user and a brand. Org-scoped. Used to manage who’s on the team and what role they hold. ## Resource shape [Section titled “Resource shape”](#resource-shape) ```json { "id": 12, "role": "admin", "is_owner": true, "created_at": "2026-04-01T10:00:00Z", "updated_at": "2026-04-01T10:00:00Z", "user": { "id": 7, "name": "Yaro", "email": "yaro@example.com" } } ``` `role` is `member` or `admin`. `is_owner` is true for the brand owner — the owner cannot be removed or demoted. ## List [Section titled “List”](#list) ```http GET /organizations/:organization_id/members.json ``` Returns members of the brand, 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 brand. Cannot demote the only remaining admin. Cannot demote the brand 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 brand. Cannot remove: * The brand 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 brand 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. ## 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. ## Resource shape [Section titled “Resource shape”](#resource-shape) ```json { "id": 88, "type": "Membership::JoinRequestReceivedNotifier", "params": { "user_name": "Yaro", "organization_name": "Acme" }, "record_type": "Membership::JoinRequest", "record_id": 42, "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 > A brand owned or co-administered by the authenticated user. Endpoints to list, show, create, update, and delete brands. A brand owned or co-administered by the authenticated user. ## Resource shape [Section titled “Resource shape”](#resource-shape) ```json { "id": 3, "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/3.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 brands the authenticated user belongs to. Paginated — see [Pagination](/start/pagination/). ## Show [Section titled “Show”](#show) ```http GET /organizations/:id.json ``` Returns one brand. Sets an ETag — supply `If-None-Match` for `304 Not Modified`. Returns `404` if the brand exists but you’re not a member. ## Create [Section titled “Create”](#create) ```http POST /organizations.json { "organization": { "name": "Hot new brand", "privacy_setting": "public" } } ``` Requires a write-scoped token. The authenticated user becomes the brand’s owner. Returns `201 Created` with the new brand. 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 brand. Pass `purge_logo: "1"` to remove an existing logo. ## Delete [Section titled “Delete”](#delete) ```http DELETE /organizations/:id.json ``` Returns `204 No Content`. Fails with `422` if the brand has an active subscription. ## 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` | Brand does not exist *for you* | | `422` | Validation failed (e.g. private brand without a logo) | # 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. ## Resource shape [Section titled “Resource shape”](#resource-shape) ```json { "id": 17, "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/17.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 have any existing 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": 42, "token": "Xy3kPq...", "description": "CI script", "permission": "write", "created_at": "2026-05-05T08:00:00Z" } ``` ## 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 `Accept: application/json` header is required — without it the same endpoint returns HTML. ## Revoking a token [Section titled “Revoking a token”](#revoking-a-token) ```http DELETE /me/access_tokens/:id.json Authorization: Bearer ``` 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" ``` 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, 422 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) Auth and authorization errors: ```json { "error": "Not authorized" } ``` 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 privacy reasons, 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" ``` 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/3/collabs/9.json Authorization: Bearer Accept: application/json ``` ```http HTTP/1.1 200 OK ETag: W/"5f3a9c1e..." Content-Type: application/json { "id": 9, "name": "Spring lipsticks", ... } ``` Second request, supplying the previous `ETag`: ```http GET /organizations/3/collabs/9.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 brands (organizations). Most endpoints live under /organizations/:id. Role-based access via memberships. Hypemarket is multi-tenant. Each **brand** is an `Organization`. Users join brands via **memberships**, which carry a role (`member` or `admin`). ## URL shape [Section titled “URL shape”](#url-shape) Most endpoints are scoped to an organization (brand) 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 a brand: * [`/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 brand resources | | -------- | -------- | ------------------------------- | | `member` | ✓ | ✗ (read-only on most resources) | | `admin` | ✓ | ✓ | The brand **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 brands 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 brand-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/3/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/3/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/3/collabs.json", params={"page": page}) r.raise_for_status() yield from r.json() total = int(r.headers["Total-Pages"]) page += 1 ``` # Response format > All Hypemarket API responses are JSON. Collections return arrays at the top level; single resources return one object. Error shape is consistent. All responses are JSON. Successful 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/:id/collabs.json`): ```json [ { "id": 9, "name": "Spring lipsticks", "state": "published", ... }, { "id": 10, "name": "Summer fragrances", "state": "draft", ... } ] ``` Single resource (`GET /organizations/:id/collabs/9.json`): ```json { "id": 9, "name": "Spring lipsticks", "state": "published", "brief": "

...

" } ``` `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) For authorization / authentication errors: ```json { "error": "Not authorized" } ``` For validation errors (`422 Unprocessable Content`): ```json { "errors": { "name": ["can't be blank"] } } ``` The `errors` object is keyed by attribute name; each value is an array of human-readable messages. ## Content-Type [Section titled “Content-Type”](#content-type) Every JSON request **must** include: ```plaintext Accept: application/json ``` Mutation requests (`POST`, `PATCH`, `PUT`) should also send: ```plaintext Content-Type: application/json ``` Without `Accept: application/json`, endpoints respond with HTML — the same controllers serve both. ## See also [Section titled “See also”](#see-also) * [Pagination](/start/pagination/) * [ETags](/start/etags/) * [Errors](/start/errors/)