API / API reference

Projects

Create, read and update projects.

Single resources and collections come back wrapped in data. Paginated collections add links and meta. Errors are never wrapped; see Errors.
GET /api/v1/projects Scope projects:read

List the projects this token can reach

Every project this designer owns, newest first, paginated. Each entry carries component_count, so you can show a picker without a second call per project.

This is normally your first call after /me: a designer picks which of their games your tool should work on, and the id you get back is the {project} in almost every other path on this page.

curl "https://dustinsdesignerden.com/api/v1/projects" \
  -H "Authorization: Bearer $DDD_TOKEN"
const res = await fetch('https://dustinsdesignerden.com/api/v1/projects', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${token}`,
  },
});

const data = await res.json();
$response = Http::withToken($token)->get('https://dustinsdesignerden.com/api/v1/projects');

$data = $response->json();
res = requests.get(
    'https://dustinsdesignerden.com/api/v1/projects',
    headers={'Authorization': f'Bearer {token}'},
)

data = res.json()
Response 200
{
    "data": [
        {
            "id": 1,
            "name": "Harvest Moon",
            "short_description": "A farming game.",
            "description": null,
            "is_locked": false,
            "player_count_min": 2,
            "player_count_max": 4,
            "playtime_minutes": 45,
            "min_age": null,
            "genre": "Worker Placement",
            "theme": null,
            "primary_mechanic": null,
            "edition": null,
            "logo_url": null,
            "backdrop_url": null,
            "website_url": null,
            "bgg_id": null,
            "component_count": 0,
            "created_at": "2026-08-01T16:20:38+00:00",
            "updated_at": "2026-08-01T16:20:38+00:00"
        }
    ],
    "links": {
        "first": "http://dustinsdesignerden.test/api/v1/projects?page=1",
        "last": "http://dustinsdesignerden.test/api/v1/projects?page=1",
        "prev": null,
        "next": null
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "last_page": 1,
        "links": [
            {
                "url": null,
                "label": "« Previous",
                "page": null,
                "active": false
            },
            {
                "url": "http://dustinsdesignerden.test/api/v1/projects?page=1",
                "label": "1",
                "page": 1,
                "active": true
            },
            {
                "url": null,
                "label": "Next »",
                "page": null,
                "active": false
            }
        ],
        "path": "http://dustinsdesignerden.test/api/v1/projects",
        "per_page": 50,
        "to": 1,
        "total": 1
    }
}

Parameters

per_page integer · query
Defaults to 50
page integer · query

Response: Pagination

data array
The page of results.
links object
first, last, prev and next URLs.
meta object
current_page, last_page, per_page, from, to, total and path.

Response: Project

id integer
Designer Den's id for the project. Use it in every project-scoped path.
name string
Project name.
short_description string|null
One-line summary.
description string|null
Long description.
is_locked boolean
A locked project is read-only. Every write returns 409 while this is true.
player_count_min integer|null
Minimum players.
player_count_max integer|null
Maximum players.
playtime_minutes integer|null
Typical play length.
min_age integer|null
Recommended minimum age.
genre string|null
Free text.
theme string|null
Free text.
primary_mechanic string|null
Free text.
edition string|null
Free text.
logo_url string|null
Project logo. Set in the app, not through this API.
backdrop_url string|null
Project backdrop. Set in the app.
website_url string|null
Project website.
bgg_id string|null
BoardGameGeek id.
component_count integer
How many components the project has.
created_at string
ISO 8601 timestamp.
updated_at string
ISO 8601 timestamp.
POST /api/v1/projects Scope projects:write

Create a project

Subject to the designer's plan limit. A free account is capped, and exceeding it returns 403 plan_limit_reached. Surface that message rather than swallowing it.

curl -X POST "https://dustinsdesignerden.com/api/v1/projects" \
  -H "Authorization: Bearer $DDD_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Second Game",
    "short_description": "A quick filler."
}'
const res = await fetch('https://dustinsdesignerden.com/api/v1/projects', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${token}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "name": "Second Game",
      "short_description": "A quick filler."
  }),
});

const data = await res.json();
$response = Http::withToken($token)->post('https://dustinsdesignerden.com/api/v1/projects', [
    'name' => 'Second Game',
    'short_description' => 'A quick filler.',
]);

$data = $response->json();
res = requests.post(
    'https://dustinsdesignerden.com/api/v1/projects',
    headers={'Authorization': f'Bearer {token}'},
    json={
    'name': 'Second Game',
    'short_description': 'A quick filler.'
},
)

data = res.json()
Response 201
{
    "data": {
        "id": 2,
        "name": "Second Game",
        "short_description": "A quick filler.",
        "description": null,
        "is_locked": false,
        "player_count_min": null,
        "player_count_max": null,
        "playtime_minutes": null,
        "min_age": null,
        "genre": null,
        "theme": null,
        "primary_mechanic": null,
        "edition": null,
        "logo_url": null,
        "backdrop_url": null,
        "website_url": null,
        "bgg_id": null,
        "component_count": 0,
        "created_at": "2026-08-01T16:20:39+00:00",
        "updated_at": "2026-08-01T16:20:39+00:00"
    }
}

Body parameters

name string required
Project name. Max 255 characters.
short_description string
One-line summary. Max 500.
description string
Long description. Max 20000.
player_count_min integer
1 to 99.
player_count_max integer
1 to 99.
playtime_minutes integer
Typical play length.
min_age integer
0 to 99.
genre string
Free text.
theme string
Free text.
primary_mechanic string
Free text.
edition string
Free text.
website_url url
Project website.
bgg_id string
BoardGameGeek id.

Response: Project

id integer
Designer Den's id for the project. Use it in every project-scoped path.
name string
Project name.
short_description string|null
One-line summary.
description string|null
Long description.
is_locked boolean
A locked project is read-only. Every write returns 409 while this is true.
player_count_min integer|null
Minimum players.
player_count_max integer|null
Maximum players.
playtime_minutes integer|null
Typical play length.
min_age integer|null
Recommended minimum age.
genre string|null
Free text.
theme string|null
Free text.
primary_mechanic string|null
Free text.
edition string|null
Free text.
logo_url string|null
Project logo. Set in the app, not through this API.
backdrop_url string|null
Project backdrop. Set in the app.
website_url string|null
Project website.
bgg_id string|null
BoardGameGeek id.
component_count integer
How many components the project has.
created_at string
ISO 8601 timestamp.
updated_at string
ISO 8601 timestamp.
GET /api/v1/projects/{project} Scope projects:read

Read one project

One project, with its component count. Use it to refresh something you already know about, for instance to check is_locked before you start writing.

A locked project rejects every write with 409 project_locked, so looking first lets you tell the designer why, instead of failing halfway through a sync.

curl "https://dustinsdesignerden.com/api/v1/projects/1" \
  -H "Authorization: Bearer $DDD_TOKEN"
const res = await fetch('https://dustinsdesignerden.com/api/v1/projects/1', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${token}`,
  },
});

