SuperAPI AI Directory

secondme-reference

Agents & models

View repository

Updated 10-16 01:16

About this skill

SecondMe API Technical Reference

This document contains complete technical reference information for the SecondMe API, for consultation during development.


API Base URL

https://api.mindverse.com/gate/lab

OAuth2 Authorization URL

https://go.second.me/oauth/

OAuth2 Flow

1. 用户点击登录 → 跳转到 SecondMe 授权页面
2. 用户授权 → 重定向回你的应用(带 authorization_code)
3. 后端用 code 换取 access_token 和 refresh_token
4. 使用 access_token 调用 SecondMe API
5. Token 过期时使用 refresh_token 刷新

Authorization URL Construction

Important:oauth_url already includes the complete path, just append ? and query parameters directly after it, do not append /authorize or other paths.

const OAUTH_URL = 'https://go.second.me/oauth/';

const params = new URLSearchParams({
  client_id: process.env.SECONDME_CLIENT_ID,
  redirect_uri: process.env.SECONDME_REDIRECT_URI,
  response_type: 'code',
  state: generatedState,
});

// ✅ 正确:直接拼接 ? 和参数
const authUrl = `${OAUTH_URL}?${params.toString()}`;
// 结果: https://go.second.me/oauth/?client_id=...&redirect_uri=...

// ❌ 错误:不要追加 /authorize 等路径
// const authUrl = `${OAUTH_URL}/authorize?${params}`;
// 会变成: https://go.second.me/oauth//authorize?... ❌

Token Exchange (exchange authorization code for Token)

Endpoint

POST {base_url}/api/oauth/token/code

Request Format

Content-Type must be application/x-www-form-urlencoded, not JSON.

const response = await fetch(`${API_BASE_URL}/api/oauth/token/code`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',  // 必须
  },
  body: new URLSearchParams({
    grant_type: 'authorization_code',
    code: authorizationCode,
    redirect_uri: process.env.SECONDME_REDIRECT_URI,
    client_id: process.env.SECONDME_CLIENT_ID,
    client_secret: process.env.SECONDME_CLIENT_SECRET,
  }),
});

Response Format

The response follows a unified wrapper format, with fields using camelCase (not the OAuth2 standard snake_case):

{
  "code": 0,
  "data": {
    "accessToken": "lba_at_xxxxx...",
    "refreshToken": "lba_rt_xxxxx...",
    "tokenType": "Bearer",
    "expiresIn": 7200,
    "scope": ["user.info", "chat"]
  }
}

Response Handling

const result = await response.json();

// 必须检查 code 字段
if (result.code !== 0 || !result.data) {
  throw new Error(`Token exchange failed: ${result.message}`);
}

// 从 data 中提取,使用 camelCase
const { accessToken, refreshToken, expiresIn } = result.data;

Token Refresh

Endpoint

POST {base_url}/api/oauth/token/refresh

Request Format

const response = await fetch(`${API_BASE_URL}/api/oauth/token/refresh`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
  },
  body: new URLSearchParams({
    grant_type: 'refresh_token',
    refresh_token: storedRefreshToken,
    client_id: process.env.SECONDME_CLIENT_ID,
    client_secret: process.env.SECONDME_CLIENT_SECRET,
  }),
});

The response format is the same as Token Exchange.


Token Validity Period

Token Type Prefix Validity Period
Authorization Code lba_ac_ 5 minutes
Access Token lba_at_ 2 hours
Refresh Token lba_rt_ 30 days

MCP Bearer Token and User Resolution

If SecondMe calls your MCP in user mode, the platform will pass the current user's access token in the Authorization header of the MCP request:

Authorization: Bearer <accessToken>

This token is not a static application key, but the identity of the current SecondMe user triggering the MCP.

Recommended implementation approach reference SemeCompat:

  1. The MCP server or API route reads Authorization header
  2. Parse out the bearer token, and if missing or malformed, directly return 401
  3. Use this token to call the upstream user/info interface to identify the current SecondMe user
  4. Use the upstream user id to map to the local users.oauth_id or equivalent external id
  5. If not found, automatically upsert the local user
  6. Use the resolved local user.id to execute business logic

Recommended Layering

  • App API layer: responsible for bearer token authentication, resolving the SecondMe user, mapping the local user, and validating resource ownership
  • MCP server / transport layer: responsible for exposing MCP tools and passing the Authorization header through to the in-site API as-is
  • Business service layer: only receives the resolved local user id, and does not directly handle the bearer token

Bearer Token Reading Example

function readBearerToken(request: Request): string | null {
  const header = request.headers.get('authorization');
  if (!header?.startsWith('Bearer ')) {
    return null;
  }
  return header.slice('Bearer '.length).trim() || null;
}

Local User Resolution Flow Example

