Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.coinlist.co/llms.txt

Use this file to discover all available pages before exploring further.

Fetch full offer details — pricing options, legal terms, milestones, and more — for a single offer the user can access.

Prerequisites

  • Completed OAuth and a working CoinListProvider
  • An offer id from Fetch offers (for example offer.id from OffersGrid onOfferClick)
The hook mirrors the SDK client method fetchOfferDetails: it waits until the provider is ready, then loads details and exposes offerDetailsState (LOADING, ERROR, or CONTENT). When the user picks an offer from the grid, keep that offer in React state and pass offer.id — TypeScript then matches the branded id type the hook expects.
"use client";

import type { ComponentProps } from "react";
import { useState } from "react";
import { OffersGrid } from "@coinlist-co/react/client/components";
import { useCoinListOfferDetails } from "@coinlist-co/react/client/hooks";

type OfferFromGrid = Parameters<
  NonNullable<ComponentProps<typeof OffersGrid>["onOfferClick"]>
>[0];

function OfferDetailPanel({ offer }: { offer: OfferFromGrid }) {
  const { offerDetailsState } = useCoinListOfferDetails(offer.id);

  if (offerDetailsState.type === "LOADING") return <p>Loading details…</p>;
  if (offerDetailsState.type === "ERROR") return <p>Unable to load this offer.</p>;

  const detail = offerDetailsState.offerDetail;
  return (
    <section>
      <h1>{detail.name}</h1>
      {/* Render options, terms, milestones from detail — see API reference for fields */}
    </section>
  );
}

export function OfferBrowseAndDetail() {
  const [selected, setSelected] = useState<OfferFromGrid | null>(null);

  return (
    <>
      <OffersGrid onOfferClick={(offer) => setSelected(offer)} />
      {selected ? <OfferDetailPanel offer={selected} /> : null}
    </>
  );
}

REST: GET /v1/offers/

Use the API directly when you are not in React or you need a one-off server fetch with a token you already hold.
// lib/coinlist-api.ts
export async function getOfferDetails(offerId: string, accessToken: string) {
  const res = await fetch(
    `https://api.coinlist.co/v1/offers/${encodeURIComponent(offerId)}`,
    {
      headers: { Authorization: `Bearer ${accessToken}` },
    },
  );

  if (!res.ok) {
    const error = await res.json();
    throw new Error(error.message ?? "Failed to fetch offer");
  }

  return res.json();
}
See the API reference for the exact response schema.
Example response shape (values are illustrative — always rely on the live API and legal URLs from CoinList):
{
  "object": "offer_details",
  "id": "{offer_id}",
  "slug": "example-offer",
  "name": "Example Token Sale",
  "starts_at": "2026-01-01T00:00:00.000000Z",
  "ends_at": "2026-01-31T00:00:00.000000Z",
  "options": [
    {
      "id": "{option_id}",
      "slug": "standard",
      "price_usd": 0.1,
      "floor_price_usd": 0.08,
      "minimum_purchase_usd": 100,
      "total_token_supply": 1000000,
      "sale_agreement_url": "https://coinlist.co/agreements/example"
    }
  ],
  "terms": [
    { "key": "Vesting", "value": "12 months linear" },
    { "key": "Lockup", "value": "6 months cliff" }
  ],
  "milestones": [
    { "name": "Registration", "schedule": "Jan 1", "status": "completed" },
    { "name": "Sale Open", "schedule": "Jan 15", "status": "active" },
    { "name": "Distribution", "schedule": "Feb 1", "status": "upcoming" }
  ],
  "faqs": [],
  "links": []
}
Display the full terms and link to the sale agreement URL as provided — do not summarize or paraphrase legal terms.

Lower-level client

Equivalent to the hook, without the built-in state machine. Pass the same offer.id you get from OffersGrid:
"use client";

import type { ComponentProps } from "react";
import { OffersGrid } from "@coinlist-co/react/client/components";
import { useCoinList } from "@coinlist-co/react";
import { useEffect, useState } from "react";

type OfferFromGrid = Parameters<
  NonNullable<ComponentProps<typeof OffersGrid>["onOfferClick"]>
>[0];

export function OfferDetailManual({ offer }: { offer: OfferFromGrid }) {
  const { coinlist, isReady } = useCoinList();
  const [detail, setDetail] = useState<unknown>(null);

  useEffect(() => {
    if (!isReady) return;
    void coinlist
      .fetchOfferDetails(offer.id)
      .then(setDetail)
      .catch(() => setDetail(null));
  }, [coinlist, isReady, offer.id]);

  if (!isReady) return <p>Loading…</p>;
  return <pre>{JSON.stringify(detail, null, 2)}</pre>;
}
Prefer useCoinListOfferDetails unless you need custom control flow.

Next step

Create and track participations

Create a participation for an offer option, then monitor participation status.