API Endpoints

Authentication

POST /api/v1/account/register

Register a public key. Returns the key's fingerprint.

Request:

{
  "public_key": "<Ed25519 SSH public key in authorized_keys format>"
}

Response (201):

{
  "fingerprint": "SHA256:..."
}

Idempotent — registering the same key returns the existing fingerprint with status 200.

Max body: 8 KB.

POST /api/v1/account/challenge

Request a challenge nonce for authentication.

Request:

{
  "fingerprint": "SHA256:..."
}

Response (200):

{
  "challenge": "<base64-encoded nonce>",
  "expires_at": "2026-02-25T12:00:00Z"
}

The nonce is valid for 5 minutes.

POST /api/v1/account/verify

Verify a signed challenge and receive a JWT.

Request:

{
  "fingerprint": "SHA256:...",
  "signature": "<base64-encoded Ed25519 signature>"
}

The signature must be over the message rockstar.ninja.v1.auth:{nonce}:{fingerprint}.

Response (200):

{
  "token": "<JWT>",
  "expires_at": "2026-02-26T12:00:00Z"
}

Account Management

All account management endpoints require authentication.

GET /api/v1/account/me

Get current user info.

Response (200):

{
  "fingerprint": "SHA256:...",
  "username": "myname",
  "profile_url": "https://rockstar.ninja/@myname"
}

username and profile_url are omitted if no username is set.

POST /api/v1/account/username

Set or change your username.

Request:

{
  "username": "myname"
}

Response (200):

{
  "username": "myname",
  "profile_url": "https://rockstar.ninja/@myname",
  "warning": "next change available after 2027-02-25"
}

Returns 409 if the username is taken, 429 if you're in the cooldown period.

DELETE /api/v1/account/username

Release your username. Returns 204.

DELETE /api/v1/account/me

Permanently destroy your account. Requires confirmation.

Request:

{
  "confirm": "DESTROY"
}

Returns 204. Deletes all sessions, plans, username, and key registration.

Data Export

All data export endpoints require authentication.

POST /api/v1/account/export

Request a new data export. Replaces any existing export for the user. The archive is built asynchronously in the background.

Request:

{
  "format": "zip"
}

format is optional (default: zip).

Response (202):

{
  "token": "...",
  "status": "pending",
  "format": "zip",
  "requested_at": "2026-03-12T12:00:00Z",
  "expires_at": "2026-03-14T12:00:00Z"
}

Only one export per user at a time. Exports expire after 48 hours.

GET /api/v1/account/export

Get the current export status.

Response (200):

{
  "token": "...",
  "status": "ready",
  "format": "zip",
  "file_size": 123456,
  "session_count": 10,
  "plan_count": 5,
  "requested_at": "2026-03-12T12:00:00Z",
  "completed_at": "2026-03-12T12:01:00Z",
  "expires_at": "2026-03-14T12:00:00Z",
  "download_url": "https://rockstar.ninja/api/v1/account/export/download/{token}"
}

Status values: pending, building, ready, failed. file_size, session_count, plan_count, completed_at, and download_url are only present when status is ready. Returns 404 if no export exists.

DELETE /api/v1/account/export

Delete the current export (file and metadata). Returns 204. Returns 404 if no export exists.

GET /api/v1/account/export/download/{token}

Download the export archive. No authentication required — the token in the URL acts as a capability. Returns the ZIP file with Content-Type: application/zip. Returns 404 if the token is invalid, 410 if expired.

Sessions

POST /api/v1/sessions

Push a session. Returns a streaming NDJSON response.

Request:

{
  "session_id": "<local UUID>",
  "provider": "claude",
  "title": "optional title",
  "data": "<raw JSONL session content>",
  "privacy": "public",
  "expires_at": "7d"
}

Max body: 100 MB. provider defaults to claude. privacy defaults to secret (valid values: public, secret, private). expires_at accepts durations (24h, 7d) or ISO 8601 dates.

