Skip to main content

API Response Standards

FlowOn API follows standard HTTP conventions and OpenAPI 3.0 specifications for all responses.

HTTP Status Codes

Status CodeDescriptionWhen Used
200OKSuccessful GET, POST, PUT, DELETE operations
204No ContentSuccessful operation with no response body (Single/Multi queries with no results)
400Bad RequestValidation errors, pre-condition failures, business rule violations
401UnauthorizedAuthentication failure or insufficient permissions
404Not FoundAttachment not found (for download operations)
500Internal Server ErrorUnexpected server-side errors

Success Responses

Create Action

Returns the ID of the created record:

{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}

Update Action

Returns an empty response body:

HTTP/1.1 200 OK
Content-Length: 0

Delete Action

Returns an empty response body:

HTTP/1.1 200 OK
Content-Length: 0

Read Action / Single Query

Returns the record directly:

{
"accountId": "a1b2c3d4-...",
"name": "Contoso Ltd",
"emailAddress1": "info@contoso.com"
}

Multi Query

Returns an array of records:

[
{ "contactId": "c1-guid", "fullName": "Alice Johnson" },
{ "contactId": "c2-guid", "fullName": "Bob Williams" }
]

Paginated Query

Returns a paginated result object:

{
"cursor": "eyJwYWdlIjoxfQ==",
"count": 20,
"totalRecordCount": 47,
"hasMoreRecords": true,
"data": [
{ "accountId": "a1-guid", "name": "Contoso Ltd" },
{ "accountId": "a2-guid", "name": "Fabrikam Inc" }
]
}

Process Actions

Returns an empty response body (or output parameters if defined):

{}

Error Response Structure

All errors follow a consistent structure:

{
"errorCode": "ERROR_CODE",
"errorMessage": "Human-readable description of the error"
}
FieldTypeDescription
errorCodeStringMachine-readable error identifier
errorMessageStringHuman-readable error description

Common Error Codes

Validation Errors (400)

{
"errorCode": "VALIDATION_ERROR",
"errorMessage": "Name is required and cannot be empty"
}

Pre-condition Failures (400)

{
"errorCode": "PRECONDITION_FAILED",
"errorMessage": "Cannot update inactive accounts"
}

Authentication Errors (401)

{
"errorCode": "UNAUTHORIZED",
"errorMessage": "Invalid or expired authentication token"
}

Authorization Errors (401)

{
"errorCode": "UNAUTHORIZED",
"errorMessage": "You do not have permission to perform this action"
}

Not Found Errors (404)

{
"errorCode": "NOT_FOUND",
"errorMessage": "Account not found"
}
{
"errorCode": "ATTACHMENT_NOT_FOUND",
"errorMessage": "The requested attachment was not found"
}

Business Rule Violations (400)

{
"errorCode": "BUSINESS_RULE_VIOLATION",
"errorMessage": "Order total exceeds customer credit limit"
}

File Upload Errors (400)

{
"errorCode": "FILE_TOO_LARGE",
"errorMessage": "File too large. Maximum size: 10 MB"
}
{
"errorCode": "INVALID_FILE_TYPE",
"errorMessage": "File type not allowed. Allowed extensions: .pdf, .doc, .docx"
}

Internal Errors (500)

{
"errorCode": "INTERNAL_ERROR",
"errorMessage": "An unexpected error occurred while processing the request"
}

Best Practices for Handling Responses

Check Status Codes

Always check the HTTP status code before processing the response:

const response = await fetch('/api/sales/accounts', {
method: 'POST',
body: JSON.stringify(accountData)
});

if (response.ok) {
const { id } = await response.json();
console.log('Created account:', id);
} else {
const error = await response.json();
console.error('Error:', error.errorMessage);
}

Handle Empty Responses

Some operations return empty responses (204 No Content):

const response = await fetch('/api/sales/queries/account?AccountId=' + id);

if (response.status === 204) {
// No record found
return null;
}

return await response.json();

Parse Paginated Results

For paginated queries, use the cursor for navigation:

async function fetchAllAccounts() {
let cursor = null;
const allAccounts = [];

do {
const url = cursor
? `/api/sales/queries/accounts?cursor=${cursor}`
: '/api/sales/queries/accounts';

const response = await fetch(url);
const { data, cursor: nextCursor, hasMoreRecords } = await response.json();

allAccounts.push(...data);
cursor = hasMoreRecords ? nextCursor : null;
} while (cursor);

return allAccounts;
}