Startnext API for Developers
The Startnext API allows you to integrate project data into your website, app, or other applications. This documentation shows you how to use the API.
A note on wording: what this guide calls a reward is named
incentivethroughout the API — in paths (/incentives), scopes (incentives:read) and response schemas. Both refer to the same thing: what a project offers its supporters in return for their support.
Quick Start
The Startnext API uses two types of API keys for external access:
| Key type | Prefix | Use case |
|---|---|---|
| Project API Key | snx_proj_* |
Access data of a specific project you manage |
| User API Key | snx_user_* |
Access your own projects programmatically |
1a. Create a Project API Key
To access data for a specific project, you need a Project API Key:
- Open your project on Startnext
- Go to Project Interface → Administration → API Keys
- Click Create New API Key
- Select the required permissions (scopes)
- Copy the displayed API key and store it securely
Important: The full API key is only shown once at creation. It starts with
snx_proj_followed by a random string.
1b. Create a User API Key
To access your own projects programmatically, you need a User API Key:
- Log in to Startnext
- Go to Settings → API & Integrations
- Click Create New API Key
- Enter a name — the
user:readscope is always included automatically - Copy the displayed API key — it starts with
snx_user_
Important: The full API key is only shown once at creation.
2. First API Request
# Project-specific data (Project API Key)
curl -H "Authorization: Bearer snx_proj_YOUR_API_KEY" \
"https://www.startnext.com/myty/api/crowdfunding/project/your-project"
# Your own and team projects (User API Key)
curl -H "Authorization: Bearer snx_user_YOUR_API_KEY" \
"https://www.startnext.com/myty/api/crowdfunding/user/me/projects"
Terms of Use
By using the Startnext API, you agree to the Startnext API Terms of Use.
Base URL
All API endpoints start with:
https://www.startnext.com/myty/api/crowdfunding
Authentication
Add your API key as a Bearer Token in the Authorization header:
Authorization: Bearer snx_proj_xxxxxxxxxxxxxxxx
Authorization: Bearer is the only accepted channel.
Available Permissions (Scopes)
Project API Key Scopes
When creating a Project API Key, select which project data may be accessed:
| Scope | Description |
|---|---|
blog:read |
Blog entries/updates of the project |
fundings:read |
Public supports for streams (timestamp and name) |
incentives:read |
Project rewards |
orders:read |
Orders (supports) with full details |
project:read |
Basic project data (title, description, funding status) |
subscribers:read |
Subscribers of the project |
wall:read |
Wall posts of the project |
Note: Project API Keys require your project to be on the Pro scope or higher. Basic/Legacy projects cannot create API keys.
User API Key Scopes
A User API Key is an end-user credential equivalent to an OAuth 2.0 access token: it draws from the same scope
catalog and works on the same endpoints as OAuth. When creating a User API Key, select which data may be accessed
(user:read is always included and cannot be deselected):
| Scope | Always enabled | Description |
|---|---|---|
user:read |
✓ | Read your own profile/identity and team invitations (/user/me/identity) — always included |
user:write |
Update your profile data, e.g. profile image (/user/me/image) |
|
project:read |
Read your own and team projects incl. team, blog, timeline, incentives and funding (/user/me/projects, /user/me/incentives) |
|
project:write |
Create and manage your own and team projects incl. team, blog, timeline, incentives and images (POST /project) |
|
contacts:read |
Read personal contact data of supporters and subscribers (orders, subscriber emails) |
API Endpoints
Retrieve Project Data
GET /project/{link_caption}
Scope: project:read
Returns the basic project information.
Example:
curl -H "Authorization: Bearer snx_proj_xxx" \
"https://www.startnext.com/myty/api/crowdfunding/project/my-awesome-project"
Response:
{
"project": {
"id": 12345,
"title": "My Awesome Project",
"subtitle": "A project for everyone",
"link_caption": "my-awesome-project",
"status": "active",
"currency": "EUR",
"current_funding": 7550.00,
"total_funding": 7550.00,
"funding_threshold": 10000.00,
"project_type_all_or_nothing": true,
"supporter_count": 150,
"like_count": 89,
"teaser_text": "Short description...",
"url": {
"main": "https://www.startnext.com/my-awesome-project",
"main_relative": "/my-awesome-project",
"comments": "https://www.startnext.com/my-awesome-project/comments",
"faq": "https://www.startnext.com/my-awesome-project/faq",
"widget": "https://www.startnext.com/my-awesome-project/widget",
"localized_urls": {
"de": "/my-awesome-project",
"en": "/en/my-awesome-project"
}
}
},
"status": 0
}
There is no url.support — build the support link from url.main. url.widget points at the
ready-made embed widget, which is often simpler than building your own funding display.
The top-level
statusfield is legacy. It is a hard-coded0on every successful response and carries no information — do not branch on it. Use the HTTP status code instead. Note that it is unrelated toproject.status, which holds the project's actual phase (new,started,active,successful, …).
List Rewards
GET /project/{link_caption}/incentives
Scope: incentives:read
Returns all rewards of the project, including hidden ones (manually hidden via status=hidden or hidden due to an unmet
funding goal). Use hidden_until_goal_reached and is_unlocked on each reward to determine its visibility state.
Parameters:
| Parameter | Type | Description |
|---|---|---|
limit |
Integer | Maximum number of results (default: 20, max: 100) |
offset |
Integer | Starting position for pagination (default: 0) |
Example:
curl -H "Authorization: Bearer snx_proj_xxx" \
"https://www.startnext.com/myty/api/crowdfunding/project/my-project/incentives?limit=10"
Response:
{
"data": [
{
"id": 12345,
"title": "Limited T-Shirt",
"description": "An exclusive T-shirt for supporters",
"price": 25.00,
"quantity_total": 100,
"quantity_remaining": 42,
"quantity_sold": 58,
"status": "enabled",
"address_required": true,
"is_available": true,
"is_unlocked": true,
"hidden_until_goal_reached": false
}
],
"meta": {
"total": 15,
"limit": 10,
"offset": 0
}
}
quantity_total: 0 means unlimited. Combine hidden_until_goal_reached with is_unlocked to
determine whether a reward is currently purchasable.
List Blog Entries
GET /project/{link_caption}/blog
Scope: blog:read
Returns all blog entries/updates of the project.
Query parameters: blog_type (text|story, default text), offset (default 0), limit (default 20,
max 100), include_supporter_only (0|1, default 0).
Supporter-only entries: Blog entries flagged
is_supporter_onlyare excluded by default. They are only returned when the request setsinclude_supporter_only=1and the caller is verified server-side as allowed to see them (an OAuth Starter/Manager token, or a logged-in starter, team member, platform manager or supporter of the project). A public project API key alone never unlocks them. Requests withinclude_supporter_only=1bypass the CDN cache.
Example:
curl -H "Authorization: Bearer snx_proj_xxx" \
"https://www.startnext.com/myty/api/crowdfunding/project/my-project/blog"
Response:
{
"data": [
{
"id": 5678,
"title": "We made it!",
"text": "Thanks to all supporters...",
"type": "text",
"status": "enabled",
"release_timestamp": 1705315800
}
],
"meta": {
"total": 5,
"limit": 20,
"offset": 0
}
}
List Orders
GET /project/{link_caption}/orders
Scope: orders:read
Note: This endpoint is only available once the project has been successfully funded (≥100%).
Returns all successful supports with full details.
When the data is final. Only supports with status success are returned. Until the project
is paid out, a support can still leave that status (e.g. a failed collection) and then drops out
of this list. After payout — process_status is payoff on the project — supports are no longer
modified: a chargeback or a supporter withdrawing at that point is handled outside Startnext and does
not change the support here. Use transaction_number as the key for matching, and
updated_timestamp to detect changes while the project is still in collection.
Example:
curl -H "Authorization: Bearer snx_proj_xxx" \
"https://www.startnext.com/myty/api/crowdfunding/project/my-project/orders"
Response:
{
"data": [
{
"support_timestamp": 1705315800,
"updated_timestamp": 1705402200,
"transaction_number": "TR1234567890ST",
"total_amount": 50.00,
"status_code": "collected",
"bill_address": {
"firstname": "Jane",
"lastname": "Doe",
"city": "Berlin",
"country": "Germany"
},
"ordered_incentives": [
{
"id": 12345,
"count": 2,
"price_single": 25.00,
"price_total": 50.00
}
]
}
],
"meta": {
"total": 150,
"limit": 20,
"offset": 0
}
}
Field naming: the address objects use
firstname/lastname, whereasuserobjects on other endpoints usefirst_name/last_name. This inconsistency exists in the API itself.
List Subscribers
GET /project/{link_caption}/subscribers
Scope: subscribers:read
Returns all subscribers of the project.
Example:
curl -H "Authorization: Bearer snx_proj_xxx" \
"https://www.startnext.com/myty/api/crowdfunding/project/my-project/subscribers"
Response:
{
"data": [
{
"subscribed_timestamp": 1705315800,
"is_news_subscribed": true,
"user": {
"id": 789,
"first_name": "Jane",
"last_name": "Doe",
"is_public": true
}
}
],
"meta": {
"total": 89,
"limit": 20,
"offset": 0
}
}
user may be null for subscribers without a Startnext account. Note that this endpoint returns
first_name / last_name and has no display_name field.
List Wall Posts
GET /project/{link_caption}/wall
Scope: wall:read
Returns all wall posts of the project.
Example:
curl -H "Authorization: Bearer snx_proj_xxx" \
"https://www.startnext.com/myty/api/crowdfunding/project/my-project/wall"
Response:
{
"data": [
{
"id": 12345,
"message": "Great project! Good luck!",
"status": "enabled",
"created_timestamp": 1705315800,
"user": {
"id": 789,
"first_name": "Jane",
"last_name": "Doe",
"display_name": "Jane Doe"
}
}
],
"meta": {
"total": 25,
"limit": 20,
"offset": 0
}
}
Public Support Stream
GET /project/{link_caption}/fundings
Scope: fundings:read
Returns publicly visible supports with minimal data. Intended for live streams or feeds of recent supporters.
Note: For detailed order data (addresses, rewards, etc.), use the
/ordersendpoint with theorders:readscope instead.
Example:
curl -H "Authorization: Bearer snx_proj_xxx" \
"https://www.startnext.com/myty/api/crowdfunding/project/my-project/fundings"
Response:
{
"data": [
{
"support_timestamp": 1705315800,
"user": {
"id": 789,
"display_name": "Jane Doe"
}
}
],
"meta": {
"total": 150,
"limit": 20,
"offset": 0
}
}
This endpoint returns only support_timestamp and user per entry — nothing else.
OpenAPI Specification
GET /crowdfunding/openapi.json
Auth: None (public endpoint)
Returns the complete OpenAPI specification for all public API endpoints (Project API Key and User API Key endpoints) as JSON. Useful for MCP clients, API explorers, and code generation.
Example:
curl "https://www.startnext.com/myty/api/crowdfunding/openapi.json"
Get API Key Identity
GET /user/me/identity
Credential: User API Key or OAuth Bearer token
Scope: user:read (always included in every credential)
Returns minimal identity data for the authenticated credential: the user ID, URL slug, and the list of active scopes. Contains no PII. Intended for verifying an end-user credential — e.g. by MCP clients — and for discovering which permissions it has.
Example:
curl -H "Authorization: Bearer snx_user_xxx" \
"https://www.startnext.com/myty/api/crowdfunding/user/me/identity"
Response:
{
"data": {
"id": 42,
"link_caption": "jane-doe",
"scopes": [
{
"scope": "user:read",
"endpoints": [
"GET /myty/api/crowdfunding/user/me/identity"
]
},
{
"scope": "project:read",
"endpoints": [
"GET /myty/api/crowdfunding/user/me/projects"
]
}
]
}
}
Get Incentives from Own and Team Projects
GET /user/me/incentives
Credential: User API Key or OAuth Bearer token
Scope: project:read
Returns a paginated list of all active incentives (rewards) from projects where the authenticated user is the initiator or a team member.
Parameters:
| Parameter | Type | Description |
|---|---|---|
sort |
String | Sort order: sold (default), new, price-a, price-d, name-a, name-d, rand |
fundable |
Boolean | Filter by fundable projects only |
is_favorite |
Boolean | Filter favorites only |
limit |
Integer | Maximum number of results (default: 20, max: 100) |
offset |
Integer | Starting position for pagination (default: 0) |
Example:
curl -H "Authorization: Bearer snx_user_xxx" \
"https://www.startnext.com/myty/api/crowdfunding/user/me/incentives"
Response:
{
"data": [
{
"id": 12345,
"title": "Limited T-Shirt",
"price": 25.00,
"is_available": true,
"quantity_total": 100,
"quantity_remaining": 42,
"project": {
"id": 678,
"title": "My Awesome Project",
"link_caption": "my-awesome-project"
}
}
],
"meta": {
"total": 5,
"limit": 20,
"offset": 0
}
}
List Own and Team Projects
GET /user/me/projects
Credential: User API Key or OAuth Bearer token
Scope: project:read
Returns a paginated list of all projects where the authenticated user is the initiator or a team member — including
drafts, regardless of the authentication path. Accepts a User API Key or OAuth Bearer token with the project:read
scope, or a session cookie.
Parameters:
| Parameter | Type | Description |
|---|---|---|
sort |
String | Sort order: project-end-date-d (default), project-end-date-a, project-activation-date-d, project-activation-date-a, project-title-d, project-title-a, project-funding-sum-d, project-funding-sum-a, project-support-count-d, project-support-count-a, project-fan-count-d, project-fan-count-a, rand |
limit |
Integer | Maximum number of results (default: 20, max: 100) |
offset |
Integer | Starting position for pagination (default: 0) |
Example:
curl -H "Authorization: Bearer snx_user_xxx" \
"https://www.startnext.com/myty/api/crowdfunding/user/me/projects?sort=project-activation-date-d&limit=20"
Response:
{
"data": [
{
"id": 12345,
"title": "My Awesome Project",
"link_caption": "my-awesome-project",
"status": "active",
"currency": "EUR",
"current_funding": 7550.00,
"funding_threshold": 10000.00,
"supporter_count": 150
}
],
"meta": {
"total": 3,
"limit": 20,
"offset": 0
}
}
Data Formats
Dates
All dates are returned as UNIX timestamps (seconds since 1970-01-01):
{
"created_timestamp": 1705315800,
"support_timestamp": 1705402200
}
Convert in JavaScript:
const date = new Date(timestamp * 1000);
Convert in PHP:
$date = new DateTime('@' . $timestamp);
Pagination
Paginated endpoints support the limit and offset parameters:
curl -H "Authorization: Bearer snx_proj_xxx" \
"https://www.startnext.com/myty/api/crowdfunding/project/my-project/incentives?limit=10&offset=20"
Edge cases — none of these return 400:
| Input | Behaviour |
|---|---|
limit above 100 |
Silently clamped to 100. Always page until offset + len(data) >= meta.total; never assume your requested page size was honoured — check meta.limit. |
limit=0 or limit= (empty) |
Treated as absent → falls back to the default of 20 |
offset=0 or offset= (empty) |
Falls back to 0 (same result) |
The response contains a meta object with pagination information. meta.limit is the limit that was
actually applied — compare it against what you sent:
{
"data": [
...
],
"meta": {
"total": 150,
"limit": 10,
"offset": 20
}
}
Rate Limiting
API requests are limited to ensure system stability. Which limit applies depends on the credential you use, not on the endpoint.
The values below are the platform defaults and may be adjusted per environment. Do not hard-code them — read the current values from the response headers instead (see below).
Project API Key
Limits depend on the plan of the project the key belongs to:
| Project plan | Per minute | Per day |
|---|---|---|
| Pro | 120 | 10,000 |
| Premium | 300 | 50,000 |
| Enterprise | 600 | 100,000 |
A key belonging to a project on any other plan (Basic, Legacy) falls back to the unauthenticated limits below.
User API Key
| Credential | Per minute | Per day |
|---|---|---|
| User API Key | 60 | 5,000 |
These limits are per key and independent of any project plan — a User API Key that reaches projects on the Premium plan is still limited to 60 requests per minute.
Unauthenticated requests
Endpoints that work without a credential are limited per IP address:
| Per minute | Per day | |
|---|---|---|
| No credential | 30 | 1,000 |
Rate Limit Headers
Responses authenticated with a Project or User API Key carry the current limit status:
X-RateLimit-Day-Limit: 10000
X-RateLimit-Day-Remaining: 9850
X-RateLimit-Day-Reset: 1705363200
X-RateLimit-Minute-Limit: 120
X-RateLimit-Minute-Remaining: 118
X-RateLimit-Minute-Reset: 1705320060
Unauthenticated responses use the same six fields with a X-RateLimit-Public- prefix
(X-RateLimit-Public-Minute-Limit, X-RateLimit-Public-Day-Remaining, …). The *-Reset fields are
UNIX timestamps.
When the Limit Is Exceeded
When the limit is reached, you receive an HTTP 429. The wait time is in the Retry-After
header (seconds) — it is not part of the response body:
HTTP/1.1 429 Too Many Requests
Retry-After: 45
{
"error": "rate_limit_exceeded",
"error_description": "Rate limit exceeded (minute). Please wait 45 seconds before retrying."
}
Read your backoff from the Retry-After header. error_description names the limit that was
exceeded (minute or day), but it is a human-readable message — do not parse it.
Error Handling
HTTP Status Codes
| Code | Meaning |
|---|---|
| 200 | Success |
| 400 | Bad request |
| 401 | Invalid or missing API key |
| 403 | Missing permission (scope) |
| 404 | Project not found |
| 429 | Rate limit exceeded |
| 500 | Server error |
Error Response
All errors use the same envelope. The HTTP status code is only in the status line — there is
no status field in the body.
{
"error": "unauthorized",
"error_description": null
}
{
"error": "insufficient_scope",
"error_description": "This endpoint requires the \"fundings:read\" scope"
}
Branch on error — it is a stable, machine-readable code. error_description is a
human-readable message that may be null and may change without notice.
Versioning and Deprecation
Be aware of what the API does not currently guarantee, so you can plan accordingly:
- No version in the URL or in a header. The base URL carries no version segment and there is no
Accept-VersionorX-API-Versionheader. There is exactly one live version of every endpoint. info.versionin the OpenAPI spec is not a contract. It reads1.0.0and is not incremented per change — do not pin against it.- Deprecations are marked in the spec, not announced by a header. An operation being retired
carries
deprecated: truein the OpenAPI document. There is noSunsetorDeprecationresponse header and no committed notice period. - No changelog endpoint. The only machine-readable source of truth is the spec itself.
Practical consequence: fetch the OpenAPI specification periodically and
diff it — that is currently the only reliable way to notice that a field, an endpoint or a default
has changed. Code defensively: treat unknown response fields as additive rather than failing on
them, and do not depend on field order or on the exact wording of error_description.
Retries and Idempotency
There is no Idempotency-Key support. Whether a retry is safe depends on the endpoint:
| Operation | Safe to retry? | What a retry does |
|---|---|---|
All GET endpoints |
✅ Yes | Read-only, no side effects |
POST /user/me/image (user:write) |
✅ Yes | Replaces the profile image — repeating it yields the same end state |
| Single-slot image uploads (project title, logo, story) | ✅ Yes | Replaces the image in that slot |
| Gallery image uploads (project, reward, blog) | ❌ No | Appends. A retry adds a duplicate image and counts against the limit of 5 per gallery (422 gallery_full) |
POST /project (project:write) |
❌ No | Creates another project. There is no deduplication — a retried request after a timeout leaves you with two projects, and it counts against a daily creation limit (default 10, then 429 rate_limit_exceeded) |
For the non-idempotent operations: on a timeout or a 5xx, do not blind-retry. Read back the
current state first (GET /user/me/projects or the relevant gallery endpoint) and only retry if the
object is genuinely missing.
429 is always safe to retry — wait for Retry-After first. 4xx other than 429 will not
succeed on retry; fix the request instead.
Code Examples
JavaScript (fetch)
const API_KEY = 'snx_proj_xxxxxxxx';
const PROJECT = 'my-project';
async function getProjectData() {
const response = await fetch(
`https://www.startnext.com/myty/api/crowdfunding/project/${PROJECT}`,
{
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Accept': 'application/json'
}
}
);
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || `HTTP ${response.status}`);
}
return response.json();
}
// Usage
getProjectData()
.then(data => {
console.log(`Funding: ${data.project.current_funding} €`);
console.log(`Supporters: ${data.project.supporter_count}`);
})
.catch(error => console.error('Error:', error));
PHP (cURL)
<?php
$apiKey = 'snx_proj_xxxxxxxx';
$project = 'my-project';
function getProjectData($project, $apiKey) {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => "https://www.startnext.com/myty/api/crowdfunding/project/{$project}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer {$apiKey}",
"Accept: application/json"
]
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
throw new Exception("API error: HTTP {$httpCode}");
}
return json_decode($response, true);
}
// Usage
try {
$data = getProjectData($project, $apiKey);
echo "Funding: " . $data['project']['current_funding'] . " €\n";
echo "Supporters: " . $data['project']['supporter_count'] . "\n";
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}
Python (requests)
import requests
API_KEY = 'snx_proj_xxxxxxxx'
PROJECT = 'my-project'
def get_project_data(project, api_key):
url = f'https://www.startnext.com/myty/api/crowdfunding/project/{project}'
headers = {
'Authorization': f'Bearer {api_key}',
'Accept': 'application/json'
}
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json()
# Usage
try:
data = get_project_data(PROJECT, API_KEY)
print(f"Funding: {data['project']['current_funding']} €")
print(f"Supporters: {data['project']['supporter_count']}")
except requests.exceptions.HTTPError as e:
print(f"Error: {e}")
Usage Examples
Display Funding Progress on Your Own Website
Show the current funding status of your project on your website:
<div id="funding-widget">
<div class="progress-bar">
<div class="progress" id="progress"></div>
</div>
<p id="funding-status"></p>
<p><span id="supporters">0</span> supporters</p>
</div>
<script>
async function updateWidget() {
const response = await fetch(
'https://www.startnext.com/myty/api/crowdfunding/project/my-project',
{headers: {'Authorization': 'Bearer snx_proj_xxx'}}
);
const {project} = await response.json();
// project_type_all_or_nothing: true = funding goal must be reached
// project_type_all_or_nothing: false = successful from the first support
if (project.project_type_all_or_nothing && project.funding_threshold) {
const percentage = (project.current_funding / project.funding_threshold) * 100;
document.getElementById('progress').style.width = `${Math.min(percentage, 100)}%`;
document.getElementById('funding-status').innerHTML =
`<span>${project.current_funding.toFixed(2)}</span> ${project.currency} of ` +
`<span>${project.funding_threshold.toFixed(2)}</span> ${project.currency} funded`;
} else {
// Project without funding goal
document.getElementById('progress').style.width = '100%';
document.getElementById('funding-status').innerHTML =
`<span>${project.current_funding.toFixed(2)}</span> ${project.currency} raised`;
}
document.getElementById('supporters').textContent = project.supporter_count;
}
updateWidget();
setInterval(updateWidget, 60000); // Refresh every 60 seconds
</script>
Display Rewards in Your Own Shop
async function loadIncentives() {
const response = await fetch(
'https://www.startnext.com/myty/api/crowdfunding/project/my-project/incentives',
{headers: {'Authorization': 'Bearer snx_proj_xxx'}}
);
const {data} = await response.json();
const container = document.getElementById('incentives');
data.forEach(incentive => {
const card = document.createElement('div');
card.className = 'incentive-card';
card.innerHTML = `
<h3>${incentive.title}</h3>
<p>${incentive.description}</p>
<p class="price">${incentive.price.toFixed(2)} €</p>
<p class="stock">${incentive.quantity_total === 0
? 'unlimited'
: `${incentive.quantity_remaining} remaining`}</p>
<a href="https://www.startnext.com/my-project/support" class="button">
Support now
</a>
`;
container.appendChild(card);
});
}