If the session contains plan file writes (~/.claude/plans/*.md), the streaming done event includes a plans array with the created or updated plans. See Streaming for details.

Streaming upload

A session transcript can be larger than the 100 MB body limit — and larger than the server can hold in memory at once. For those, send the same request as Content-Type: application/x-ndjson instead: the metadata object goes on the first line, with data omitted, and the session records follow it one per line.

POST /api/v1/sessions
Content-Type: application/x-ndjson
Content-Encoding: gzip

{"session_id":"<local UUID>","provider":"claude","title":"optional title"}
{"type":"user","uuid":"...","message":{...}}
{"type":"assistant","uuid":"...","message":{...}}

Content-Encoding: gzip is optional but recommended: the limit that matters is measured on the compressed bytes as they arrive, so compressing the upload is what makes a transcript well past 100 MB pushable. The server never buffers the records — it hashes, compresses and stores them as they stream past — so the practical ceiling is the compressed request size, not the transcript size.

The response is the same NDJSON event stream, and the stored result is identical to the JSON framing. Empty lines are dropped and every record is stored newline terminated, so the size the server reports may differ slightly from the file on disk. rn uses this framing for every session push.

Errors before the stream starts are ordinary JSON responses: 413 if the body exceeds the limit, 415 for a Content-Encoding other than gzip, 400 if the metadata line is not a JSON object.

GET /api/v1/sessions

List your pushed sessions.

Response (200):

{
  "sessions": [
    {
      "nanoid": "abc123xyz456",
      "session_id": "<local UUID>",
      "provider": "claude",
      "slug": "auto-generated-slug",
      "title": "my session title",
      "privacy": "secret",
      "expires_at": "2026-03-04T12:00:00Z",
      "versions": 3,
      "url": "https://rockstar.ninja/s/abc123xyz456",
      "created_at": "2026-02-25 12:00:00",
      "updated_at": "2026-02-25 14:30:00"
    }
  ]
}

GET /api/v1/sessions/resolve?q={query}

Resolve an identifier to a nanoid. Resolution priority: exact nanoid, exact session_id, exact slug, session_id prefix.

Response (200):

{
  "nanoid": "abc123xyz456",
  "matched_by": "session_id"
}

Returns 409 with a matches array if the identifier is ambiguous (for example when the same session_id exists under different providers). Each match includes provider.

DELETE /api/v1/sessions/{nanoid}

Delete a session and all its versions. Returns 204.

DELETE /api/v1/sessions/{nanoid}/versions/{version}

Delete a specific version. Returns 204.

PATCH /api/v1/sessions/{nanoid}

Update session metadata. All fields are optional.

Request:

{
  "title": "new title",
  "privacy": "public",
  "expires_at": "30d"
}

Max body: 64 KB.

Plans

POST /api/v1/plans

Push a plan. Returns a streaming NDJSON response.

Request:

{
  "name": "my-plan.md",
  "title": "optional title",
  "content": "<markdown content>",
  "privacy": "public",
  "expires_at": "30d"
}

Max body: 10 MB. The name must end with .md. If the content hasn't changed since the last push, no new version is created.

GET /api/v1/plans

List your pushed plans.

Response (200):

{
  "plans": [
    {
      "nanoid": "xyz789abc012",
      "name": "my-plan.md",
      "title": "My Plan",
      "privacy": "public",
      "expires_at": null,
      "versions": 2,
      "url": "https://rockstar.ninja/p/xyz789abc012",
      "created_at": "2026-02-25 12:00:00",
      "updated_at": "2026-02-25 14:30:00"
    }
  ]
}

GET /api/v1/plans/resolve?q={query}

Resolve an identifier to a nanoid. Matches exact nanoid or plan name (with or without .md).

DELETE /api/v1/plans/{nanoid}

Delete a plan and all its versions. Returns 204.

PATCH /api/v1/plans/{nanoid}

Update plan metadata. Same fields as session PATCH. Max body: 64 KB.

POST /api/v1/account/web-login

Generate a one-time login code for web browser sessions. Requires Bearer JWT auth (CLI or macOS app). The code can be exchanged at GET /login?code=<code> to create a web session cookie.

Response (200):

{
  "code": "<one-time login code>"
}

The CLI's rn login command calls this endpoint and opens the browser to /login?code=<code> automatically. Login codes expire after 5 minutes.

Skills

POST /api/v1/skills

Push a skill. Returns a streaming NDJSON response.

Request:

{
  "name": "my-skill",
  "title": "optional title",
  "description": "skill description",
  "argument_hint": "optional argument hint",
  "content": "<SKILL.md markdown content>",
  "files": [
    {"path": "helper.py", "content": "<base64-encoded file content>"}
  ],
  "privacy": "public",
  "expires_at": "30d",
  "project_label": "my-project"
}

Max body: 50 MB. files contains all skill files with base64-encoded content. If the SKILL.md content hasn't changed since the last push, no new version is created — metadata updates (title, description, privacy, expiry) are applied without incrementing the version. Add ?force=1 to create a new version regardless.

GET /api/v1/skills

List your pushed skills. Requires authentication.

Response (200):

{
  "skills": [
    {
      "nanoid": "abc123xyz456",
      "name": "my-skill",
      "title": "My Skill",
      "description": "Does something useful",
      "argument_hint": "<filename>",
      "privacy": "public",
      "expires_at": null,
      "versions": 3,
      "file_count": 4,
      "total_size": 12345,
      "url": "https://rockstar.ninja/skill/abc123xyz456",
      "created_at": "2026-03-01 12:00:00",
      "updated_at": "2026-03-15 14:30:00",
      "project_nanoid": "pRj_Xm9kQ2w1",
      "project_label": "my-project"
    }
  ]
}

GET /api/v1/skills/resolve?name={name}

Resolve a skill name to a nanoid. Requires authentication (own skills only).

Response (200):

{
  "nanoid": "abc123xyz456",
  "matched_by": "name"
}

Returns 404 if not found.

GET /api/v1/skills/lookup

Public lookup of a skill by owner, name, and optional project. No authentication required — only returns public skills.

Query parameters:

Parameter Type Required Description
owner string yes Username of the skill owner
name string yes Skill name
project string no Project label (for disambiguation)

Response (200):

{
  "nanoid": "abc123xyz456",
  "matched_by": "lookup"
}

Returns 404 if not found or not public.

PATCH /api/v1/skills/{nanoid}

Update skill metadata. All fields are optional. Requires authentication as the owner.

Request:

{
  "title": "new title",
  "description": "new description",
  "argument_hint": "new hint",
  "privacy": "public",
  "expires_at": "30d"
}

Max body: 64 KB.

DELETE /api/v1/skills/{nanoid}

Delete a skill and all its versions and files. Returns 204.

GET /api/v1/skills/{nanoid}/download

Download a skill as a tar.gz archive. Optional authentication (public/secret accessible to anyone, private requires owner auth).

Query parameters:

Parameter Type Required Description
version int no Specific version to download (default: latest)

Response: application/gzip with Content-Disposition header.

GET /api/v1/skills/{nanoid}/files

List files in the latest version of a skill. Optional authentication.

Response (200):

[
  {"path": "SKILL.md", "size": 1234, "sha256": "abc..."},
  {"path": "helper.py", "size": 567, "sha256": "def..."}
]

GET /api/v1/skills/{nanoid}/files/{path}

Get the raw content of a single file. Optional authentication. Append ?html=1 for syntax-highlighted HTML output.

Projects

POST /api/v1/projects

Create a new project.

Request:

{
  "label": "my-project",
  "privacy": "secret",
  "description": "optional description"
}

Response (201):

{
  "nanoid": "pRj_Xm9kQ2w1",
  "label": "my-project",
  "privacy": "secret",
  "description": "",
  "url": "https://rockstar.ninja/proj/pRj_Xm9kQ2w1"
}

GET /api/v1/projects

List your projects.

Response (200):

{
  "projects": [
    {
      "nanoid": "pRj_Xm9kQ2w1",
      "label": "my-project",
      "privacy": "secret",
      "description": "",
      "sessions": 5,
      "plans": 2,
      "url": "https://rockstar.ninja/proj/pRj_Xm9kQ2w1",
      "created_at": "2026-02-25 12:00:00",
      "updated_at": "2026-03-01 14:30:00"
    }
  ]
}

GET /api/v1/projects/{nanoid}

Get a specific project.

PATCH /api/v1/projects/{nanoid}

Update project metadata (label, privacy, description). All fields are optional. Max body: 64 KB.

DELETE /api/v1/projects/{nanoid}

Delete a project. Sessions, plans, and skills within the project are kept but unlinked. Returns 204.

POST /api/v1/projects/{nanoid}/plans

Add an existing plan to a project.

Request:

{
  "plan_nanoid": "xyz789abc012"
}

DELETE /api/v1/projects/{nanoid}/plans/{planNanoid}

Remove a plan from a project (does not delete the plan).

GET /api/v1/projects/resolve?q={query}

Resolve a project identifier (nanoid or label) to a nanoid.

Note: Sessions, plans, and skills are automatically associated with projects when pushed. The project_label field in push requests triggers auto-creation of projects. Plans can also be manually added to projects via the endpoints above.

Search

GET /api/v1/search

Search across your sessions, plans, and skills. Requires authentication.

Query parameters:

Parameter Type Required Default Description
q string yes Search query
type string no Filter: session, plan, or skill
scope string no Content scope: user, assistant, agent, tool
project string no Filter by project nanoid
limit int no 20 Max results (1-100)
offset int no 0 Pagination offset

Response (200):

{
  "results": [
    {
      "type": "session",
      "nanoid": "abc123xyz456",
      "title": "fixing the auth bug",
      "snippet": "...updated the <mark>auth middleware</mark> to check...",
      "provider": "claude",
      "git_branch": "fix-auth",
      "updated_at": "2026-03-15 12:00:00"
    }
  ],
  "total": 42,
  "query": "auth middleware"
}

The snippet field contains HTML <mark> tags around matching terms. provider and git_branch are omitted when empty.

Rendering

Render endpoints accept optional authentication via Bearer token or web session cookie (rn_session). Public and secret content is accessible to anyone. Private content returns 404 unless the request is authenticated as the owner.

Endpoint Description
GET /s/{nanoid} Render session (latest version)
GET /s/{nanoid}/{version} Render session (specific version)
GET /s/{nanoid}/og.png Session social preview image
GET /p/{nanoid} Render plan (latest version)
GET /p/{nanoid}/{version} Render plan (specific version)
GET /p/{nanoid}/history Plan version history
GET /p/{nanoid}/diff/{v1}..{v2} Diff between two plan versions
GET /p/{nanoid}/{version}/{file} Raw plan markdown
GET /p/{nanoid}/og.png Plan social preview image
GET /skill/{nanoid} Render skill (latest version)
GET /skill/{nanoid}/{version} Render skill (specific version)
GET /skill/{nanoid}/history Skill version history
GET /skill/{nanoid}/diff/{v1}..{v2} Diff between two skill versions
GET /skill/{nanoid}/og.png Skill social preview image
GET /proj/{nanoid} Render project page
GET /proj/{nanoid}/og.png Project social preview image
GET /@{username} User profile page
GET /@{username}/og.png Profile social preview image
GET /@{username}/feed.xml Profile Atom feed
GET /dashboard Authenticated user dashboard with search
GET /login Login page; with ?code= exchanges code for session cookie
POST /logout Clear web session cookie