const data = await res.json();
$response = Http::withToken($token)->get('https://dustinsdesignerden.com/api/v1/projects/1');

$data = $response->json();
res = requests.get(
    'https://dustinsdesignerden.com/api/v1/projects/1',
    headers={'Authorization': f'Bearer {token}'},
)

data = res.json()
Response 200
{
    "data": {
        "id": 1,
        "name": "Harvest Moon",
        "short_description": "A farming game.",
        "description": null,
        "is_locked": false,
        "player_count_min": 2,
        "player_count_max": 4,
        "playtime_minutes": 45,
        "min_age": null,
        "genre": "Worker Placement",
        "theme": null,
        "primary_mechanic": null,
        "edition": null,
        "logo_url": null,
        "backdrop_url": null,
        "website_url": null,
        "bgg_id": null,
        "component_count": 0,
        "created_at": "2026-08-01T16:20:38+00:00",
        "updated_at": "2026-08-01T16:20:38+00:00"
    }
}

Parameters

project integer · path required
Project id.

Response: Project

id integer
Designer Den's id for the project. Use it in every project-scoped path.
name string
Project name.
short_description string|null
One-line summary.
description string|null
Long description.
is_locked boolean
A locked project is read-only. Every write returns 409 while this is true.
player_count_min integer|null
Minimum players.
player_count_max integer|null
Maximum players.
playtime_minutes integer|null
Typical play length.
min_age integer|null
Recommended minimum age.
genre string|null
Free text.
theme string|null
Free text.
primary_mechanic string|null
Free text.
edition string|null
Free text.
logo_url string|null
Project logo. Set in the app, not through this API.
backdrop_url string|null
Project backdrop. Set in the app.
website_url string|null
Project website.
bgg_id string|null
BoardGameGeek id.
component_count integer
How many components the project has.
created_at string
ISO 8601 timestamp.
updated_at string
ISO 8601 timestamp.
PATCH /api/v1/projects/{project} Scope projects:write

