BasicOps

Introduction

You can create apps to allow users to access and modify their data in BasicOps.

Managing your Apps

If you haven't set up any apps yet, go to Add App to register your first app.

Once you have created one or more apps, you can manage your apps in Apps.

OAuth Authorization Flow

BasicOps supports OAuth 2.0 authorization with PKCE (Proof Key for Code Exchange) for enhanced security. PKCE is required for public clients (like web applications and integrations such as Claude.ai) and optional for confidential clients.

Client Types

  • Public Clients: Applications that cannot securely store a client secret (e.g., browser-based apps, mobile apps, Claude.ai MCP integrations). These clients must use PKCE.
  • Confidential Clients: Server-side applications that can securely store a client secret (e.g., traditional backend services). These clients may use either PKCE or client_secret authentication.

Authorizing a Client to Access a User Account

Step 1: Generate PKCE Parameters (Public Clients)

If you're building a public client, generate PKCE parameters before starting the authorization flow:

// Generate a random code_verifier (43-128 characters)
function generateCodeVerifier() {
    const array = new Uint8Array(32);
    crypto.getRandomValues(array);
    return base64URLEncode(array);
}

// Generate code_challenge from code_verifier
async function generateCodeChallenge(verifier) {
    const encoder = new TextEncoder();
    const data = encoder.encode(verifier);
    const hash = await crypto.subtle.digest('SHA-256', data);
    return base64URLEncode(new Uint8Array(hash));
}