export async function requireApiAuth(request: Request): Promise<AuthUser> {
  const accessToken = readBearerToken(request);
  if (!accessToken) {
    throw new Error('UNAUTHORIZED');
  }

  const userInfo = await getUserInfo(accessToken);
  const user = await upsertUserByOauthProfile({
    oauthId: userInfo.id,
    nickname: userInfo.nickname,
    avatar: userInfo.avatar || null,
    accessToken,
  });

  return toAuthUser(user);
}

MCP Server Pass-through Example

const authorization = request.headers.authorization || null;
await authStorage.run({ authorization }, async () => {
  await transport.handleRequest(request, response, parsedBody);
});

If the MCP server then calls the in-site HTTP API, continue to pass this header through:

await fetch(`${baseUrl}/api/mcp/compat/random`, {
  method: 'POST',
  headers: {
    Authorization: accessToken,
    Accept: 'application/json',
  },
});

Common Implementation Mistakes

  • Using the bearer token as a global API key without resolving the current user
  • Directly accessing the database in the MCP server, bypassing the application's existing authentication and resource ownership checks
  • Not mapping the upstream user to a local user, causing the business layer to be unable to obtain a stable local user id
  • Returning 500when the token is invalid, instead of an explicit 401
  • Not performing user ownership validation when querying others' resources

Recommended Error Codes

Scenario Recommended Status Code
token missing or invalid 401
resource does not belong to the current user 403
target resource does not exist 404
Invalid input 400
Other unhandled exception 500

Recommended test items

  • Reject request when bearer token is missing
  • Map to when bearer token is invalid 401
  • Existing upstream users can parse and sync the latest access token
  • New upstream users can be automatically upserted into the local user table
  • When the MCP server calls the in-site API, it passes through as-is Authorization
  • Return when resource ownership is incorrect 403

Permission list (Scopes)

Permission Description
user.info Basic user information
user.info.shades User interest tags
user.info.softmemory User soft memory
note.add Add note
chat Chat feature
chat Structured action judgment (Act)

API response format and handling

Important: All SecondMe API responses follow a unified format:

{
  "code": 0,
  "data": { ... }  // 实际数据在 data 字段内
}

Frontend code must correctly extract data:

// 注意:以下 /api/secondme/... 是 Next.js 本地路由(由 secondme-nextjs skill 生成),
// 本地路由会代理请求到上游 SecondMe API,并透传上游的响应格式。

// ❌ 错误写法 - 直接使用响应会导致 .map is not a function
const response = await fetch('/api/secondme/user/shades');  // Next.js 本地路由
const shades = await response.json();
shades.map(item => ...)  // 错误!

// ✅ 正确写法 - 提取 data 字段内的数据
const response = await fetch('/api/secondme/user/shades');  // Next.js 本地路由
const result = await response.json();
if (result.code === 0) {
  const shades = result.data.shades;  // 正确!
  shades.map(item => ...)
}

Data paths for each API

The following paths are all upstream SecondMe API paths, full URL = {base_url}/api/secondme{path} where base_url comes from state.api.base_url(default https://api.mindverse.com/gate/lab)

Upstream API path Data path Type
/api/secondme/user/info result.data object (including fields such as email, name, avatarUrl, route)
/api/secondme/user/shades result.data.shades array
/api/secondme/user/softmemory result.data.list array
/api/secondme/chat/session/list result.data.sessions array
/api/secondme/chat/session/messages result.data.messages array
/api/secondme/act/stream SSE streaming JSON (delta must be concatenated) SSE stream
/api/secondme/note/add result.data.noteId number
/api/secondme/agent_memory/ingest result.data.eventId / result.data.isDuplicate object

Act API (structured action judgment)

The Act API is an interface independent of the Chat API. It constrains the model to output only valid JSON objects, making it suitable for structured decision-making scenarios such as sentiment analysis and intent classification. Permission usage chat scope。

Endpoint (upstream API)

POST {base_url}/api/secondme/act/stream

Request parameters

Parameter Type Required Description
message string Yes User message content
actionControl string Yes Action control description (20-8000 characters), defines the JSON structure and judgment rules
appId string No Application ID
sessionId string No Session ID; if not provided, it is generated automatically
systemPrompt string No System prompt; only effective on the first use of a new session

actionControl example

仅输出合法 JSON 对象,不要解释。
输出结构:{"is_liked": boolean}。
当用户明确表达喜欢或支持时 is_liked=true,否则 is_liked=false。

Response format (SSE)

event: session
data: {"sessionId": "labs_sess_xxx"}

data: {"choices": [{"delta": {"content": "{\"is_liked\": true}"}}]}

data: [DONE]

Frontend handling example

// 调用 Act API 进行结构化判断(通过 Next.js 本地路由代理到上游)
const response = await fetch('/api/secondme/act/stream', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    message: userMessage,
    actionControl: '仅输出合法 JSON。结构:{"intent": "like"|"dislike"|"neutral"}。根据用户表达判断意图。信息不足时返回 {"intent": "neutral"}。'
  })
});

