External integration API (read-only)
An API for reading your organization's members and to-dos for today from your own systems. Keys, requests, limits, and errors.
Last updated: 2026-10-10
This API lets an external system read your Addness organization's information, server to server. It can read only these two things:
- The list of members in the organization
- Every member's to-dos for today, and the goal hierarchy related to those to-dos (you can specify a date)
Writes, notifications, chat, and any other information can't be read with this key.
Limited release: This API is available only to organizations that Addness has enabled. In other organizations, "Integration API key" can't be chosen when creating an API key, and calls with an existing key return 403
EXTERNAL_INTEGRATION_FEATURE_DISABLED. If access is turned off, keys are not revoked; when it is turned back on, the same keys work again.
How it differs from MCP integration
- Used by: The external integration API is used by your own system (a server). MCP integration is used by the AI agent you use.
- Acts as: The external integration API acts as one integration (bot) per organization, which isn't a member of the organization. MCP integration acts as you, signed in.
- What it can do: The external integration API only reads members and to-dos for today. MCP integration reads and writes goals, within your own permissions.
- Auth: The external integration API uses a key fixed to the organization (
Authorization: Bearer). MCP integration uses signing in through the browser (OAuth).
To use Addness from an AI, use MCP integration. Integration API keys can't be used with MCP or any other API (403 AUTH_API_KEY_RESOURCE_RESTRICTED).
Prepare a key
- An organization admin selects "Create" under "API Keys" in Settings > Integrations, and chooses "Integration API key" as the type.
- Enter a name and the key's expiry. Expiry is 30, 90, or 365 days; no expiry isn't allowed.
- The key (a string starting with
sk-) is shown only once, when it's created. It can't be shown again after you close it, so set it in your external system right away.
Each organization can have only one integration API key. Once it exists, "Integration API key" can't be chosen anymore; replace the key with "Renew" in the API key list instead. A key works only for the organization where it was created.
- Renew a key: Select "Renew" on the integration API key in the API key list to issue a new key. The old key is shown as "Rotating" with its expiry, and keeps working until its current expiry or 7 days after the renewal, whichever is earlier.
- Revoke a key: A key revoked from the API key list stops working immediately (401). If you revoke the last one, external systems can't read anything until you create a new one.
- Delete the organization: All keys stop working.
Making requests
The host is https://vt.api.addness.com. Put the key in the Authorization header on every request.
curl -H "Authorization: Bearer sk-xxxxxxxx" \
"https://vt.api.addness.com/api/v1/external/organizations/{organizationId}/members"
- Put the ID of the organization that created the key in
{organizationId}. Specifying another organization returns 403AUTH_API_KEY_ORG_MISMATCH. - The
X-Organization-IDheader isn't needed. If you send it, use the same organization ID. - Only these two
GETrequests are available.- Members:
GET /api/v1/external/organizations/{organizationId}/members - To-dos for today:
GET /api/v1/external/organizations/{organizationId}/today-todos
- Members:
Members
GET /api/v1/external/organizations/{organizationId}/members?limit=100&cursor=…
| Parameter | Meaning |
|---|---|
limit | Members per page. Default 100, max 200 |
cursor | nextCursor from the previous page. Omit for the first page |
{
"members": [
{"memberId": "0b6f0e8e-…", "name": "Hanako Yamada", "isGuest": false, "isExternal": false}
],
"nextCursor": "MGI2ZjBl…"
}
| Field | Meaning |
|---|---|
memberId | The organization member ID. Same value as memberId in to-dos for today |
name | Display name in the organization |
isGuest | A guest member |
isExternal | A member joining from another organization (only their information in this organization is returned) |
- Sorted by
memberId. - Only human members are returned. AI agents and members who left are not included.
- Email addresses, user IDs, avatars, managers, and tags are not returned.
To-dos for today
GET /api/v1/external/organizations/{organizationId}/today-todos?date=2026-10-05&limit=50&cursor=…
| Parameter | Meaning |
|---|---|
date | Activity date (YYYY-MM-DD). Omit for today |
limit | Members per page. Default 20, max 50. Depending on member count and to-do volume, even a page of 20 or 50 members can exceed 10 seconds and return 504. If you get 504, read with a smaller limit |
cursor | nextCursor from the previous page |
{
"activityDate": "2026-10-05",
"timezone": "Asia/Tokyo",
"members": [
{
"memberId": "0b6f0e8e-…",
"todos": [
{"todoId": "7d1c2b0a-…", "kind": "goal", "objectiveId": "c3a0f4c2-…", "createdAt": "2026-10-04T23:30:00Z"}
],
"goals": [
{"id": "9f0a1b2c-…", "parentId": null, "orderNo": 1, "title": "Q3 goal", "status": "NONE", "completedAt": null, "isWaitingOnOther": false},
{"id": "c3a0f4c2-…", "parentId": "9f0a1b2c-…", "orderNo": 2, "title": "Write the weekly review", "status": "COMPLETED", "completedAt": "2026-10-05T02:10:00Z", "isWaitingOnOther": false}
]
}
],
"nextCursor": null
}
todos is "who picked what as a to-do for today", and goals holds those goals' titles, parent-child links, and status. goals is the source of truth for goal titles and status; rows in todos carry only objectiveId.
todos contains only rows added from a goal (kind: "goal"). Free to-dos not tied to a goal (including rows from running a Goal task and recurring-schedule results) are not returned.
| Field | Meaning |
|---|---|
kind | Always goal (a row added from a goal) |
objectiveId | The goal ID. Match it with goals[].id |
createdAt | When the row was added to to-dos for today (UTC) |
- The set of members is the same as the member list. Members with no to-dos are returned with empty
todosandgoals. - Each member's rows are in the same order as in Addness (manual order, then the order added). Up to 800 to-dos per member.
Goal hierarchy (goals)
goals is the material for building the tree in the "to-dos for today" area of a member's page.
| Field | Meaning |
|---|---|
id | The goal ID. Match it with todos[].objectiveId |
parentId | The parent's ID within the same goals. null if there is no parent (a root of the tree) |
orderNo | Sibling order (ascending). For equal values, keep the array order |
title | Title. Today: the current title. Past dates: the title saved on that day |
status | NONE, IN_PROGRESS, COMPLETED, CANCELLED. Today: the member's execution record for that day if any, otherwise the goal's status. COMPLETED if there is a completion time |
completedAt | Completion time (UTC). Only when COMPLETED |
isWaitingOnOther | A row waiting on someone else's work (another assignee's unfinished goal directly under the member's own goal) |
goals contains only the goals of that day's to-dos, plus their descendants and ancestors that are screen candidates (the member's assigned goals and their ancestors, goals completed that day, and waiting rows for other assignees). It doesn't include the organization's whole goal tree, and it doesn't return assignees, descriptions, due dates, or numbers.
Listing all of goals as is does not reproduce the "to-dos for today" area. To build the same tree as the screen, follow these steps:
- Start from each
objectiveIdintodos(one tree row per goal even if it has several to-dos). The starting goal is always ingoals. Goals that aren't screen candidates are appended at the end ofgoalsas roots withparentId: null. Only goals outside the organization or deleted are missing fromgoals; treat those to-dos as having no title. - From each starting point, follow
parentIdtoward children and add all descendants found ingoals. - For each starting point, add its direct parent. When branches from different starting points meet at the same ancestor, also add that common ancestor and the goals between. Added ancestors are context rows (gray on screen). Don't follow parents that aren't in
goals; make the child a root instead. - Sort siblings by
orderNoascending (array order for ties) and display depth-first from the roots. Recompute depth from the tree you built. - Keep completed rows in place. Show
isWaitingOnOtherrows under their parent as "waiting".
Reading to the last page
For both APIs, pass nextCursor from the response as cursor in the next request, and keep reading until nextCursor is null.
- Pages are split only by number of members (
limit), not by number of to-do rows. - Don't depend on what's inside
cursor. An unreadablecursorreturns 400INVALID_REQUEST.
Date rules
- The day switches at 4:00 a.m. in the organization's time zone (
timezonein the response;Asia/Tokyoif not set). - Today (
dateomitted, or today's date): The current list. Unfinished rows carry over across days until they are completed or removed. - Past dates: Rows added that day and rows completed that day. This differs from the list shown on screen that day in these ways:
- Unfinished rows carried over from earlier days, and rows later removed (deleted), are not included.
- Goal titles, parents, and status (
goals) are those saved on that day and aren't overwritten with the current goal status. If nothing was saved that day,goalsis empty.
- Future dates: Not allowed (400
INVALID_REQUEST). - All times (
createdAt,completedAt) are in UTC.
Limits
| Scope | Limit |
|---|---|
| Per key | 6,000 per hour (bursts of up to 2,000 more; a per-server guide) |
| Per integration | 200 per minute |
| To-dos for today: frequency | 120 per hour per organization (bursts of up to 20 more; per server) |
| To-dos for today: concurrency | 2 per organization (4 per server) |
| To-dos for today: one page | 10 seconds |
- Exceeding a limit returns 429 (504 when one page runs out of time). See "Errors" below for the codes.
- Rejected requests count against the same limits.
- On 429 or 504, retry after
Retry-After(seconds). On 504, use a smallerlimit. - Keep at most 2 requests in parallel. One round takes "members ÷
limit(rounded up)" pages; a smallerlimitincreases pages per round. - Space out rounds for your member count and
limitso that requests per hour (pages per round × rounds) stay within 120. For example, withlimit50, 120 members take 3 calls per round, so about once every 5 minutes (36 per hour) fits. Withlimit50 and 600 members, 12 calls per round mean once every 5 minutes would be 144 per hour, over the limit; use an interval longer than 6 minutes. Retries count too, so leave some headroom.
Errors
The error body is {"error": "reason", "code": "code"}. Use code to decide what happened.
400
INVALID_REQUEST:limitout of range, unreadablecursor, wrongdateformat, or a future dateAUTH_ORGANIZATION_ID_INVALID: The organization ID in the path isn't a UUID
401
AUTH_INVALID_API_KEY, etc.: Missing, invalid, revoked, or expired key (including a deleted integration or organization)
403
AUTH_API_KEY_ORG_MISMATCH: Another organization was specifiedAUTH_API_KEY_RESOURCE_RESTRICTED: Called with a key that isn't an integration API key, or an integration API key was used for something other than these twoAUTH_EXTERNAL_INTEGRATION_KEY_REQUIRED: Called with a sign-in sessionEXTERNAL_INTEGRATION_FEATURE_DISABLED: The API isn't enabled for the organization (the key stays valid)EXTERNAL_INTEGRATION_INACTIVE: The integration has been deleted (for example, after its last key was revoked)
429 (retry after Retry-After seconds)
API_KEY_RATE_LIMIT_EXCEEDED,RATE_LIMIT_EXCEEDED: Exceeded the per-key or per-integration rate limitEXTERNAL_READ_BUSY: Hit the concurrency limit for to-dos for todayEXTERNAL_GOALS_RATE_LIMITED: Hit the frequency limit for to-dos for today (120 per hour per organization)
504 (retry after Retry-After seconds with a smaller limit)
EXTERNAL_READ_TIMEOUT: One page of to-dos for today didn't finish within 10 seconds
Articles in this section— Running Your Team
Was this article helpful?