API Documentation
Integrate LogsNinja into your app in minutes. Send events from any language using a simple HTTP POST.
Quick start
Get the starter promptAuthentication
All requests must include an API token in the Authorization header.
Authorization: Bearer YOUR_API_TOKEN
You can create API tokens from your project settings. Go to tokens
If you're an AI agent rather than a human, you can get a token without one being copied and pasted for you. See device authorization
Rate limits & quotas
Limits depend on your plan. The rate limit is per organisation and shared across all API tokens; creating additional tokens does not increase it.
The organisation-wide rate limit applies to every endpoint authenticated with an API token, including POST /v1/events, PUT /v1/users, POST /v1/ping, PUT /v1/metrics, /v1/org/charts, /v1/org/widgets, and delete operations. Each call consumes one request from the same shared limit.
/v1/device/authorize (no token yet at that point) is capped at 10 requests per hour per IP address, and /v1/device/token at 60 requests per 5 minutes per IP address.
| Plan | Rate limit | Events / month | Images / month | Images per event |
|---|---|---|---|---|
| Free | 60 req/min | 3,000 | – | Not included |
| Starter | 300 req/min | 100,000 | 20,000 | 1 per event · 1 MB max · resized to 1024px · request body up to 1 MB |
| Plus | 1,200 req/min | 500,000 | 100,000 | 4 per event · 1 MB max · resized to 1024px · request body up to 8 MB |
| Enterprise | Custom | Custom | Custom | Custom |
When the rate limit is exceeded, the API returns 429 with Too many requests. Event count and image count each have their own monthly quota; when either is reached the API returns 429 with Monthly event or hosted image quota exceeded. Upgrade your plan to continue.
Quota response headers
Every successful POST /v1/events response includes these headers:
X-Quota-Remaining: 2497
X-Image-Quota-Remaining: 18320
X-Plan: starter
X-Quota-Remaining– events remaining in the current calendar month.-1means unlimited (Enterprise).X-Image-Quota-Remaining– images remaining in the current calendar month.-1means unlimited (Enterprise).X-Plan– the current plan of the organisation (free,starter,plus,enterprise).
Metadata format
The metadata field on events and users follows the same rules:
- Keys: lowercase letters, digits, hyphens and underscores only (
^[a-z0-9_-]+$) - Values:
string,number, orboolean - Maximum 20 keys per object
- Keys: 100 characters max
- String values: 255 characters max
{
"plan": "pro",
"amount": 49,
"score": 4.8,
"is-trial": false
}
Starter prompt
Paste into Claude Code, Cursor, Codex, or any AI coding agent to get started.
You are integrating LogsNinja into this project.
LogsNinja is a real-time event tracking and push notification platform. Use its REST API to log application events, track users, update dashboard metrics, and trigger instant push notifications to mobile and desktop devices.
LogsNinja provides no SDK – all integration is done via plain HTTP calls using the libraries and conventions already present in this project.
## Fetch the full API documentation
Before writing any integration code, fetch the complete reference in Markdown:
curl https://logsninja.com/en/docs -H "Accept: text/markdown"
Read it carefully – it covers all endpoints, request fields, response codes, rate limits, and examples.
## Core concepts
**Events** – send a POST to /v1/events. All fields are covered in the documentation.
**Streams** – lightweight channels grouping events. Auto-created on first use (e.g. "payments", "errors").
**Users** – PUT /v1/users to create or update a user profile. The same id is used in the user field when sending events. Optional country_code is caller-supplied (LogsNinja does not geolocate IPs itself) – see the full docs for how to obtain it.
**Metrics** – PUT /v1/metrics to create or update a numeric, text, currency, or percent dashboard tile. Supports atomic diff increments.
**Presence** – POST /v1/ping to signal a user is online without creating an event.
**Event chains** – link events sequentially with before/after fields to trace multi-step workflows.
**Charts** – POST/GET/PATCH/DELETE /v1/org/charts to manage dashboard charts. Same fields and validation rules as the dashboard's own chart editor.
**Widgets** – POST/GET/PATCH/DELETE /v1/org/widgets (plus POST /v1/org/widgets/reorder) to manage what's on the dashboard. The dashboard is a masonry layout, not a rigid grid: "small" widgets (metric, online_users) are meant for width 1 or 2 and render at exactly half the visual height of "large" widgets (chart, world_map, events_7d, top_countries), meant for width 2 or 3 (full row) – keep that in mind when arranging one via the API.
## Authentication
All requests require:
Authorization: Bearer YOUR_API_TOKEN
Creating an account and generating a token both require a human to sign in
to the LogsNinja dashboard - you cannot do either yourself. If you don't
already have a token (e.g. in an environment variable), stop and ask the
person you're working with to create one at https://logsninja.com/en/my/tokens
and give it to you (for example as a LOGSNINJA_API_TOKEN environment
variable) before you continue.
## API base URL
https://api.logsninja.com/v1
## MCP server
If you are an AI agent with MCP support rather than a code-generation assistant, LogsNinja is also usable directly as an MCP server at https://logsninja.com/mcp (same Bearer token as above) – it exposes tools to manage charts and dashboard widgets, explore project data, and create/delete test events, users, and metrics, without writing HTTP calls yourself. See the full docs for the tool list.
## Integration guidelines
- Use the HTTP client and patterns already established in this project – do not introduce new dependencies.
- All LogsNinja calls must be fire-and-forget from the application's perspective: they must never block the main flow or surface errors to the end user.
- Apply a 3-second timeout to every HTTP call, if possible.
- If this project already has a job queue, background worker, or retry mechanism, use it for LogsNinja calls.
- If no such mechanism exists, wrap calls in a silent try/catch (or equivalent) so that a LogsNinja failure has zero impact on the application.
## What to instrument
Scan the codebase and identify meaningful moments to send events: key user actions, business transactions, errors, background jobs, and any other signal that would be useful to monitor. For each, choose a descriptive title and a fitting emoji. Once done, provide a summary of every integration point added.
## Presence
Use presence sparingly. Sending an event with a user field already updates that user's online status for 15 minutes, so a separate ping is redundant in those cases. Only call POST /v1/ping when you know the user is active but no event will be sent soon – for example on login, or during an idle session. Do not ping on every page view or API call.
Send an event
https://api.logsninja.com/v1/events
Headers
Authorization: Bearer YOUR_API_TOKEN
Content-Type: application/json
Request body
{
"project": "YOUR_PROJECT_ID",
"stream": "payments",
"title": "New subscription",
"content": "[email protected] subscribed to Pro",
"emoji": "💳",
"metadata": { "plan": "pro", "amount": 49 },
"notify": true
}
Examples
curl -X POST https://api.logsninja.com/v1/events \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"project": "YOUR_PROJECT_ID",
"stream": "payments",
"title": "New subscription",
"content": "[email protected] subscribed to Pro",
"notify": true
}'
await fetch('https://api.logsninja.com/v1/events', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json',
},
body: JSON.stringify({
project: 'YOUR_PROJECT_ID',
stream: 'payments',
title: 'New subscription',
content: '[email protected] subscribed to Pro',
notify: true,
}),
});
import requests
requests.post(
'https://api.logsninja.com/v1/events',
headers={
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json',
},
json={
'project': 'YOUR_PROJECT_ID',
'stream': 'payments',
'title': 'New subscription',
'content': '[email protected] subscribed to Pro',
'notify': True,
},
)
Event fields
| Field | Type | Required | Description |
|---|---|---|---|
project |
string | ✓ | The project ID. Find it in your project settings. |
stream |
string | ✓ | The stream this event belongs to. Created automatically on first use. |
title |
string | ✓ | Short title of the event. 100 characters max. |
content |
string | Additional details, 500 characters max. Supports Markdown: bold, italic, code, lists, and links. | |
emoji |
string | Emoji to visually identify the event. Defaults to 🔔 if omitted. Accepted formats: emoji character ("💳"), shortcode (":credit_card:"), or hex code ("1F4B3"). Also used as the push notification image. Browse available emojis. |
|
metadata |
object | Key/value metadata. See metadata format rules above. | |
user |
string | Your app user ID (the same id you pass to PUT /v1/users). The user does not need to exist yet: events are linked automatically when the user is created. Sending a real-time event with a user field also counts as presence: the user is considered online for 15 minutes after their last linked event or ping. Backdated events sent with timestamp do not affect online presence. |
|
images |
string[] | Images for this event. Requires a paid plan. Either an array of public HTTPS image URLs to fetch and store (duplicates within one event are deduplicated), or send the files themselves as multipart/form-data. Images are processed asynchronously and kept for 30 days. See Uploading images for the full contract, size limits, accepted formats, URL requirements and how to read images_status. |
|
notify |
boolean | Send a push notification to subscribed devices. Default: false. Cannot be true when timestamp is set. When notify is present, the response also includes notification_queued; false means the event was saved but the mobile notification queue was unavailable after retry. | |
timestamp |
integer | Unix timestamp (seconds) to backdate the event. Must be in the past. Cannot be combined with notify: true. Backdated events do not affect online presence. | |
before |
string | The event will be placed before the one whose ID is passed in this property, in an event chain. | |
after |
string | The event will be placed after the one whose ID is passed in this property, in an event chain. |
Uploading images
There are two ways to attach images to an event, both on paid plans and both processed the same way, asynchronously: pass an array of public HTTPS URLs in the JSON images field for LogsNinja to fetch and store, or send the files themselves in the same POST /v1/events request as multipart/form-data.
Binary upload (multipart/form-data)
The request has one event part and one or more file parts named image:
| Part | Description |
|---|---|
event |
A string containing the exact JSON body you would otherwise send as application/json: project, stream, title and any other field, with metadata as a real JSON object (not metadata[key] form fields). It may still include an images array of URLs, stored alongside the uploaded files. |
image |
One part per file, each sent as a file (with a filename) and carrying a Content-Type of image/jpeg, image/png or image/webp. Parts with any other name, and non-file image parts, are ignored. |
curl -X POST https://api.logsninja.com/v1/events \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-F 'event={"project":"YOUR_PROJECT_ID","stream":"payments","title":"New subscription","metadata":{"plan":"pro"},"notify":true};type=application/json' \
-F 'image=@./screenshot.png;type=image/png'
const form = new FormData();
form.append('event', JSON.stringify({
project: 'YOUR_PROJECT_ID',
stream: 'payments',
title: 'New subscription',
metadata: { plan: 'pro' },
notify: true,
}));
form.append('image', fileOrBlob, 'screenshot.png'); // Content-Type image/png
await fetch('https://api.logsninja.com/v1/events', {
method: 'POST',
headers: { 'Authorization': 'Bearer YOUR_API_TOKEN' }, // no Content-Type
body: form,
});
Synchronous errors
These are returned immediately in the POST response, before the event is created:
| Code | Cause |
|---|---|
400 | The event part is missing or is not a JSON object. |
403 | Your plan does not include images on events. |
411 | The request has no Content-Length header. |
413 | A file is over your plan's per-image size limit, or the whole request body is over your plan's maximum size. |
415 | An image part's Content-Type does not start with image/. A wrong image type (e.g. image/gif) passes this check but fails later as bad_format. |
422 | More images than your plan allows per event. |
See Rate limits & quotas for the per-plan image count, per-image size and maximum request body size.
Fetching from a URL
When you pass URLs instead of files, each one must meet all of these or the image is marked failed:
- HTTPS only, on port 443. No IP-literal hosts, no
localhost,.localor.internal. The URL must be at most 2048 characters. - The response must carry
Content-Type: image/jpeg,image/pngorimage/webp. A200that returnstext/html(an error page, a login wall) is a permanent failure – it is never retried. - The fetch times out after 15 seconds and follows at most 3 redirects.
- On
403,404,408,425,429,500,502,503or504, the fetch is retried for about a minute before the image is markedfailed. Every other status fails permanently on the first try.
Formats and processing
- Accepted formats: JPEG, PNG and WebP, detected from the file's magic bytes – not its extension or declared type.
- Each image is resized to fit your plan's maximum dimension and stored as both JPEG and WebP. Images are kept for 30 days.
- A backdated event (
timestamp) whose date is already outside the 30-day window skips image processing entirely; itsimages_statusisexpired.
Reading the result
The POST response includes images_status, but at that point it is always pending (or none / expired) because processing has not run yet. There is no webhook: read the event again later – with the MCP get_event tool or in the Studio event feed – to see the outcome.
images_status |
Meaning |
|---|---|
none | The event has no images. |
pending | Queued or in progress. |
ready | Every image was processed. |
partial | Some images are ready, some failed. |
failed | Every image failed. |
expired | The event predates the 30-day image window; nothing was uploaded. |
Each image also carries an error string and an error_code (too_large, bad_format or unreachable) when it failed.
Recommendation: for an event you emit right after generating an image (a render, a screenshot, an export), upload the file rather than a URL. A URL that is not yet reachable – or already deleted – when LogsNinja fetches it fails the image, and nothing tells you when that happens.
Event chains
The before and after fields link events into a chain. The event detail panel shows adjacent events and lets you view the full sequence.
Only references to events in the same project are resolved, and only when their timestamps are logically consistent. The after target (predecessor) must have an earlier timestamp, and the before target (successor) must have a later one.
Delete an event
https://api.logsninja.com/v1/events/:id?project=YOUR_PROJECT_ID
Permanently deletes an event.
Create / update a user
https://api.logsninja.com/v1/users
Creates or updates an app user. Calling with the same id replaces the record.
{
"project": "YOUR_PROJECT_ID",
"id": "user_123",
"country_code": "FR",
"display_as": "[email protected]",
"metadata": { "plan": "pro", "mrr": 49 }
}
| Field | Type | Required | Description |
|---|---|---|---|
project |
string | ✓ | The project ID. |
id |
string | ✓ | Your own stable identifier for this user (e.g. your database ID). This is the value you use in the user field when sending events. |
country_code |
string | ISO 3166-1 alpha-2 country code (e.g. FR, US). See Geolocation for how to obtain it. | |
display_as |
string | Name displayed on the site and mobile app for this user. If not set or null, the user's id is used instead. |
|
metadata |
object | Key/value metadata. See metadata format rules above. |
Examples
curl -X PUT https://api.logsninja.com/v1/users \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"project": "YOUR_PROJECT_ID",
"id": "user_123",
"country_code": "FR",
"metadata": { "plan": "pro" }
}'
await fetch('https://api.logsninja.com/v1/users', {
method: 'PUT',
headers: {
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json',
},
body: JSON.stringify({
project: 'YOUR_PROJECT_ID',
id: 'user_123',
country_code: 'FR',
metadata: { plan: 'pro' },
}),
});
import requests
requests.put(
'https://api.logsninja.com/v1/users',
headers={
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json',
},
json={
'project': 'YOUR_PROJECT_ID',
'id': 'user_123',
'country_code': 'FR',
'metadata': {'plan': 'pro'},
},
)
Geolocation
LogsNinja never resolves a user's country from their IP address – country_code is entirely caller-supplied.
- If your backend is proxied through Cloudflare (Workers/Pages, or a DNS record with the orange cloud on), read the
CF-IPCountryrequest header directly – it's already set on every incoming request. - The same idea applies behind most other CDNs/edge networks (e.g. Akamai, Bunny, Fastly, Amazon CloudFront) – they typically inject their own equivalent geo header. Check that provider's documentation for the exact header name and format.
- Otherwise, use a GeoIP library or service against the request's IP address (e.g. MaxMind GeoLite2, ipinfo.io, ip-api.com).
Pass the resulting code as country_code – it powers the country breakdown chart and world map widget.
Delete a user
https://api.logsninja.com/v1/users/:id?project=YOUR_PROJECT_ID
Deletes an app user. Their events remain but are no longer linked to the user.
Signal user presence
https://api.logsninja.com/v1/ping
Records that a user is currently online, without creating an event. A user is considered online if they have sent a ping or had a linked event in the last 15 minutes. If the user does not exist yet, the ping is silently ignored – create the user first via PUT /v1/users.
{
"project": "YOUR_PROJECT_ID",
"id": "user_123"
}
| Field | Type | Required | Description |
|---|---|---|---|
project |
string | ✓ | The project ID. |
id |
string | ✓ | Your app user ID (the same id you passed to PUT /v1/users). |
Create / update a metric
https://api.logsninja.com/v1/metrics
Creates or updates a metric tile. Calling with the same id replaces the value. For numeric types, you can also apply a delta instead of setting an absolute value.
{
"project": "YOUR_PROJECT_ID",
"id": "mrr",
"title": "MRR",
"emoji": "💰",
"value": 4900,
"value_type": "currency",
"currency": "EUR"
}
{
"project": "YOUR_PROJECT_ID",
"id": "active-users",
"title": "Active users",
"value": { "diff": 1 },
"value_type": "number"
}
| Field | Type | Required | Description |
|---|---|---|---|
project |
string | ✓ | The project ID. |
id |
string | ✓ | Stable identifier for this metric (e.g. mrr, active-users). |
title |
string | ✓ | Display label for the metric tile. |
value |
string | number | object | ✓ | The metric value. For numeric types, pass {"diff": N} to add or subtract N atomically (e.g. {"diff": -1}). Not available for value_type text. |
value_type |
string | ✓ | One of: number, text, currency, percent. |
currency |
string | ISO 4217 currency code (e.g. EUR, USD). Required when value_type is currency. |
|
emoji |
string | Emoji shown on the metric tile. Accepted formats: emoji character ("💰"), shortcode (":money-bag:"), or hex code ("1F4B0"). When omitted, ℹ️ is displayed by default without being stored on the metric. Browse available emojis. |
Examples
curl -X PUT https://api.logsninja.com/v1/metrics \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"project": "YOUR_PROJECT_ID",
"id": "mrr",
"title": "MRR",
"value": 4900,
"value_type": "currency",
"currency": "EUR"
}'
await fetch('https://api.logsninja.com/v1/metrics', {
method: 'PUT',
headers: {
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json',
},
body: JSON.stringify({
project: 'YOUR_PROJECT_ID',
id: 'mrr',
title: 'MRR',
value: 4900,
value_type: 'currency',
currency: 'EUR',
}),
});
import requests
requests.put(
'https://api.logsninja.com/v1/metrics',
headers={
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json',
},
json={
'project': 'YOUR_PROJECT_ID',
'id': 'mrr',
'title': 'MRR',
'value': 4900,
'value_type': 'currency',
'currency': 'EUR',
},
)
Delete a metric
https://api.logsninja.com/v1/metrics/:id?project=YOUR_PROJECT_ID
Deletes a metric tile from the dashboard.
Projects
List projects
https://api.logsninja.com/v1/org/projects
Returns projects in the organisation, paginated. A project-scoped token only receives its linked project. Accepts page (default 1) and limit (default 50, max 200) query params. Response includes page, limit, total and has_more.
Create a project
https://api.logsninja.com/v1/org/projects
Creating a project requires an organization-wide token. A project-scoped token cannot create projects.
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | ✓ | The project name. |
Tokens
List tokens
https://api.logsninja.com/v1/org/tokens
Returns API tokens in the organisation, paginated. A project-scoped token only receives tokens linked to the same project. Accepts page (default 1) and limit (default 50, max 200) query params. Response includes page, limit, total, has_more and each token's can_manage_tokens capability. Token values are never returned after creation.
Create a token
https://api.logsninja.com/v1/org/tokens
The calling credential must have can_manage_tokens=true. The token value is returned once and cannot be retrieved again. A project-scoped caller can only create another token for its own project; omitting project_id does not grant organization-wide access.
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | ✓ | The token name. |
project_id |
string | Restricts the token to a single project. If omitted, the token has access to all projects in the organisation. | |
can_manage_tokens |
boolean | Allows this token to create and delegate other tokens within its own organization or project boundary. Defaults to false. |
Device authorization
Lets a CLI tool or AI agent get an API token without a human copying and pasting one. The tool requests a short code and shows it to a human; a human with an existing LogsNinja account opens a link and approves the request from their own browser session; the tool then receives the token automatically. This never creates an account or skips sign-up – only an already-signed-in human can approve a request.
Start the flow
https://api.logsninja.com/v1/device/authorize
No authentication required. Returns a device_code, a short user_code to show the human, and a verification_uri to open in a browser.
{
"device_code": "a1b2c3...",
"user_code": "WXYZ-1234",
"verification_uri": "https://logsninja.com/en/my/tokens#devices",
"verification_uri_complete": "https://logsninja.com/en/my/tokens?user_code=WXYZ-1234#devices",
"expires_in": 600,
"interval": 5
}
Show the user_code (or the verification_uri_complete link) to the human and ask them to open it and approve the request.
Poll for the token
https://api.logsninja.com/v1/device/token
No authentication required. Poll with the device_code at the interval given above until the status is no longer pending.
| Field | Type | Required | Description |
|---|---|---|---|
device_code |
string | ✓ | The device_code returned by the authorize call. |
Response is {"status": "pending"}, {"status": "denied"}, {"status": "expired"}, or, once approved, {"status": "approved", "token": "..."}. The token is only ever returned once – store it immediately.
MCP server
Lets an AI agent (Claude, etc.) use LogsNinja directly from a conversation instead of writing HTTP calls.
https://logsninja.com/mcp
Point your MCP client at this URL, authenticated with the same API token as the rest of this API: Authorization: Bearer YOUR_API_TOKEN.
Claude Code
claude mcp add --transport http logsninja https://logsninja.com/mcp \
--header "Authorization: Bearer YOUR_API_TOKEN"
Claude Desktop
Claude Desktop's config file no longer accepts a remote server's url and headers directly – only stdio servers (command/args). Bridge to this HTTP endpoint with mcp-remote:
{
"mcpServers": {
"logsninja": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://logsninja.com/mcp",
"--header",
"Authorization: Bearer YOUR_API_TOKEN"
]
}
}
}
| Platform | Config file location |
|---|---|
| macOS | ~/Library/Application Support/Claude/claude_desktop_config.json |
| Windows | %APPDATA%\Claude\claude_desktop_config.json |
Cursor
Cursor supports a remote server's url and headers directly, no bridge needed:
{
"mcpServers": {
"logsninja": {
"url": "https://logsninja.com/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_TOKEN"
}
}
}
}
Config file: ~/.cursor/mcp.json (global) or .cursor/mcp.json (project)
Codex CLI and the ChatGPT app
Both share the same ~/.codex/config.toml – the ChatGPT app's Settings → Plugins → MCPs → Add Server form writes to it too. Edit it directly to use bearer_token_env_var, which reads the token from an environment variable instead of storing it in plain text:
[mcp_servers.logsninja]
url = "https://logsninja.com/mcp"
bearer_token_env_var = "LOGSNINJA_API_TOKEN"
Set the environment variable in your shell profile before launching Codex or the ChatGPT app, e.g. export LOGSNINJA_API_TOKEN=YOUR_API_TOKEN.
Config file: ~/.codex/config.toml
Tools
| Tool | Description |
|---|---|
list_projects, create_project |
List projects in the caller's scope. Creating a project requires an organization-wide token. |
list_charts, create_chart, update_chart, delete_chart |
Same fields and validation rules as the dashboard's chart editor. |
list_widgets, create_widget, update_widget, reorder_widgets, delete_widget |
Same masonry layout rules as the widgets REST endpoints. |
get_data_inventory |
Known stream names, event titles, and metadata keys – useful before building a chart. |
preview_chart_data |
Compute a chart's data without creating it. |
search_emojis |
Search emojis by name, shortcode, or keyword – useful for picking one for an event. |
get_documentation |
The full API documentation as Markdown, in your choice of language. |
list_events, get_event |
The last 100 events (optionally by stream), or one event's detail with its chronological neighbors. No pagination. |
list_users, get_user |
The last 100 users, or one user's detail by id. No pagination or search. |
list_streams, list_metrics |
Streams and custom metrics defined in a project. |
create_event, delete_event, create_or_update_user, delete_user, create_or_update_metric, delete_metric |
Same as the ingestion endpoints – meant for creating and cleaning up test data, not for sending real production events on a user's behalf. |
Most tools take a project_id argument, required unless the token is scoped to a single project – same rule as the REST endpoints below.
Charts
Create and manage the same charts you'd build by hand on your dashboard – every option below and every validation rule is identical, whether you use the dashboard's chart editor or the API. This is chart configuration only: it defines what a chart shows, not its computed values – there's no endpoint to fetch a chart's data points through this API.
Chart types
A line chart (line) plots one value per period as a connected line – best for watching a trend over time. A pie chart (pie) shows a single snapshot split into slices – best for a share or breakdown at a glance. A bar chart (bar) shows one bar per period; as soon as you add a breakdown (see "Group by" below), it's automatically drawn as stacked segments instead of one bar – there's no separate "stacked bar" type to pick. A funnel chart (funnel) is a different kind of question entirely: instead of a metric over time, it shows how many of the same users made it through an ordered sequence of steps.
Time range and time interval
Choose how far back the chart looks: the last 7 days (7d), the last 30 days (30d), or the last 90 days (90d). You can also size your own rolling window in hours, days, or months (duration, with periodDurationValue and periodDurationUnit, e.g. "the last 45 days"), or set a fixed start date that always runs up to today (custom, with periodDateFrom) – there's no field for a fixed end date, it always runs up to today.
The time interval (granularity) controls how that window is split into buckets: by hour, day, week, or month (hour, day, week, month). It doesn't apply to funnel charts: they still use the time range above to filter events, but show one bar per step instead of a series of buckets over time.
A few combinations only have 90 days of history available, regardless of how far back you set the window: a funnel, a pie chart with a breakdown, a breakdown by country/user/metadata, more than one breakdown at once, any calculation other than a plain count, or filtering by metadata. A plain count with no breakdown, or a breakdown by stream or event title only, can reach back further.
Filtering which events count
By default a chart includes every event (all). You can narrow it down to a single stream (stream, e.g. only "payments"), a single event title (title, e.g. only "Order placed"), or events matching a specific metadata value (metadata, e.g. only events where "plan" equals "pro").
| Goal | Configuration |
|---|---|
| Only count events on the "payments" stream | event_source_type: "stream", event_source_value: "payments" |
| Only count "Order placed" events, whatever stream they're on | event_source_type: "title", event_source_value: "Order placed" |
| Only count events where the "plan" metadata equals "pro" | event_source_type: "metadata", event_source_value: "plan=pro" |
Aggregation
This decides how the matching events in each time bucket turn into the one number that gets plotted: a simple count of events (count), a count of unique users (count_distinct), or a calculation over a numeric metadata value on your events – its total (sum), average (avg), minimum (min), maximum (max), or median (median). For any calculation other than a plain count, you also pick which metadata key holds that number via aggregation_field; events missing it, or where it isn't numeric, are skipped.
| Goal | Configuration |
|---|---|
| Events per day | aggregation: "count" |
| Unique users active per day | aggregation: "count_distinct" |
| Total revenue per day (an "amount" metadata key on your events) | aggregation: "sum", aggregation_field: "amount" |
| Average order value per day (an "amount" metadata key on your events) | aggregation: "avg", aggregation_field: "amount" |
| Smallest order per day (an "amount" metadata key on your events) | aggregation: "min", aggregation_field: "amount" |
| Largest order per day (an "amount" metadata key on your events) | aggregation: "max", aggregation_field: "amount" |
| Median session duration per day (a "duration_seconds" metadata key on your events) | aggregation: "median", aggregation_field: "duration_seconds" |
Group by
By default a chart shows a single aggregate value per period. Grouping (group_by) splits it into several series or segments instead – one per distinct value of whatever you group by – drawn as separate lines, stacked segments, or pie slices depending on the chart type.
You can group by stream (stream), event title (title), country (country), or user (user) with nothing else to configure, or by a metadata key of your choosing (metadata, with aggregation_field holding the key to split by).
| Goal | Configuration |
|---|---|
| Events per day, one line per stream | type: line, aggregation: count, group_by: ["stream"] |
| Which event titles are most common each day | type: bar, aggregation: count, group_by: ["title"] |
| Signups per day, split by country | type: bar, aggregation: count_distinct, group_by: ["country"] |
| Compare activity across a specific handful of users (your own team, a few VIP accounts) – one series per matching user, so this only reads well for a small, deliberately narrow set | type: line, aggregation: count, group_by: ["user"] |
| Event volume per day, one segment per plan (a "plan" metadata key on your events, e.g. free/pro/enterprise) | type: bar, aggregation: count, group_by: ["metadata"], aggregation_field: "plan" |
| Share of events per plan, as a pie | type: pie, aggregation: count, group_by: ["metadata"], aggregation_field: "plan" |
One constraint worth knowing: the metadata key you group by and the metadata key you aggregate are the same setting (aggregation_field), since a chart only has one of it. So "total order amount per day, split by plan" isn't possible as a single chart – you'd need to either total the amount with no breakdown, or count orders broken down by plan, not both on the same chart.
Funnel charts
A funnel (funnel) asks a different question entirely: not "how many events" but "how many of the same users made it from step 1, to step 2, to step 3". You give it an ordered list of at least two event titles via funnel_steps; the chart shows one bar per step, each counting only the users who also completed every earlier step in order. The time range above still applies, but granularity, aggregation, and grouping don't.
| Goal | Configuration |
|---|---|
| Signup conversion: how many viewed pricing, started checkout, then completed it | type: "funnel", funnel_steps: ["Pricing viewed", "Checkout started", "Subscription created"] |
Display options
You can show or hide the legend (show_legend), and choose your own colors (colors, an array of hex strings applied in order) for each series or segment instead of the default palette. You can also switch to a running total (cumulative) instead of a per-period value – only meaningful for a line or bar chart adding up a count or a sum, since a running total of an average, a distinct-user count, or a pie/funnel doesn't mean anything.
List charts
https://api.logsninja.com/v1/org/charts
Accepts a project_id query param, required unless the token is already scoped to a single project.
Create a chart
https://api.logsninja.com/v1/org/charts
Update a chart
https://api.logsninja.com/v1/org/charts/{id}
Only the fields you send are changed.
Delete a chart
https://api.logsninja.com/v1/org/charts/{id}
| Field | Type | Required | Description |
|---|---|---|---|
project_id |
string | ✓ | Required unless the token is scoped to a single project. |
name |
string | ✓ | Chart name. |
type |
string | ✓ | line, bar, pie, funnel. There's no separate "stacked bar" type: a bar chart with group_by set is still type bar, automatically rendered as stacked segments. |
period |
string | ✓ | 7d, 30d, 90d, duration, custom |
period_date_from |
string | Required when period is custom, format YYYY-MM-DD. | |
period_duration_value / period_duration_unit |
integer / string | Required when period is duration. Unit is hour, day, or month. | |
granularity |
string | ✓ | hour, day, week, month |
aggregation |
string | ✓ | count, count_distinct, sum, avg, min, max, median |
aggregation_field |
string | Metadata field name, required for sum/avg/min/max/median and when group_by includes metadata. | |
group_by |
array | Any of stream, title, country, user, metadata. | |
event_source_type |
string | ✓ | all, stream, title, metadata |
event_source_value |
string | Required unless event_source_type is all. | |
funnel_steps |
array | At least two ordered step names, required when type is funnel. | |
show_legend / cumulative |
boolean | cumulative is only honored for line/bar charts with count or sum aggregation. | |
colors |
array | Series colors as hex strings. |
Historical data beyond the retention window isn't available for combinations that scan raw events (funnel, pie with a group-by, group-by country/user/metadata, or any non-count aggregation).
Widgets
Manage which tiles appear on the dashboard and how they're arranged – this is layout configuration only, not the data shown inside a tile.
The dashboard is a masonry layout, not a rigid grid. "Small" widgets (metric, online_users) are meant to take up a third or half of the row (width 1 or 2) and render at exactly half the visual height of "large" widgets (chart, world_map, events_7d, top_countries), which are meant to take up half the row or the full row (width 2 or 3). Keep this in mind when arranging a dashboard via the API.
List widgets
https://api.logsninja.com/v1/org/widgets
Accepts a project_id query param, required unless the token is already scoped to a single project.
Add a widget
https://api.logsninja.com/v1/org/widgets
| Field | Type | Required | Description |
|---|---|---|---|
project_id |
string | ✓ | Required unless the token is scoped to a single project. |
type |
string | ✓ | metric, chart, events_7d, world_map, top_countries, online_users. Only one of each native type (events_7d/world_map/top_countries/online_users) may exist per dashboard. |
metric_id |
string | Required when type is metric. Must be an existing metric id. | |
chart_id |
string | Required when type is chart. Must be a chart on the same project. | |
width |
integer | 1 (1/3), 2 (1/2, default), or 3 (full row). |
Resize or move a widget
https://api.logsninja.com/v1/org/widgets/{id}
Send width and/or position (0-based index among the project's widgets). Setting a position moves the widget there and shifts every other widget after it down by one – it doesn't swap two widgets. Only the fields you send are changed.
Reorder the whole dashboard
https://api.logsninja.com/v1/org/widgets/reorder
Send order as the full, ordered list of every widget id on the project (a partial list is rejected), and optionally widths as a map of id to width.
Remove a widget
https://api.logsninja.com/v1/org/widgets/{id}
Response codes
| Endpoint | Success | Body |
|---|---|---|
POST /v1/events |
201 | { "id": "...", "image_count": 0, "images_status": "none", "notification_queued": true } |
DELETE /v1/events/:id |
200 | { "ok": true } |
PUT /v1/users |
200 | { "id": "..." } |
DELETE /v1/users/:id |
200 | { "ok": true } |
POST /v1/ping |
200 | { "ok": true } |
PUT /v1/metrics |
200 | { "id": "..." } |
DELETE /v1/metrics/:id |
200 | { "ok": true } |
Error codes: 400 validation error, 401 missing or invalid token, 403 token restricted to another project, 404 project not found.
Emoji Reference
1837 emojis supported
smileys & emotion
smiling
affectionate
with tongue
with hands
neutral / skeptical
sleepy
unwell
with hats
with glasses
concerned
negative
costumed & creatures
cat faces
monkey faces
hearts
emotions
people & body
fingers open
hand signs
finger pointing
fingers closed
hands
hand props
body parts
people
gestures
roles & careers
fantasy
activities
athletics
resting
family
people symbols
animals & nature
mammals
birds
amphibians
reptiles
marine life
bugs
flowers
other plants
food & drink
fruit
vegetables
cooked / prepared
asian
sweets & candy
drink
dishware
travel & places
globes & maps
geographic locations
buildings
religious buildings
other places
ground transportation
water transportation
air transportation
hotel
time
weather
activities
events & holidays
award medals
sports
games & hobbies
arts & crafts
objects
clothing
sound
music
musical instruments
phone
computer
light, film & video
books & paper
money
writing
office supplies
lock & keys
tools
science equipment
medical
household items
other objects
symbols
transport signs
warning symbols
arrows
religious symbols
zodiac signs
audio & video symbols
gender signs
math symbols
punctuation
currencies
other symbols
keypad characters
alphanumeric symbols
shapes & colors
flags
other flags
country flags
