---
title: "Developers: Nimbli's API and AI assistants"
description: "Make and change short links from a script or an AI assistant, with the same rules as the dashboard."
url: https://nimbli.app/developers
mcp: https://nimbli.app/mcp
---

# Developers

Everything you do in Nimbli also works from a script or an AI assistant, with the same rules and the same 25-link limit. They act as you and can't do more than you can.

On this pageAI assistants · API tokens · API basics · listTeams · createLink · createLinks · listLinks · getLink · setDestination · setNote · renameLink · deleteLink · linkClicks · Errors

## Connect an AI assistant

Claude, ChatGPT or any client that speaks MCP.

## Claude

In Claude, open Settings, then Connectors, then "Add custom connector". Paste the address below and sign in when asked.

## ChatGPT

In ChatGPT, open Settings, then Connectors, then "Create". Paste the address below as the MCP server and sign in when asked.

## Other

Add the address below as a remote MCP server (Streamable HTTP, with OAuth). Your client opens Nimbli's sign-in the first time.

https://nimbli.app/mcp

Then ask: "Shorten https://shop.example.com/spring as spring-sale." Deleting a link always asks you first. Assistants never make tokens, change teams or delete your account.

An assistant gets these tools: `myTeams` (the teams you're in), `teamLinks` (list and search a team's links), `linkByCode`, `linkClicks`, `createLink`, `createLinks` (up to 50 at once), `changeDestination`, `setNote`, `renameLink`, `deleteLink`, `askForReview` and `reportLink`.

## API tokens

A token lets a script act as you, in the team you pick.

