You can create apps to allow users to access and modify their data in BasicOps.
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.
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.
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!
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 IDresponse_type (required): Must be coderedirect_uri (required): Must match the redirect URI registered with your appcodechallenge (required for public clients): Base64URL-encoded SHA-256 hash of codeverifiercodechallengemethod (required for public clients): Must be S256state (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.
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
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:
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
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
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.
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>'
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 parametersinvalidclient: Invalid clientid or client_secretinvalid_grant: Invalid or expired authorization code or refresh tokenunauthorized_client: Client not authorized for this grant typeunsupportedgranttype: Grant type not supportedREST 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 can be returned either as HTTP status codes or as JSON:
{
"success": false,
"error": "Error description"
}
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
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"
}
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.
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.
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 ]
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.
To filter on a boolean field specify the value as 'true' or 'false'. As an example, for isTemplate in project records:
isTemplate:true
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]
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)
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
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
}
]
}