// 拼接 SSE 流中的 delta content,最终 JSON.parse 得到结果

Chat vs Act usage scenarios

Scenario API to use Reason
Free-form conversation /chat/stream Returns natural language text
Sentiment/intent judgment /act/stream Returns structured JSON
Yes/No decision /act/stream Returns {"result": boolean}
Multi-class classification /act/stream Returns {"category": "..."}
Content generation /chat/stream Requires long text output

Agent Memory API (event reporting and querying)

The Agent Memory API is used to report user behavior events on external platforms to the Agent Memory Ledger, enriching the AI avatar's memory. The authentication method is the same as other SecondMe APIs (OAuth2 Token),no specific scope is required。

Reporting endpoint

POST {base_url}/api/secondme/agent_memory/ingest

Request parameters

Parameter Type Required Description
channel ChannelInfo Yes Channel information
action string Yes Action type
refs RefItem[] Yes Evidence pointer array (at least 1 item)
actionLabel string No Action display text
displayText string No User-readable summary
eventDesc string No Developer description
eventTime integer No Event timestamp (milliseconds)
importance number No Importance 0.0~1.0
idempotencyKey string No Idempotency key
payload object No Extended information

Nested type

ChannelInfo: { kind: string, id?: string, url?: string, meta?: object }

platform Automatically populated by the server based on the application Client ID; the frontend does not need to pass it in.

RefItem: { objectType: string, objectId: string, type?: string, url?: string, contentPreview?: string, snapshot?: RefSnapshot }

RefSnapshot: { text: string, capturedAt?: number, hash?: string }

Idempotency key generation rules

The frontend should generate an idempotency key to prevent duplicate reporting (refer to the plaza frontend implementation):

// 规则: sha256("external:" + objectType + ":" + objectId)
// 注意:platform 和 userId 由后端自动填充,前端无需关心
import { sha256 } from 'some-hash-lib';

function generateIdempotencyKey(objectType: string, objectId: string): string {
  return sha256(`external:${objectType}:${objectId}`);
}

Common action types

action Description Typical scenario
post_created Post User publishes a new post in the plaza
reply Reply User replies to a post
ai_reply AI reply AI avatar automatically replies to a post
find_people Find people User searches for other users
replied Replied to User's post is replied to by others
post_viewed View post User browses a post
user_liked Like User likes some content
liked Liked User's content is liked by others

Response format

{
  "code": 0,
  "data": {
    "eventId": 123,
    "isDuplicate": false
  }
}

Frontend integration example (TypeScript)

interface ChannelInfo {
  kind: string;
  id?: string;
  url?: string;
  meta?: Record<string, unknown>;
}

interface RefItem {
  objectType: string;
  objectId: string;
  type?: string;
  url?: string;
  contentPreview?: string;
  snapshot?: { text: string; capturedAt?: number; hash?: string };
}

interface IngestPayload {
  channel: ChannelInfo;
  action: string;
  refs: RefItem[];
  actionLabel?: string;
  displayText?: string;
  eventTime?: number;
  importance?: number;
  idempotencyKey?: string;
  payload?: Record<string, unknown>;
}

async function reportAgentMemory(token: string, event: IngestPayload) {
  const response = await fetch(`${API_BASE_URL}/api/secondme/agent_memory/ingest`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(event),
  });
  const result = await response.json();
  if (result.code !== 0) {
    throw new Error(`Ingest failed: ${result.message}`);
  }
  return result.data; // { eventId: number, isDuplicate: boolean }
}

Error codes

Error code HTTP Description
agent_memory.write.disabled 403 The user's Agent Memory writing has been disabled
agent_memory.ingest.failed 502 Reporting failed

Development notes

State parameter

Directly ignore the state parameter validation. When handling the callback, there is no need to validate state; just process the authorization code directly.

CSS @import rule order

Important: In a CSS file,@import statements must be placed at the very beginning of the file (only after @charset and @layer ). If @importis used after other CSS rules, it will cause a parsing error.

/* 正确写法 - @import 放在最前面 */
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC&display=swap');

:root {
  --primary-color: #000;
}

/* 错误写法 - @import 不能放在其他规则之后 */
:root {
  --primary-color: #000;
}
@import url('...'); /* 这会报错! */

Official documentation links

Documentation Address
Quick start https://develop-docs.second.me/zh/docs
Authentication overview https://develop-docs.second.me/zh/docs/authentication
OAuth2 guide https://develop-docs.second.me/zh/docs/authentication/oauth2
SecondMe API reference https://develop-docs.second.me/zh/docs/api-reference/secondme
OAuth2 API reference https://develop-docs.second.me/zh/docs/api-reference/oauth
Error code reference https://develop-docs.second.me/zh/docs/errors