# Recipes

Complete, copy-ready examples for the jobs people most often build with the API. Each one is short enough to read in a minute and adapt to your own tools.

URL: https://www.success.co/docs/guides/api-recipes  
Published: 2026-11-12

## A helper to start from

The JavaScript recipes use this small helper. It sends a query with its variables and returns `data`, or throws with the API's error messages. It needs Node 18 or later, with your key in the `SUCCESS_API_KEY` environment variable.

```js
const API_URL = 'https://www.success.co/graphql'

async function gql(query, variables = {}) {
  const response = await fetch(API_URL, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.SUCCESS_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ query, variables }),
  })
  const body = await response.json()
  if (!response.ok || body.errors || body.error) {
    const messages = body.errors?.map((e) => e.message) ?? [body.error ?? response.statusText]
    throw new Error(`Success.co API: ${messages.join('; ')}`)
  }
  return body.data
}
```

## Send a Scorecard number every week

Push a number from another system (revenue from your accounting tool, tickets closed from your help desk) into a weekly measurable. Run it on a schedule, such as every Monday morning; running it twice in a week just updates the number.

It does four things:

1. Finds the measurable by name.
2. Works out the Monday that starts the week, which is the `startDate` every weekly value needs.
3. Looks for a value already in that week, including a deleted one, as there can only be one per week.
4. Updates that value, or creates one if there isn't one.

```js
// Monday of the week that contains `date`, as YYYY-MM-DD (UTC)
function mondayOf(date = new Date()) {
  const d = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()))
  const daysSinceMonday = (d.getUTCDay() + 6) % 7 // Monday 0 … Sunday 6
  d.setUTCDate(d.getUTCDate() - daysSinceMonday)
  return d.toISOString().slice(0, 10)
}

async function sendWeeklyNumber(measurableName, value, date = new Date()) {
  const { dataFields, currentCompanyId } = await gql(
    `query ($name: String!) {
      currentCompanyId
      dataFields(filter: {
        name: { equalToInsensitive: $name }
        type: { equalTo: "WEEKLY" }
        stateId: { equalTo: "ACTIVE" }
        dataFieldStatusId: { equalTo: "ACTIVE" }
      }) { nodes { id name } }
    }`,
    { name: measurableName },
  )
  const measurable = dataFields.nodes[0]
  if (!measurable) throw new Error(`No active weekly measurable called "${measurableName}"`)

  const startDate = mondayOf(date)

  const { dataValues } = await gql(
    `query ($dataFieldId: UUID!, $startDate: Date!) {
      dataValues(filter: { dataFieldId: { equalTo: $dataFieldId }, startDate: { equalTo: $startDate } }) {
        nodes { id }
      }
    }`,
    { dataFieldId: measurable.id, startDate },
  )
  const existing = dataValues.nodes[0]

  if (existing) {
    // Also brings back a value someone deleted in that week
    await gql(
      `mutation ($id: UUID!, $value: String!) {
        updateDataValue(input: { id: $id, patch: { value: $value, stateId: "ACTIVE" } }) { dataValue { id } }
      }`,
      { id: existing.id, value: String(value) },
    )
  } else {
    await gql(
      `mutation ($dataValue: DataValueInput!) {
        createDataValue(input: { dataValue: $dataValue }) { dataValue { id } }
      }`,
      { dataValue: { dataFieldId: measurable.id, startDate, value: String(value), companyId: currentCompanyId } },
    )
  }
  console.log(`${measurable.name}: ${value} for the week of ${startDate}`)
}

await sendWeeklyNumber('Weekly revenue', 48250)
```

For monthly measurables the `startDate` is the 1st of the month, and for annual ones 1 January. Quarterly measurables use your company's quarter start dates, which you can read with `company(id: "...") { quarterOneDate quarterTwoDate quarterThreeDate quarterFourDate }`.

## Create an Issue from another tool

Raise an Issue on a team's list when something happens elsewhere, such as an urgent support ticket or a failed payment. This finds the team by name and the owner by email, then creates the Issue on the short-term list.

```js
async function raiseIssue({ teamName, ownerEmail, title, details = '' }) {
  const { teams, users, currentCompanyId } = await gql(
    `query ($team: String!, $email: String!) {
      currentCompanyId
      teams(filter: { name: { equalToInsensitive: $team }, stateId: { equalTo: "ACTIVE" } }) { nodes { id } }
      users(filter: { email: { equalToInsensitive: $email }, stateId: { equalTo: "ACTIVE" } }) { nodes { id } }
    }`,
    { team: teamName, email: ownerEmail },
  )
  if (!teams.nodes[0]) throw new Error(`No team called "${teamName}"`)
  if (!users.nodes[0]) throw new Error(`No user with the email ${ownerEmail}`)

  const { createIssue } = await gql(
    `mutation ($issue: IssueInput!) {
      createIssue(input: { issue: $issue }) { issue { id number } }
    }`,
    {
      issue: {
        name: title,
        desc: details ? `<p>${escapeHtml(details)}</p>` : '',
        teamId: teams.nodes[0].id,
        userId: users.nodes[0].id,
        type: 'short-term',
        companyId: currentCompanyId,
      },
    },
  )
  return createIssue.issue
}

function escapeHtml(text) {
  return text.replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[c])
}

await raiseIssue({
  teamName: 'Leadership Team',
  ownerEmail: 'sam@example.com',
  title: 'Three enterprise renewals at risk this month',
  details: 'Flagged by the CRM health check.',
})
```

