# Add Aden to your website
Source: https://docs.aden.space/docs/developers/website

Show your releases, shows and news on any site. Public data needs no key.
Your site reads from Aden, so a new release, a sold-out show or a news post
shows up there without anyone editing the site.

| You want to show                                    | Use                                                           |
| --------------------------------------------------- | ------------------------------------------------------------- |
| Your profile, links, upcoming shows, releases, news | [The site feed](#the-site-feed): no key, works in the browser |
| Anything private, or more than the feed holds       | [An API key](#private-data-with-an-api-key), from your server |
| A whole new site, built for you                     | **Team → API → Build your website with AI** in Aden           |

## The site feed [#the-site-feed]

One public address returns what your Aden profile already shows, as JSON:

```
https://aden.space/api/v1/public/sites/<handle>
```

Your handle is the part after `@` in your profile address
(`aden.space/@stripes` → `stripes`). On **Team → API**, the arrow beside
**Open in ChatGPT** has **Copy feed URL**.

### What's in it [#whats-in-it]

```json
{
    "data": {
        "artist": {
            "name": "Stripes",
            "slug": "stripes",
            "bio": "…",
            "genres": ["Electronic"],
            "city": "Antwerp",
            "country": "BE",
            "url": "https://aden.space/@stripes",
            "imageUrl": "https://…",
            "bannerUrl": "https://…"
        },
        "links": [{ "platform": "spotify", "url": "https://…" }],
        "shows": [
            {
                "id": 81,
                "name": "Stripes live",
                "date": "2026-11-21",
                "startTime": "21:00:00",
                "venue": "Trix",
                "city": "Antwerp",
                "country": "BE",
                "tour": null,
                "ticketUrl": "https://…",
                "url": "https://aden.space/@stripes/events/…",
                "imageUrl": "https://…",
                "soldOut": false,
                "cancelled": false
            }
        ],
        "releases": [
            {
                "id": 412,
                "kind": "track",
                "title": "Night Bus",
                "releaseDate": "2026-10-03",
                "coverUrl": "https://…",
                "url": "https://aden.space/@stripes/track/night-bus"
            }
        ],
        "posts": [
            {
                "id": 9,
                "title": "New single out",
                "excerpt": "…",
                "date": "2026-10-03",
                "imageUrl": "https://…",
                "url": "https://aden.space/@stripes/posts/…"
            }
        ]
    }
}
```

* `shows`: the next 20, today included. `releases`: the newest 12 tracks and
  12 albums, newest first. `posts`: the latest 6.
* Each release `url` opens its Aden page, with every streaming link.
* Only public things appear: a show marked busy, an unreleased track or a
  draft post never does. Hide your picture on public pages and `imageUrl` is
  `null`.
* Image links never expire, so you can cache the feed as long as you like.

### Use it [#use-it]

<Tabs items="['Any site', 'Next.js', 'curl']">
  <Tab value="Any site">
    Paste this where the releases should appear. It works on any page, including
    Webflow, Framer and Squarespace code blocks.

    ```html
    <ul id="aden-releases"></ul>
    <script>
        fetch('https://aden.space/api/v1/public/sites/stripes')
            .then((res) => res.json())
            .then(({ data }) => {
                const list = document.getElementById('aden-releases')
                for (const release of data.releases) {
                    const item = document.createElement('li')
                    const link = document.createElement('a')
                    link.href = release.url
                    link.textContent = release.title
                    item.append(link)
                    list.append(item)
                }
            })
    </script>
    ```
  </Tab>

  <Tab value="Next.js">
    Read it on the server and let Next.js cache it for five minutes:

    ```tsx title="app/shows.tsx"
    const FEED = 'https://aden.space/api/v1/public/sites/stripes'

    export const Shows = async () => {
        const res = await fetch(FEED, { next: { revalidate: 300 } })
        if (!res.ok) return null
        const { data } = await res.json()

        return (
            <ul>
                {data.shows.map((show) => (
                    <li key={show.id}>
                        {show.date} · {show.venue}, {show.city}
                        {show.soldOut ? ' · Sold out' : null}
                        {show.ticketUrl && !show.soldOut ? (
                            <a href={show.ticketUrl}>Tickets</a>
                        ) : null}
                    </li>
                ))}
            </ul>
        )
    }
    ```
  </Tab>

  <Tab value="curl">
    ```bash
    curl https://aden.space/api/v1/public/sites/stripes
    ```
  </Tab>
</Tabs>

Changes reach the feed within about five minutes. Fetching more often than
that returns the same answer, and one address that asks for more than 60
uncached feeds a minute gets a `429`.

## Private data with an API key [#private-data-with-an-api-key]

An API key reads your team's own data: the whole catalogue, unreleased tracks,
milestones, tasks, contacts. It belongs on a server, never in a web page.

<Callout type="warn">
  A key in a web page is readable by every visitor. Aden refuses any key sent
  from a browser with `403 server_only`, so the mistake shows up while you
  build, not after you ship. For the browser, use the site feed.
</Callout>

<Steps>
  <Step>
    ### Create a key [#create-a-key]

    In Aden, open your team, go to **API** and press **New key**. Pick **Read
    only** unless your code changes things in Aden. Copy the key: it starts with
    `aden_` and is shown once. API keys need the Lite plan or higher.
  </Step>

  <Step>
    ### Store it as a secret [#store-it-as-a-secret]

    Put it in your server's environment, never in your code or git:

    ```bash title=".env"
    ADEN_API_KEY=aden_…
    ADEN_TEAM_ID=216
    ```

    Your team id is the number in the API page's address
    (`aden.space/app/teams/216/api` → `216`).
  </Step>

  <Step>
    ### Call Aden from your server [#call-aden-from-your-server]

    Send the key as a bearer token, and name your team with `teamId`:

    <Tabs items="['Next.js', 'Node', 'curl']">
      <Tab value="Next.js">
        ```tsx title="app/catalogue.tsx"
        const aden = async (path: string) => {
            const res = await fetch(`https://aden.space/api/v1${path}`, {
                headers: { authorization: `Bearer ${process.env.ADEN_API_KEY}` },
                next: { revalidate: 300 },
            })
            if (!res.ok) throw new Error(`Aden answered ${res.status}`)
            return res.json()
        }

        export const Catalogue = async () => {
            const { data: tracks, total } = await aden(
                `/tracks?teamId=${process.env.ADEN_TEAM_ID}&limit=50`
            )
            return (
                <section>
                    <h2>{total} tracks</h2>
                    <ul>
                        {tracks.map((track) => (
                            <li key={track.id}>{track.title}</li>
                        ))}
                    </ul>
                </section>
            )
        }
        ```

        This runs on the server, so the key never reaches the visitor.
      </Tab>

      <Tab value="Node">
        ```ts title="sync.ts"
        const res = await fetch(
            `https://aden.space/api/v1/events?teamId=${process.env.ADEN_TEAM_ID}`,
            { headers: { authorization: `Bearer ${process.env.ADEN_API_KEY}` } }
        )
        const { data: events } = await res.json()
        ```
      </Tab>

      <Tab value="curl">
        ```bash
        curl "https://aden.space/api/v1/tracks?teamId=216&limit=5" \
          -H "Authorization: Bearer $ADEN_API_KEY"
        ```
      </Tab>
    </Tabs>
  </Step>
</Steps>

### What a key can reach [#what-a-key-can-reach]

A key belongs to one team and only ever reaches that team's data. It can call:

| Resource   | List                                                           |
| ---------- | -------------------------------------------------------------- |
| Tracks     | `GET /tracks?teamId=…` (versions under `/tracks/:id/versions`) |
| Albums     | `GET /albums?teamId=…`                                         |
| Playlists  | `GET /playlists?teamId=…`                                      |
| Shows      | `GET /events?teamId=…`                                         |
| Tours      | `GET /tours?teamId=…`                                          |
| Posts      | `GET /posts?teamId=…`                                          |
| Milestones | `GET /milestones?teamId=…`                                     |
| Tasks      | `GET /todos?teamId=…`                                          |
| Contacts   | `GET /contacts?teamId=…`                                       |
| Webhooks   | `GET /webhooks?teamId=…`                                       |
| App data   | `GET /app-data?entityType=…&entityId=…`                        |

Most also have `GET /<resource>/:id` for one item. A **Read & write** key can also create
and change milestones, tasks, contacts, webhooks and app data. Uploading audio,
creating tracks and adding collaborators happen in Aden, by a signed-in member.

Every other endpoint answers a key with `403 endpoint_not_allowed`. The
[API reference](/docs/developers/api/reference) lists each endpoint's fields.

## Limits [#limits]

| Plan | Per minute | Per hour | Per month |
| ---- | ---------- | -------- | --------- |
| Lite | 300        | 1,000    | 50,000    |
| Pro  | 300        | 5,000    | 250,000   |
| Max  | 300        | 20,000   | 1,000,000 |

All of a team's keys share these. Every keyed response carries
`X-RateLimit-Remaining` and `X-RateLimit-Reset`; a `429` adds `Retry-After`.
Cache what you read: a site that fetches on every page view runs out fast. See
[Rate limits](/docs/developers/api/rate-limits).

## Errors [#errors]

| Status | `error`                | What to do                                                                |
| ------ | ---------------------- | ------------------------------------------------------------------------- |
| 401    | `Unauthorized`         | The key is wrong, revoked or expired. Create a new one on **Team → API**. |
| 403    | `server_only`          | The key was sent from a browser. Call Aden from your server.              |
| 403    | `endpoint_not_allowed` | Keys can't call this endpoint. `message` lists the ones they can.         |
| 403    | `insufficient_scope`   | A Read only key tried to change something. Use a Read & write key.        |
| 403    | `plan_required`        | The team's plan has no API access. Upgrade to Lite or higher.             |
| 429    | `rate_limited`         | Wait for `Retry-After` seconds, then cache more.                          |

Error bodies hold the code in `error`, and most add a `message` that says
what happened.

## Keep keys safe [#keep-keys-safe]

* One key per site or script, named after it, so you can revoke one without
  breaking the rest.
* **Read only** unless the code writes.
* Revoke a key on **Team → API** the moment you suspect it leaked. It stops
  working within a minute.