> Sign in to create a token.[Sign in](https://nimbli.app/login.md?next=%2Fdevelopers)

## API basics

Base address`https://nimbli.app/api/v1` · [openapi.json](https://nimbli.app/api/v1/openapi.json)

Sign in`Authorization: Bearer <token>`. Every call is a POST to its operation's address, with its params as JSON.

Safe retriesSend `Idempotency-Key` when creating; a retry with the same key returns the same link.

LimitsShared with the dashboard: 20 new links an hour and 100 a day per team; 600 calls a minute. Every answer has `RateLimit` and `RateLimit-Policy`; when limited, `Retry-After`.

PagingLists return up to 100 items a call, with a `next` cursor to send back as `after`.

## listTeams POST

The teams you're in, by name. Pass a team's id as team to make or list its links.

```
curl -X POST https://nimbli.app/api/v1/listTeams \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"limit":50}'
```

Example answer

```
{
  "data": [
    {
      "id": "team_01JB8XQ2F4ZK7M3N9P5R2T6V8W",
      "name": "name"
    }
  ],
  "next": "eyJrIjpbIjIwMzAtMDEtMTUiXX0"
}
```

## createLink POST

Makes a short link in a team, with a random 7-character code, and optionally a short link name (3 to 50 of a-z 0-9 - \_) and a note. The destination must be an https address that isn't an IP address, a Nimbli link, another link shortener or blocked. A team makes at most 25 links, 20 an hour and 100 a day (10 a day in its first week). Send an Idempotency-Key header to retry safely.

```
curl -X POST https://nimbli.app/api/v1/createLink \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"team":"team_01JB8XQ2F4ZK7M3N9P5R2T6V8W","destination":"destination","name":"name","note":"note"}'
```

Params

teaman id, team\_... · required

destinationtext · required

nametext or null · optional

notetext or null · optional, 0 to 100 characters

Example answer

```
{
  "id": "link_01JB8XQ2F4ZK7M3N9P5R2T6V8W",
  "code": "code",
  "name": "name",
  "destination": "https://example.com/page",
  "note": "note",
  "state": "active",
  "createdAt": "2030-01-15T09:30:00.000Z",
  "changedAt": "2030-01-15T09:30:00.000Z"
}
```

Errors to branch on

400blocked_destinationNimbli doesn't allow links to that site.

503check_unavailableWe couldn't check this address. Try again in a minute.

400invalid_destinationThat address is too long: 2,048 characters at most.

400invalid_nameUse 3 to 50 lowercase letters, numbers, - and \_.

400invalid_noteA note can have letters, numbers, spaces and - \_ , . ' ( ) & ! ? : only.

429limit_reachedThis team invited that address in the last day. Try again tomorrow.

409name_takenThat name is taken. Try another.

400private_destinationThat address is on a private network.

400reserved_nameThat name is reserved. Try another.

400shortener_destinationNimbli links can't point to other Nimbli links.

409team_full

400unsafe_destinationThis address is on Google's list of unsafe sites.

## createLinks POST

Makes up to 50 links in a team at once. Each gets its own result: the link, or the status and message createLink would have failed with. One failing doesn't stop the others.

```
curl -X POST https://nimbli.app/api/v1/createLinks \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"team":"team_01JB8XQ2F4ZK7M3N9P5R2T6V8W","links":[{"destination":"destination","name":"name","note":"note"}]}'
```

Params

teaman id, team\_... · required

linksa list of an object · required

Example answer

```
{
  "results": [
    {
      "link": {
        "id": "link_01JB8XQ2F4ZK7M3N9P5R2T6V8W",
        "code": "code",
        "name": "name",
        "destination": "https://example.com/page",
        "note": "note",
        "state": "active",
        "createdAt": "2030-01-15T09:30:00.000Z",
        "changedAt": "2030-01-15T09:30:00.000Z"
      },
      "status": 3,
      "code": "code",
      "message": "message"
    }
  ]
}
```

## listLinks POST

A team's links, newest first, deleted ones left out. state keeps only active, flagged or disabled links; q matches the code, short link name, destination or note.

```
curl -X POST https://nimbli.app/api/v1/listLinks \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"team":"team_01JB8XQ2F4ZK7M3N9P5R2T6V8W","state":"active","q":"q","limit":50}'
```

Params

teaman id, team\_... · required

stateone of "active", "flagged", "disabled" or null · optional

qtext or null · optional

Example answer

```
{
  "data": [
    {
      "id": "link_01JB8XQ2F4ZK7M3N9P5R2T6V8W",
      "code": "code",
      "name": "name",
      "destination": "https://example.com/page",
      "note": "note",
      "state": "active",
      "createdAt": "2030-01-15T09:30:00.000Z",
      "changedAt": "2030-01-15T09:30:00.000Z"
    }
  ],
  "next": "eyJrIjpbIjIwMzAtMDEtMTUiXX0"
}
```

## getLink POST

One link by its code. A deleted link, or one outside your teams, is 404.

```
curl -X POST https://nimbli.app/api/v1/getLink \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"code":"codexxx"}'
```

Params

codetext · required, 7 to 7 characters

Example answer

```
{
  "id": "link_01JB8XQ2F4ZK7M3N9P5R2T6V8W",
  "code": "code",
  "name": "name",
  "destination": "https://example.com/page",
  "note": "note",
  "state": "active",
  "createdAt": "2030-01-15T09:30:00.000Z",
  "changedAt": "2030-01-15T09:30:00.000Z"
}
```

## setDestination POST

Changes where a link goes. Printed QR codes and shared links follow. A flagged or disabled link can't change.

```
curl -X POST https://nimbli.app/api/v1/setDestination \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"code":"codexxx","destination":"destination"}'
```

Params

codetext · required, 7 to 7 characters

destinationtext · required

Example answer

```
{
  "id": "link_01JB8XQ2F4ZK7M3N9P5R2T6V8W",
  "code": "code",
  "name": "name",
  "destination": "https://example.com/page",
  "note": "note",
  "state": "active",
  "createdAt": "2030-01-15T09:30:00.000Z",
  "changedAt": "2030-01-15T09:30:00.000Z"
}
```

Errors to branch on

400blocked_destinationNimbli doesn't allow links to that site.

503check_unavailableWe couldn't check this address. Try again in a minute.

400invalid_destinationThat address is too long: 2,048 characters at most.

429limit_reachedThis team invited that address in the last day. Try again tomorrow.

409link_disabledNimbli disabled this link, so it can't be changed.

409link_flaggedYou can't change where it goes while it's flagged. Ask for a review.

400private_destinationThat address is on a private network.

400shortener_destinationNimbli links can't point to other Nimbli links.

400unsafe_destinationThis address is on Google's list of unsafe sites.

## setNote POST

Sets a link's note to self, or clears it with null.

```
curl -X POST https://nimbli.app/api/v1/setNote \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"code":"codexxx","note":"note"}'
```

Params

codetext · required, 7 to 7 characters

notetext or null · required, 0 to 100 characters

Example answer

```
{
  "id": "link_01JB8XQ2F4ZK7M3N9P5R2T6V8W",
  "code": "code",
  "name": "name",
  "destination": "https://example.com/page",
  "note": "note",
  "state": "active",
  "createdAt": "2030-01-15T09:30:00.000Z",
  "changedAt": "2030-01-15T09:30:00.000Z"
}
```

Errors to branch on

400invalid_noteA note can have letters, numbers, spaces and - \_ , . ' ( ) & ! ? : only.

## renameLink POST

Sets, changes or removes (null) a link's short link name. The old name stops working at once and stays your team's; QR codes keep working, since they use the code.

```
curl -X POST https://nimbli.app/api/v1/renameLink \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"code":"codexxx","name":"name"}'
```

Params

codetext · required, 7 to 7 characters

nametext or null · required

Example answer

```
{
  "id": "link_01JB8XQ2F4ZK7M3N9P5R2T6V8W",
  "code": "code",
  "name": "name",
  "destination": "https://example.com/page",
  "note": "note",
  "state": "active",
  "createdAt": "2030-01-15T09:30:00.000Z",
  "changedAt": "2030-01-15T09:30:00.000Z"
}
```

Errors to branch on

400invalid_nameUse 3 to 50 lowercase letters, numbers, - and \_.

429limit_reachedThis team invited that address in the last day. Try again tomorrow.

409link_disabledNimbli disabled this link, so it can't be changed.

409link_flaggedYou can't change where it goes while it's flagged. Ask for a review.

409name_takenThat name is taken. Try another.

400reserved_nameThat name is reserved. Try another.

## deleteLink POST

Deletes a link: it answers "link not found" at once, and its code and name are never used again.

```
curl -X POST https://nimbli.app/api/v1/deleteLink \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"code":"codexxx"}'
```

Params

codetext · required, 7 to 7 characters

Example answer

```
null
```

Errors to branch on

429limit_reachedThis team invited that address in the last day. Try again tomorrow.

## linkClicks POST

A link's clicks per day for the last 30 days, oldest first, with the totals for 7 and 30 days.

```
curl -X POST https://nimbli.app/api/v1/linkClicks \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"code":"codexxx"}'
```

Params

codetext · required, 7 to 7 characters

Example answer

```
{
  "days": [
    {
      "date": "2030-01-15",
      "clicks": 3
    }
  ],
  "last7": 3,
  "last30": 3
}
```

## Errors

Each is application/problem+json with the status, a code, a detail to show, and the field at fault.

400blocked_destinationNimbli doesn't allow links to that site.

503check_unavailableWe couldn't check this address. Try again in a minute.

400invalid_destinationThat address is too long: 2,048 characters at most.

400invalid_nameUse 3 to 50 lowercase letters, numbers, - and \_.

400invalid_noteA note can have letters, numbers, spaces and - \_ , . ' ( ) & ! ? : only.

429limit_reachedThis team invited that address in the last day. Try again tomorrow.

409link_disabledNimbli disabled this link, so it can't be changed.

409link_flaggedYou can't change where it goes while it's flagged. Ask for a review.

409name_takenThat name is taken. Try another.

400private_destinationThat address is on a private network.

400reserved_nameThat name is reserved. Try another.

400shortener_destinationNimbli links can't point to other Nimbli links.

409team_full

400unsafe_destinationThis address is on Google's list of unsafe sites.

401invalid_tokenThe token is unknown, revoked or expired: make another on the app's agents page.

403insufficient_scopeThe token is limited to other operations.

403outside_token_recordThe token works within one record, and this would change something outside it: outside it, the token reads only what someone signed out reads.

403first_stepIts person hasn't taken the app's first step yet, such as agreeing to its terms: \`detail\` says where they take it, in the app.

403token_record_leftThe token works within a record its person no longer belongs to, or that's gone. It works again if they rejoin.

429rate_limitedToo many calls: wait the seconds \`Retry-After\` says, then try again.

409idempotency_key_in_useA call with this \`Idempotency-Key\` is still running: try again in a second.

422idempotency_key_reusedThis \`Idempotency-Key\` was used with another body: a new request needs a new key.

400cursor_mismatch\`after\` came from another operation or other params: start again without it.

410version_retiredThe version was retired: move to the newest one.

400bad_request

401unauthorized

402payment_required

403forbidden

404not_found

409conflict

410gone

422unprocessable_content

429too_many_requests

500internal_server_error

503service_unavailable