The key's owner needs access to that team, just as in the app; if they don't have it, the API refuses the change (see [Errors and limits](https://www.success.co/docs/guides/api-errors-and-limits#common-errors)). To create To-dos instead, the same pattern works with `createTodo`; see [Data model](https://www.success.co/docs/guides/api-data-model#to-dos) for its required fields.

## Export a team's Rocks

Pull a team's current Rocks, with their owners, statuses and Milestones, into a CSV file for a board pack or a BI tool. This one is in Python and uses the `requests` library.

```python
import csv
import os
import requests

API_URL = "https://www.success.co/graphql"
HEADERS = {"Authorization": f"Bearer {os.environ['SUCCESS_API_KEY']}"}

def gql(query, variables=None):
    body = requests.post(API_URL, headers=HEADERS, json={"query": query, "variables": variables or {}}).json()
    if "errors" in body or "error" in body:
        raise RuntimeError(body.get("errors") or body.get("error"))
    return body["data"]

team = gql(
    """query ($name: String!) {
      teams(filter: { name: { equalToInsensitive: $name }, stateId: { equalTo: "ACTIVE" } }) { nodes { id } }
    }""",
    {"name": "Leadership Team"},
)["teams"]["nodes"][0]

links = gql(
    """query ($teamId: UUID!) {
      teamsOnRocks(filter: { teamId: { equalTo: $teamId }, stateId: { equalTo: "ACTIVE" } }) { nodes { rockId } }
    }""",
    {"teamId": team["id"]},
)["teamsOnRocks"]["nodes"]

rocks = gql(
    """query ($ids: [UUID!]) {
      rocks(
        filter: { id: { in: $ids }, stateId: { equalTo: "ACTIVE" }, isArchived: { equalTo: false } }
        orderBy: DUE_DATE_ASC
      ) {
        nodes {
          name
          dueDate
          rockStatus { name }
          user { firstName lastName }
          milestones(filter: { stateId: { equalTo: "ACTIVE" } }) { nodes { milestoneStatusId } }
        }
      }
    }""",
    {"ids": [link["rockId"] for link in links]},
)["rocks"]["nodes"]

with open("rocks.csv", "w", newline="") as f:
    writer = csv.writer(f)
    writer.writerow(["Rock", "Owner", "Due", "Status", "Milestones done"])
    for rock in rocks:
        milestones = rock["milestones"]["nodes"]
        done = sum(1 for m in milestones if m["milestoneStatusId"] == "COMPLETE")
        owner = f"{rock['user']['firstName']} {rock['user']['lastName']}"
        writer.writerow([rock["name"], owner, rock["dueDate"], rock["rockStatus"]["name"], f"{done} of {len(milestones)}"])

print(f"Wrote {len(rocks)} Rocks to rocks.csv")
```

## Pick up changes since your last run

To keep another system in step with Success.co (a data warehouse, a reporting database), fetch only what changed since you last looked, rather than everything each time. Every item's `updatedAt` moves forward whenever it's created, changed or deleted, so filter on it and keep the latest time you've seen.

```js
async function changedIssuesSince(since) {
  const changed = []
  let after = null
  do {
    const { issues } = await gql(
      `query ($since: Datetime!, $after: Cursor) {
        issues(
          first: 200
          after: $after
          filter: { updatedAt: { greaterThan: $since } }
          orderBy: [UPDATED_AT_ASC, ID_ASC]
        ) {
          pageInfo { hasNextPage endCursor }
          nodes { id number name issueStatusId teamId stateId updatedAt }
        }
      }`,
      { since, after },
    )
    changed.push(...issues.nodes)
    after = issues.pageInfo.hasNextPage ? issues.pageInfo.endCursor : null
  } while (after)
  return changed
}

const lastRun = '2026-11-01T00:00:00Z' // load this from wherever you saved it last time
const changes = await changedIssuesSince(lastRun)
for (const issue of changes) {
  if (issue.stateId === 'DELETED') {
    // remove it from your copy
  } else {
    // insert or update it in your copy
  }
}
const nextRun = changes.at(-1)?.updatedAt ?? lastRun // save this for next time
```

Don't filter on `stateId` here: deleted items are changes too, and this is how you hear about them. Run one of these per object you copy (`todos`, `rocks`, `headlines`, `dataValues` and so on).

## List overdue To-dos

The basis for a weekly reminder or a digest: every open To-do past its due date, grouped by owner.

```graphql
query Overdue($today: Date!) {
  todos(
    filter: {
      stateId: { equalTo: "ACTIVE" }
      todoStatusId: { equalTo: "TODO" }
      dueDate: { lessThan: $today }
    }
    orderBy: [USER_ID_ASC, DUE_DATE_ASC]
  ) {
    totalCount
    nodes {
      name
      dueDate
      team { name }
      user { firstName lastName email }
    }
  }
}
```

Pass today's date as `today`, for example `{ "today": "2026-11-12" }`.