Update a project's details

Updates the project's own details, such as its name or description. Send only the fields you want changed; anything you leave out is untouched.

This never affects components. Deliberately narrow: it cannot alter ownership, lock state or billing. If at least one field really changes, a project.updated webhook fires naming the fields that were written.

curl -X PATCH "https://dustinsdesignerden.com/api/v1/projects/1" \
  -H "Authorization: Bearer $DDD_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "genre": "Engine Builder",
    "playtime_minutes": 60
}'
const res = await fetch('https://dustinsdesignerden.com/api/v1/projects/1', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${token}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "genre": "Engine Builder",
      "playtime_minutes": 60
  }),
});

const data = await res.json();
$response = Http::withToken($token)->patch('https://dustinsdesignerden.com/api/v1/projects/1', [
    'genre' => 'Engine Builder',
    'playtime_minutes' => 60,
]);

$data = $response->json();
res = requests.patch(
    'https://dustinsdesignerden.com/api/v1/projects/1',
    headers={'Authorization': f'Bearer {token}'},
    json={
    'genre': 'Engine Builder',
    'playtime_minutes': 60
},
)

data = res.json()
Response 200
{
    "data": {
        "id": 1,
        "name": "Harvest Moon",
        "short_description": "A farming game.",
        "description": null,
        "is_locked": false,
        "player_count_min": 2,
        "player_count_max": 4,
        "playtime_minutes": 60,
        "min_age": null,
        "genre": "Engine Builder",
        "theme": null,
        "primary_mechanic": null,
        "edition": null,
        "logo_url": null,
        "backdrop_url": null,
        "website_url": null,
        "bgg_id": null,
        "component_count": 0,
        "created_at": "2026-08-01T16:20:38+00:00",
        "updated_at": "2026-08-01T16:20:39+00:00"
    }
}

Parameters

project integer · path required
Project id.

Body parameters

name string
Project name. Max 255 characters.
short_description string
One-line summary. Max 500.
description string
Long description. Max 20000.
player_count_min integer
1 to 99.
player_count_max integer
1 to 99.
playtime_minutes integer
Typical play length.
min_age integer
0 to 99.
genre string
Free text.
theme string
Free text.
primary_mechanic string
Free text.
edition string
Free text.
website_url url
Project website.
bgg_id string
BoardGameGeek id.

Response: Project

id integer
Designer Den's id for the project. Use it in every project-scoped path.
name string
Project name.
short_description string|null
One-line summary.
description string|null
Long description.
is_locked boolean
A locked project is read-only. Every write returns 409 while this is true.
player_count_min integer|null
Minimum players.
player_count_max integer|null
Maximum players.
playtime_minutes integer|null
Typical play length.
min_age integer|null
Recommended minimum age.
genre string|null
Free text.
theme string|null
Free text.
primary_mechanic string|null
Free text.
edition string|null
Free text.
logo_url string|null
Project logo. Set in the app, not through this API.
backdrop_url string|null
Project backdrop. Set in the app.
website_url string|null
Project website.
bgg_id string|null
BoardGameGeek id.
component_count integer
How many components the project has.
created_at string
ISO 8601 timestamp.
updated_at string
ISO 8601 timestamp.