> ## Documentation Index
> Fetch the complete documentation index at: https://developers.investorlift.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart for sellers

> Create a draft, add a photo and the contract, publish, register a webhook and read the events feed from your CRM.

<Note>
  The Marketplace API is in a private beta. It answers only for accounts Investorlift has enabled. [Access](/marketplace/access) says how to ask for one. A route shape on these pages can change before the beta ends. The changelog records every change.
</Note>

On this page you act for your organization. You create a draft deal, add a photo and the acquisition contract, publish it,
and register a webhook. Then you read the events feed. Every request goes to `https://api.investorlift.com/marketplace/v1`
with an OAuth token.

The token needs five scopes. `marketplace:profile` is for the check in step 1. `deals:write` is for the draft, the media,
the contract and the publish. `deals:read` is for the read-back in step 5. `webhooks:manage` is for the endpoint, and
`events:read` is for the feed. `webhooks:manage` needs the owner or admin role in the organization, and a member's token
answers `403 insufficient_role` on that step.

<Steps>
  <Step title="Get access and a token">
    [Access](/marketplace/access) says how Investorlift enables your account. [Authentication](/marketplace/authentication)
    says how your client gets an access token for the audience `https://api.investorlift.com/marketplace/v1`.

    Put the token in your shell. Every snippet on this page reads it from `MARKETPLACE_TOKEN`.

    ```bash theme={null}
    export MARKETPLACE_TOKEN="eyJ..."
    ```

    Check what the token can do. `GET /me` needs `marketplace:profile`.

    <CodeGroup>
      ```bash curl theme={null}
      curl "https://api.investorlift.com/marketplace/v1/me" \
        -H "Authorization: Bearer $MARKETPLACE_TOKEN"
      ```

      ```javascript Node.js theme={null}
      const res = await fetch("https://api.investorlift.com/marketplace/v1/me", {
        headers: { Authorization: `Bearer ${process.env.MARKETPLACE_TOKEN}` },
      });
      if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
      const body = await res.json();
      ```

      ```python Python theme={null}
      import os

      import requests

      r = requests.get(
          "https://api.investorlift.com/marketplace/v1/me",
          headers={"Authorization": f"Bearer {os.environ['MARKETPLACE_TOKEN']}"},
          timeout=30,
      )
      r.raise_for_status()
      body = r.json()
      ```
    </CodeGroup>

    The block is the answer, cut to the fields this step explains.

    ```json theme={null}
    {
      "data": {
        "organization": {
          "id": "slr_3Kd9mQ2xB7Lp",
          "name": "SAGUARO HOLDINGS LLC",
          "role": "owner",
          "designations": ["seller"]
        },
        "sides": ["sell"],
        "scopes": ["deals:read", "deals:write", "events:read", "marketplace:profile", "webhooks:manage"],
        "terms": {
          "sell": { "accepted": true, "agreement_id": "marketplace-api-terms-v2026-09-18" },
          "buy": { "accepted": false, "agreement_id": "marketplace-api-terms-v2026-09-18" }
        },
        "environment": "production",
        "api_version": "2026-09-18"
      },
      "meta": {
        "request_id": "6f1c2a8e-3b7d-4f21-9a0c-2e5b81d7a4f3",
        "api_version": "2026-09-18"
      }
    }
    ```

    `sides` must carry `sell`, and `organization.role` must read `owner` or `admin` for step 6. Every answer carries
    `meta.request_id` and `meta.api_version`. Quote the request id when you write to support.
  </Step>

  <Step title="Create a draft">
    A draft holds no property slot and costs nothing. Send the address, the property type, the prices and the condition. The
    draft, the publish and the webhook send an `Idempotency-Key`. That is a string of your own, and the API keeps it for
    24 hours. The media post and the document post take none. [Idempotency](/marketplace/concepts/idempotency) has the key rules.

    <CodeGroup>
      ```bash curl theme={null}
      curl -X POST "https://api.investorlift.com/marketplace/v1/sell/drafts" \
        -H "Authorization: Bearer $MARKETPLACE_TOKEN" \
        -H "Content-Type: application/json" \
        -H "Idempotency-Key: $(uuidgen)" \
        -d '{
          "address": { "street": "4218 E Campbell Ave", "city": "Phoenix", "state": "AZ", "zip": "85018" },
          "basics": { "property_type": "single_family", "beds": 3, "baths": 2, "sq_footage": 1640, "year_built": 1978 },
          "price": { "asking_price": 285000, "purchase_price": 262000, "min_emd": 5000 },
          "value": { "arv_estimate": 410000, "condition": "MAJOR_REHAB" },
          "description": "Block home on a corner lot. Roof from 2019, original kitchen and baths. Sold as-is, cash or hard money, close in 14 days."
        }'
      ```

      ```javascript Node.js theme={null}
      import crypto from "node:crypto";

      const res = await fetch("https://api.investorlift.com/marketplace/v1/sell/drafts", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.MARKETPLACE_TOKEN}`,
          "Content-Type": "application/json",
          "Idempotency-Key": crypto.randomUUID(),
        },
        body: JSON.stringify({
          address: { street: "4218 E Campbell Ave", city: "Phoenix", state: "AZ", zip: "85018" },
          basics: { property_type: "single_family", beds: 3, baths: 2, sq_footage: 1640, year_built: 1978 },
          price: { asking_price: 285000, purchase_price: 262000, min_emd: 5000 },
          value: { arv_estimate: 410000, condition: "MAJOR_REHAB" },
          description:
            "Block home on a corner lot. Roof from 2019, original kitchen and baths. Sold as-is, cash or hard money, close in 14 days.",
        }),
      });
      if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
      const draft = (await res.json()).data;
      ```

      ```python Python theme={null}
      import os
      import uuid

      import requests

      r = requests.post(
          "https://api.investorlift.com/marketplace/v1/sell/drafts",
          json={
              "address": {"street": "4218 E Campbell Ave", "city": "Phoenix", "state": "AZ", "zip": "85018"},
              "basics": {"property_type": "single_family", "beds": 3, "baths": 2, "sq_footage": 1640, "year_built": 1978},
              "price": {"asking_price": 285000, "purchase_price": 262000, "min_emd": 5000},
              "value": {"arv_estimate": 410000, "condition": "MAJOR_REHAB"},
              "description": "Block home on a corner lot. Roof from 2019, original kitchen and baths. Sold as-is, cash or hard money, close in 14 days.",
          },
          headers={
              "Authorization": f"Bearer {os.environ['MARKETPLACE_TOKEN']}",
              "Idempotency-Key": str(uuid.uuid4()),
          },
          timeout=30,
      )
      r.raise_for_status()
      draft = r.json()["data"]
      ```
    </CodeGroup>

    The answer is `201` with the draft, cut here to the fields this step explains. The id starts with `drf_`.

    ```json theme={null}
    {
      "data": {
        "id": "drf_8Hn2vT4qWz6c",
        "status": "draft",
        "address": { "street": "4218 E Campbell Ave", "city": "Phoenix", "state": "AZ", "zip": "85018" },
        "price": { "asking_price": 285000, "purchase_price": 262000, "min_emd": 5000 },
        "value": { "arv_estimate": 410000, "condition": "MAJOR_REHAB" },
        "media": [],
        "documents": [],
        "contract": null,
        "ready_to_publish": false,
        "incomplete_reason": "The draft needs a photo and the acquisition contract.",
        "created_at": "2026-09-18T15:04:11Z",
        "updated_at": "2026-09-18T15:04:11Z"
      },
      "meta": {
        "request_id": "0b4d7e21-9c3a-4f58-8e6d-1a2b3c4d5e6f",
        "api_version": "2026-09-18"
      }
    }
    ```

    Every money field is a whole number of dollars. `purchase_price` is private: it stays on the seller side and reaches no
    buyer. `ready_to_publish` turns true once the draft passes the publish check, and `incomplete_reason` says what it still
    needs.

    The address check is advisory. If another organization holds a live deal at the address, the API answers
    `409 address_unavailable` and names no owner. The real lock applies at publish.
  </Step>

  <Step title="Add a photo">
    A file reaches a draft in three hops. First you ask for an upload ticket. Then you post the file to the object store with
    the fields of the ticket. Then you register the staged key on the draft. The API never fetches a URL you supply.

    Ask for the ticket. `size_bytes` is the size of the file in bytes.

    <CodeGroup>
      ```bash curl theme={null}
      curl -X POST "https://api.investorlift.com/marketplace/v1/sell/drafts/drf_8Hn2vT4qWz6c/media/uploads" \
        -H "Authorization: Bearer $MARKETPLACE_TOKEN" \
        -H "Content-Type: application/json" \
        -d '{ "kind": "image", "filename": "front.jpg", "content_type": "image/jpeg", "size_bytes": 482113 }'
      ```

      ```javascript Node.js theme={null}
      import { readFile } from "node:fs/promises";

      const bytes = await readFile("front.jpg");
      const res = await fetch(
        "https://api.investorlift.com/marketplace/v1/sell/drafts/drf_8Hn2vT4qWz6c/media/uploads",
        {
          method: "POST",
          headers: {
            Authorization: `Bearer ${process.env.MARKETPLACE_TOKEN}`,
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            kind: "image",
            filename: "front.jpg",
            content_type: "image/jpeg",
            size_bytes: bytes.byteLength,
          }),
        },
      );
      if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
      const ticket = (await res.json()).data;
      ```

      ```python Python theme={null}
      import os

      import requests

      size = os.path.getsize("front.jpg")
      r = requests.post(
          "https://api.investorlift.com/marketplace/v1/sell/drafts/drf_8Hn2vT4qWz6c/media/uploads",
          json={"kind": "image", "filename": "front.jpg", "content_type": "image/jpeg", "size_bytes": size},
          headers={"Authorization": f"Bearer {os.environ['MARKETPLACE_TOKEN']}"},
          timeout=30,
      )
      r.raise_for_status()
      ticket = r.json()["data"]
      ```
    </CodeGroup>

    The ticket says where to post the file, and with which fields.

    ```json theme={null}
    {
      "data": {
        "method": "POST",
        "url": "https://uploads.example.com/investorlift-staging",
        "fields": {
          "key": "staged/drf_8Hn2vT4qWz6c/front.jpg",
          "policy": "eyJleHBpcmF0aW9uIjoiMjAyNi0wOS0xOFQxNToyMDoxMVoiLCJjb25kaXRpb25zIjpbXX0",
          "signature": "d2f1c9a8b7e6f5d4c3b2a1908f7e6d5c4b3a29181"
        },
        "key": "staged/drf_8Hn2vT4qWz6c/front.jpg",
        "content_type": "image/jpeg",
        "max_bytes": 26214400,
        "expires_in": 900
      },
      "meta": {
        "request_id": "3e9a1f6c-2d4b-4a7e-9c0f-5b6a7d8e9f01",
        "api_version": "2026-09-18"
      }
    }
    ```

    Post the file to `url` as a multipart form. Send every entry of `fields` as it is, then the file as the field `file`,
    last. The ticket is valid for `expires_in` seconds, and it takes a file of at most `max_bytes` bytes.

    <CodeGroup>
      ```bash curl theme={null}
      curl -X POST "https://uploads.example.com/investorlift-staging" \
        -F "key=staged/drf_8Hn2vT4qWz6c/front.jpg" \
        -F "policy=eyJleHBpcmF0aW9uIjoiMjAyNi0wOS0xOFQxNToyMDoxMVoiLCJjb25kaXRpb25zIjpbXX0" \
        -F "signature=d2f1c9a8b7e6f5d4c3b2a1908f7e6d5c4b3a29181" \
        -F "file=@front.jpg;type=image/jpeg"
      ```

      ```javascript Node.js theme={null}
      const form = new FormData();
      for (const [name, value] of Object.entries(ticket.fields)) form.append(name, value);
      form.append("file", new Blob([bytes], { type: ticket.content_type }), "front.jpg");
      const upload = await fetch(ticket.url, { method: "POST", body: form });
      if (!upload.ok) throw new Error(`${upload.status} ${await upload.text()}`);
      ```

      ```python Python theme={null}
      with open("front.jpg", "rb") as f:
          upload = requests.post(
              ticket["url"],
              data=ticket["fields"],
              files={"file": ("front.jpg", f, ticket["content_type"])},
              timeout=60,
          )
      upload.raise_for_status()
      ```
    </CodeGroup>

    Register the staged key on the draft. The first photo becomes the cover photo.

    <CodeGroup>
      ```bash curl theme={null}
      curl -X POST "https://api.investorlift.com/marketplace/v1/sell/drafts/drf_8Hn2vT4qWz6c/media" \
        -H "Authorization: Bearer $MARKETPLACE_TOKEN" \
        -H "Content-Type: application/json" \
        -d '{ "key": "staged/drf_8Hn2vT4qWz6c/front.jpg", "filename": "front.jpg", "kind": "image" }'
      ```

      ```javascript Node.js theme={null}
      const res = await fetch("https://api.investorlift.com/marketplace/v1/sell/drafts/drf_8Hn2vT4qWz6c/media", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.MARKETPLACE_TOKEN}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ key: ticket.key, filename: "front.jpg", kind: "image" }),
      });
      if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
      const media = (await res.json()).data;
      ```

      ```python Python theme={null}
      r = requests.post(
          "https://api.investorlift.com/marketplace/v1/sell/drafts/drf_8Hn2vT4qWz6c/media",
          json={"key": ticket["key"], "filename": "front.jpg", "kind": "image"},
          headers={"Authorization": f"Bearer {os.environ['MARKETPLACE_TOKEN']}"},
          timeout=30,
      )
      r.raise_for_status()
      media = r.json()["data"]
      ```
    </CodeGroup>

    The answer is the media list of the draft, in the order a buyer sees it. The list is one page, so `page.next_cursor`
    is null.

    ```json theme={null}
    {
      "data": [
        {
          "id": "Xq7Lp2Rt9Kd3",
          "kind": "image",
          "url": "https://mogul.investorlift.com/media/drf_8Hn2vT4qWz6c/front.jpg",
          "filename": "front.jpg",
          "size_bytes": 482113,
          "content_type": "image/jpeg",
          "order": 0,
          "is_default": true
        }
      ],
      "page": { "next_cursor": null, "limit": 1, "returned": 1 },
      "meta": {
        "request_id": "7c2e5b90-4a1d-4f3e-8b6c-9d0e1f2a3b4c",
        "api_version": "2026-09-18"
      }
    }
    ```
  </Step>

  <Step title="Attach the acquisition contract">
    A property deal needs the acquisition contract before publish. Upload it the same way: a ticket with `kind` set to
    `contract`, then the multipart post. Then attach the staged key as a document of type `contract`. A contract takes one key
    per page, so a multi-page scan sends every key in order.

    <CodeGroup>
      ```bash curl theme={null}
      curl -X POST "https://api.investorlift.com/marketplace/v1/sell/drafts/drf_8Hn2vT4qWz6c/documents" \
        -H "Authorization: Bearer $MARKETPLACE_TOKEN" \
        -H "Content-Type: application/json" \
        -d '{ "type": "contract", "keys": ["staged/drf_8Hn2vT4qWz6c/contract.pdf"], "filename": "contract.pdf" }'
      ```

      ```javascript Node.js theme={null}
      const res = await fetch("https://api.investorlift.com/marketplace/v1/sell/drafts/drf_8Hn2vT4qWz6c/documents", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.MARKETPLACE_TOKEN}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ type: "contract", keys: [contractTicket.key], filename: "contract.pdf" }),
      });
      if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
      const documents = (await res.json()).data;
      ```

      ```python Python theme={null}
      r = requests.post(
          "https://api.investorlift.com/marketplace/v1/sell/drafts/drf_8Hn2vT4qWz6c/documents",
          json={"type": "contract", "keys": [contract_ticket["key"]], "filename": "contract.pdf"},
          headers={"Authorization": f"Bearer {os.environ['MARKETPLACE_TOKEN']}"},
          timeout=60,
      )
      r.raise_for_status()
      documents = r.json()["data"]
      ```
    </CodeGroup>

    The API stores the file and reads its fields, as the app's wizard does. It caches the result on the draft.

    ```json theme={null}
    {
      "data": {
        "documents": [],
        "contract": {
          "filename": "contract.pdf",
          "content_type": "application/pdf",
          "size_bytes": 1834002,
          "uploaded_at": "2026-09-18T15:09:48Z",
          "extraction_confidence": "high",
          "property_address": "4218 E Campbell Ave, Phoenix, AZ 85018",
          "purchase_price": 262000
        }
      },
      "meta": {
        "request_id": "9a8b7c6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d",
        "api_version": "2026-09-18"
      }
    }
    ```

    `contract.purchase_price` is private, as every contract figure is. `documents` lists the supporting files of the other
    types, and the contract sits apart from them.
  </Step>

  <Step title="Publish">
    The publish checks the draft, checks the credit balance, locks the address and creates the deal as `available`. It spends
    the organization's Mogul credits, as the app does, and charges no card. It deletes the draft in the same transaction. Send
    an `Idempotency-Key`, because a publish spends credits.

    <CodeGroup>
      ```bash curl theme={null}
      curl -X POST "https://api.investorlift.com/marketplace/v1/sell/drafts/drf_8Hn2vT4qWz6c/publish" \
        -H "Authorization: Bearer $MARKETPLACE_TOKEN" \
        -H "Content-Type: application/json" \
        -H "Idempotency-Key: $(uuidgen)" \
        -d '{ "notify_buyers": true }'
      ```

      ```javascript Node.js theme={null}
      import crypto from "node:crypto";

      const res = await fetch("https://api.investorlift.com/marketplace/v1/sell/drafts/drf_8Hn2vT4qWz6c/publish", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.MARKETPLACE_TOKEN}`,
          "Content-Type": "application/json",
          "Idempotency-Key": crypto.randomUUID(),
        },
        body: JSON.stringify({ notify_buyers: true }),
      });
      if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
      const published = (await res.json()).data;
      ```

      ```python Python theme={null}
      import uuid

      r = requests.post(
          "https://api.investorlift.com/marketplace/v1/sell/drafts/drf_8Hn2vT4qWz6c/publish",
          json={"notify_buyers": True},
          headers={
              "Authorization": f"Bearer {os.environ['MARKETPLACE_TOKEN']}",
              "Idempotency-Key": str(uuid.uuid4()),
          },
          timeout=60,
      )
      r.raise_for_status()
      published = r.json()["data"]
      ```
    </CodeGroup>

    The answer carries the new deal id. It starts with `mdl_`. `fee` is the credits the publish spent.

    ```json theme={null}
    {
      "data": {
        "deal_id": "mdl_5Rt7yU1pKm3e",
        "fee": 1,
        "status": "available"
      },
      "meta": {
        "request_id": "1f2e3d4c-5b6a-4798-8a7b-6c5d4e3f2a1b",
        "api_version": "2026-09-18"
      }
    }
    ```

    The deal is live on Mogul. Verification starts after the publish, and the event `deal.verification_changed` follows. The
    deal carries `expires_at`, 30 days after the publish. `PATCH /sell/deals/{deal_id}` changes it. Read the deal back with
    `GET /sell/deals/{deal_id}`.

    Three failures are common on a first publish. Each is a problem body with a `code` and a `recovery` sentence.

    | Status and code            | Why                                                                                   | What to do                                                                        |
    | -------------------------- | ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
    | `422 deal_incomplete`      | The draft lacks a field, a photo or the contract. The detail names each one           | Add each field the detail names to the draft, then publish it again               |
    | `402 insufficient_balance` | The organization's balance is below the fee. The body carries the fee and the balance | Add credits to the organization on Investorlift, then publish the draft again     |
    | `409 address_unavailable`  | Another live deal holds the address. The lock applies at publish                      | Wait for the other deal on this address to close, or publish a different property |

    [Errors](/marketplace/concepts/errors) lists every code.
  </Step>

  <Step title="Register a webhook">
    A webhook endpoint gets every event of the types it names, signed. This step needs `webhooks:manage` and the owner or
    admin role. The URL must be `https` on a public host, and the API follows no redirect. An endpoint on a private address
    answers `422 webhook_url_refused`.

    <CodeGroup>
      ```bash curl theme={null}
      curl -X POST "https://api.investorlift.com/marketplace/v1/sell/webhooks" \
        -H "Authorization: Bearer $MARKETPLACE_TOKEN" \
        -H "Content-Type: application/json" \
        -H "Idempotency-Key: $(uuidgen)" \
        -d '{ "url": "https://crm.example.com/investorlift/webhooks", "event_types": ["offer.created", "inquiry.created"] }'
      ```

      ```javascript Node.js theme={null}
      import crypto from "node:crypto";

      const res = await fetch("https://api.investorlift.com/marketplace/v1/sell/webhooks", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.MARKETPLACE_TOKEN}`,
          "Content-Type": "application/json",
          "Idempotency-Key": crypto.randomUUID(),
        },
        body: JSON.stringify({
          url: "https://crm.example.com/investorlift/webhooks",
          event_types: ["offer.created", "inquiry.created"],
        }),
      });
      if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
      const endpoint = (await res.json()).data;
      ```

      ```python Python theme={null}
      import uuid

      r = requests.post(
          "https://api.investorlift.com/marketplace/v1/sell/webhooks",
          json={
              "url": "https://crm.example.com/investorlift/webhooks",
              "event_types": ["offer.created", "inquiry.created"],
          },
          headers={
              "Authorization": f"Bearer {os.environ['MARKETPLACE_TOKEN']}",
              "Idempotency-Key": str(uuid.uuid4()),
          },
          timeout=30,
      )
      r.raise_for_status()
      endpoint = r.json()["data"]
      ```
    </CodeGroup>

    The answer shows the signing secret once. Store it now. The block shows only the fields this step explains.

    ```json theme={null}
    {
      "data": {
        "id": "whk_2Gf6bN9sQx4d",
        "url": "https://crm.example.com/investorlift/webhooks",
        "event_types": ["offer.created", "inquiry.created"],
        "api_version": "2026-09-18",
        "status": "pending_verification",
        "secret": "whsec_Qm9ndXNTZWNyZXRGb3JEb2NzT25seQ",
        "created_at": "2026-09-18T15:12:30Z"
      },
      "meta": {
        "request_id": "b6c7d8e9-f0a1-4b2c-9d3e-4f5a6b7c8d9e",
        "api_version": "2026-09-18"
      }
    }
    ```

    The endpoint starts at `pending_verification`. The API sends a challenge to the URL, and your endpoint echoes it in the
    response. Then the status becomes `active` and the deliveries start. An endpoint pins `api_version` at create and gets
    every event rendered at that version.

    Verify every delivery before you trust it. Read `webhook-id`, `webhook-timestamp` and `webhook-signature` from the
    headers. The signature is an HMAC-SHA256 with the secret. It covers the id, the timestamp and the body, joined with a
    full stop.

    Refuse a timestamp more than 5 minutes from now. Delivery is at least once and unordered, so the event id in `webhook-id`
    is your dedupe key. [Webhooks and events](/marketplace/concepts/webhooks-and-events) has the verification code.
  </Step>

  <Step title="Read your events feed">
    The feed is the replay path. It holds the same events a webhook delivers, in sequence order, for 30 days. Read it with
    `events:read` after a missed delivery, or instead of a webhook.

    <CodeGroup>
      ```bash curl theme={null}
      curl "https://api.investorlift.com/marketplace/v1/sell/events?types=offer.created,inquiry.created" \
        -H "Authorization: Bearer $MARKETPLACE_TOKEN"
      ```

      ```javascript Node.js theme={null}
      const params = new URLSearchParams({ types: "offer.created,inquiry.created" });
      const res = await fetch(`https://api.investorlift.com/marketplace/v1/sell/events?${params}`, {
        headers: { Authorization: `Bearer ${process.env.MARKETPLACE_TOKEN}` },
      });
      if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
      const { data: events, page } = await res.json();
      ```

      ```python Python theme={null}
      r = requests.get(
          "https://api.investorlift.com/marketplace/v1/sell/events",
          params={"types": "offer.created,inquiry.created"},
          headers={"Authorization": f"Bearer {os.environ['MARKETPLACE_TOKEN']}"},
          timeout=30,
      )
      r.raise_for_status()
      events, page = r.json()["data"], r.json()["page"]
      ```
    </CodeGroup>

    A list answer adds `page`. The feed serves 50 events a page by default, and `limit=` takes it to 100. The answer, cut
    to one event:

    ```json theme={null}
    {
      "data": [
        {
          "id": "evt_7Lm3cV8kRt2h",
          "type": "offer.created",
          "sequence": 41827,
          "occurred_at": "2026-09-18T16:02:54Z",
          "api_version": "2026-09-18",
          "data": {
            "id": "ofr_4Wq8zX2nJd6b",
            "deal_id": "mdl_5Rt7yU1pKm3e",
            "buyer_id": "byr_6Pd2kM4vTq8n",
            "status": "new",
            "offer_amount": 270000,
            "emd_amount": 5000,
            "financing": "cash",
            "round": 1,
            "buyer": {
              "buyer_id": "byr_6Pd2kM4vTq8n",
              "name": "Dana Rivera",
              "entity_name": "RIVERA CAPITAL LLC"
            }
          },
          "actor": { "kind": "person", "id": null, "client_id": null }
        }
      ],
      "page": {
        "next_cursor": "s1.41827.Qm9ndXNTaWduYXR1cmVGb3JEb2Nz",
        "limit": 50,
        "returned": 1
      },
      "meta": {
        "request_id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
        "api_version": "2026-09-18"
      }
    }
    ```

    One event has one shape on the feed and on a webhook. Every seller-side event carries `deal_id` and the id of the
    resource, so your CRM correlates it. The buyer block carries the name, the entity and `buyer_id`. The email and the phone
    ride on it only when the endpoint's creator holds `contacts:read`. `actor.id` names a member of your organization as
    `mbr_`. It is null here, because the buyer acted.

    Follow `page.next_cursor` until it is null, and keep the last cursor. The cursor is a sequence position, so `types=`
    changes the filter and keeps the cursor valid. `from=` names an instant for the first read. A page never includes an
    event newer than a short lag, so a late commit never falls behind your cursor.
  </Step>
</Steps>

## Where to go next

<CardGroup cols={3}>
  <Card title="Authentication" icon="id-badge" href="/marketplace/authentication">
    The OAuth flow at the Investorlift issuer, the scopes and the roles.
  </Card>

  <Card title="Webhooks and events" icon="plug" href="/marketplace/concepts/webhooks-and-events">
    The event catalogue, the signature, the retries and the replay rule.
  </Card>

  <Card title="Idempotency" icon="list-check" href="/marketplace/concepts/idempotency">
    The key, the 24 hours, the replay header and the two conflicts.
  </Card>

  <Card title="Errors" icon="triangle-exclamation" href="/marketplace/concepts/errors">
    Every code the API answers, with a body to recognise and the recovery.
  </Card>

  <Card title="Quickstart for buyers" icon="magnifying-glass" href="/marketplace/quickstart-buyer">
    The other side: search, preview, submit and counter.
  </Card>

  <Card title="Terms" icon="scale-balanced" href="/marketplace/terms">
    The Marketplace API Terms you accepted on the consent page.
  </Card>
</CardGroup>


## Related topics

- [Quickstart for buyers](/marketplace/quickstart-buyer.md)
- [The Marketplace API](/marketplace/overview.md)
- [Quickstart](/guides/quickstart.md)
- [Authentication](/marketplace/authentication.md)
- [Changelog](/changelog.md)