function base64URLEncode(buffer) {
    return btoa(String.fromCharCode(...buffer))
        .replace(/\+/g, '-')
        .replace(/\//g, '_')
        .replace(/=/g, '');
}

// Example usage
const codeVerifier = generateCodeVerifier();
const codeChallenge = await generateCodeChallenge(codeVerifier);
// Store codeVerifier securely - you'll need it later!

Step 2: Initiate Authorization

Redirect the user to the authorization endpoint:

For Public Clients (with PKCE):

https://app.basicops.com/oauth/auth?client_id=<client-id>&response_type=code&redirect_uri=<redirect-uri>&code_challenge=<code-challenge>&code_challenge_method=S256&state=OPTIONAL_STATE

For Confidential Clients (without PKCE):

https://app.basicops.com/oauth/auth?client_id=<client-id>&response_type=code&redirect_uri=<redirect-uri>&state=OPTIONAL_STATE

Parameters:

  • client_id (required): Your application's client ID
  • response_type (required): Must be code
  • redirect_uri (required): Must match the redirect URI registered with your app
  • codechallenge (required for public clients): Base64URL-encoded SHA-256 hash of codeverifier
  • codechallengemethod (required for public clients): Must be S256
  • state (optional): Random string to prevent CSRF attacks; will be returned in the callback

Note: The redirect_uri must exactly match the redirect URI you specified when creating the app.

Step 3: User Authorization

The user will see:

Do you want to allow <Client Name> to access your account?

Allow   Deny

After clicking "Allow", the browser redirects to your redirect_uri with the authorization code:

https://myservice.com/oauth/callback?code=<code>&state=OPTIONAL_STATE

Step 4: Exchange Code for Access Token

For Public Clients (with PKCE):

POST https://api.basicops.com/oauth/token
Content-Type: application/x-www-form-urlencoded
client_id=<client-id>&redirect_uri=<redirect-uri>&grant_type=authorization_code&code=<code>&code_verifier=<code-verifier>

For Confidential Clients (with client_secret):

POST https://api.basicops.com/oauth/token
Content-Type: application/x-www-form-urlencoded
client_id=<client-id>&client_secret=<client-secret>&redirect_uri=<redirect-uri>&grant_type=authorization_code&code=<code>

Parameters:

  • client_id (required): Your application's client ID
  • redirect_uri (required): Must match the redirect URI from Step 2
  • granttype (required): Must be authorizationcode
  • code (required): The authorization code received in Step 3
  • codeverifier (required for public clients): The original codeverifier generated in Step 1
  • client_secret (required for confidential clients without PKCE): Your application's client secret

Response:

{
  "access_token": "<access-token>",
  "refresh_token": "<refresh-token>",
  "expires_in": 3600,
  "token_type": "Bearer"
}

Using cURL (Public Client with PKCE):

curl -X POST https://api.basicops.com/oauth/token \
  -d 'client_id=<client-id>&redirect_uri=<redirect-uri>&grant_type=authorization_code&code=<code>&code_verifier=<code-verifier>'

Using cURL (Confidential Client with client_secret):

curl -X POST https://api.basicops.com/oauth/token \
  -d 'client_id=<client-id>&client_secret=<client-secret>&redirect_uri=<redirect-uri>&grant_type=authorization_code&code=<code>'

Understanding the Token Response

  • accesstoken: Use this to authorize API requests (valid for the time specified in expiresin)
  • refresh_token: Use this to obtain a new access token when the current one expires
  • expires_in: Number of seconds until the access token expires (typically 3600 = 1 hour)
  • token_type: Always "Bearer" for BasicOps OAuth

Using the Access Token

Include the access token in the Authorization header for all API requests:

GET https://api.basicops.com/v1/task
Authorization: Bearer <access-token>

Using cURL:

curl -H "Authorization: Bearer <access-token>" https://api.basicops.com/v1/task

Token Expiration

The expires_in field indicates how many seconds the access token is valid. Attempting to use an expired token will result in a 401 Unauthorized response.

Refreshing an Access Token

When your access token expires, use the refresh token to obtain a new one without requiring the user to re-authenticate.

POST https://api.basicops.com/oauth/token
Content-Type: application/x-www-form-urlencoded
client_id=<client-id>&grant_type=refresh_token&refresh_token=<refresh-token>

Note: The redirect_uri parameter is not required for refresh token requests.

Response:

{
  "access_token": "<new-access-token>",
  "refresh_token": "<new-refresh-token>",
  "expires_in": 3600,
  "token_type": "Bearer"
}

Using cURL:

curl -X POST https://api.basicops.com/oauth/token \
  -d 'client_id=<client-id>&grant_type=refresh_token&refresh_token=<refresh-token>'

Security Best Practices

  • Always use PKCE for public clients - It protects against authorization code interception attacks
  • Keep client_secret secure - Never expose it in client-side code or version control
  • Store code_verifier securely - Only in memory during the OAuth flow; never log or persist it
  • Use the state parameter - Helps prevent CSRF attacks
  • Use HTTPS - All OAuth endpoints require HTTPS in production
  • Rotate refresh tokens - Store and use the latest refresh token returned by the API

OAuth Error Responses

If an error occurs during the OAuth flow, the API will return an error response:

{
  "error": "invalid_grant",
  "error_description": "Invalid authorization code"
}

Common error codes:

  • invalid_request: Missing or invalid parameters
  • invalidclient: Invalid clientid or client_secret
  • invalid_grant: Invalid or expired authorization code or refresh token
  • unauthorized_client: Client not authorized for this grant type
  • unsupportedgranttype: Grant type not supported

BasicOps REST API Endpoint

REST API endpoints start with:

https://api.basicops.com/v1

For example:

GET https://api.basicops.com/v1/task/<task-id>

For more information, see API Reference

Errors

Errors can be returned either as HTTP status codes or as JSON:

{
  "success": false,
  "error": "Error description"
}

Authorization Header

All API requests must include a Bearer authorization header with the access token:

GET https://api.basicops.com/v1/task/<task-id>
Authorization: Bearer <access-token>

In this example, <access-token> is the OAuth access token retrieved from the OAuth authorization process.

For requests that include a JSON body (POST, PUT, PATCH), you must also include the Content-Type header:

POST https://api.basicops.com/v1/task
Authorization: Bearer <access-token>
Content-Type: application/json

Paging

End points returning a list of records will have a limit on the maximum number of records returned in each call. The current limit is 500 records, but that may be changed in the future.

You may specify a limit on the number of records you want to retrieve by adding the optional limit parameter, e.g.

GET https://api.basicops.com/v1/task?limit=100

If more records may be available, the result will contain a field named nextPage, which contains the entire query to run to get the next page:

{
  "success": true,
  "data": [
    {
      "attachments": [],
    "section": {
      "endDate": "2018-03-31T07:00:00.000Z",
      "name": "Q1",
      "id": 1,
      "state": "Open"
    },
    "project": {
        "dueDate": "2018-08-01T07:00:00.000Z",
        "description": "Define our presence, define values we want to promote.",
        "id": 6,
        "title": "Corporate website",
        "status": "On Track"
      },
      "id": 73,
      "assignee": {
        "firstName": "Alex",
        "lastName": "Jackson",
        "id": 38,
        "email": "alex@testco.com",
        "picture": "https://api.basicops.com/v1/user/38/picture/mB1Wqsr7DjnvhagZeIag0w%3D%3D/download"
      },
      "title": "Organization of corporate website",
      "status": "Accepted"
    },
    ...
  ],
  "nextPage" : "/v1/project?limit=100&start=500"
}

Rate Limits

In order to manage the amount of work performed through the BasicOps REST API and to make sure everyone gets their fare share of resources, the number of API calls within a certain time frame may be limited. If the limit is exceeded, an API call will return the status 429, Too Many Requests. The response will also contain the header 'Retry-After', which specifies the number of seconds to wait before retrying the request.

Filters

For any end point returning a list of records you can add filters. A filter on a field has this general form:

<field name>:<value>

Example:

status:in progress

Please refer to the individual end points in order to get a list of the fields available for use in filters.

The format of the value depends on the type of field.

Date filters

For date fields you can specify a date range using this format:

[<from date> to <to date>]

Example:

dueDate:[2024-05-01T12:00:00.000Z to 2024-05-05T12:00:00.000Z]

Dates must be specified in this format: yyyy-MM-dd'T'HH:mm:ss.SSS'Z', e.g. 2024-05-21T12:00:00.000Z.

To filter on date before a certain date, omit the from date, e.g.

dueDate:[ to 2024-05-05T12:00:00.000Z]

To filter on date after a certain date, omit the to date, e.g.

dueDate:[2024-05-05T12:00:00.000Z]

or

dueDate:[2024-05-05T12:00:00.000Z to ]

Filters on status

You can filter on task status or project status. To filter on a single status use this form (using task status as an example):

status:in progress

To filter on multiple values, specify the values in square brackets and separate by commas:

status:[new,accepted,in progress]

Note that the values are case insensitive. Refer to Project Data and Task Data for the actual values available.

Filters on boolean fields

To filter on a boolean field specify the value as 'true' or 'false'. As an example, for isTemplate in project records:

isTemplate:true

Filters on references

You can filter on references to other records, such as the project a task resides in, or the assignee of a task. To add a reference filter you need to use the ID of that record, which is an integer.

To filter on a single reference, use this form (using the project of a task as an example):

project:12345

To filter on multiple values, specify the record IDs in square brackets and separate by commas:

project:[12345, 12348, 12349]

Combining filters

You can combine filter using the 'and' and 'or' keywords, and you can group the filters using parentheses. These examples are for task queries:

project:[12345, 12348, 12349] and status:in progress

(project:[12345, 12348, 12349] and status:in progress) or (status:[complete,on hold,blocked] and assignee:5678)

Adding your filter to the query

You add the filter to your query by adding a 'q' parameter:

?q=<URL encoded filter>

Note that the filter must be URL encoded since it's transmitted using the HTTP protocol. As an example, let's assume we want to query tasks using this filter:

status:[accepted,in progress]

This will result in the following query with the filter URL encoded:

https://api.basicops.com/v1/task?q=status%3A%5Baccepted%2Cin+progress%5D

Errors in filtering

When you add a filter to a query, the filter is checked for the correct syntax as well as correct field names and values appropriate to the fields. If an error is found, the result will contain an object named 'error'. In most cases, the result will also contain an array named 'errors', that contains more information about the detected error. See below for an example.

{
    "success": false,
    "error": "Column state not found",
    "errors": [
        {
            "query": "project:12345 and state:in progress",
            "index": 18
        }
    ]
}