Complete reference for the Connectively Business Plan Users API
https://connectively.us/api/external-usersJWT Token in x-access-token header
v1.0.0
The Connectively Business Plan Users API allows you to manage profiles, submit answers to questions, and track submissions and publications programmatically.
Copy this prompt into your AI coding agent (Claude Code, Cursor, Copilot, etc.) to scaffold a typed API client for these endpoints.
Implement a typed API client for the Connectively Business Plan API.
Base URL: https://connectively.us/api/external-users
Reference docs: https://connectively.us/api-integration/documentation/business
## Authentication
- Every endpoint except POST /login requires a JWT.
- Obtain a token via POST /login with a JSON body { "username": ..., "password": ... }. The token is returned in the `x-access-token` RESPONSE header, not the response body.
- Send that token in the `x-access-token` REQUEST header on all authenticated calls.
- Tokens expire after 24 hours — re-authenticate when a request returns 401.
## Endpoints
### Authentication
- POST /login — Authenticate external user
Request body (application/json): { username: string (required), password: string (required, min 8 chars) }
Returns: JWT token in x-access-token header
### Profiles
- POST /add-profile — Create a new profile
Request body (multipart/form-data): { firstName: string (required), lastName: string (required), prefix: string (optional, max 20 chars), suffix: string (optional, max 50 chars), jobTitle: string (required), company: string (optional), companyWebsite: string (optional), identityLink: string (required) - LinkedIn URL, profileBlob: file (required) - JPEG/PNG/WebP, max 5MB, square, min 200x200px, optOut: string (required) - 'true' or 'false', clientEmail: string (optional) - email format }
Returns: success, message, and profileId
- GET /profiles — Get user profiles
Returns: profileList array with profileId, firstName, lastName, linkedinUrl, isActive, isFlagged, isUsed
- POST /edit-profile — Edit a profile
Request body (multipart/form-data): { profileId: integer (required, min 1), firstName: string (optional) - queued for review, lastName: string (optional) - queued for review, prefix: string (optional, max 20 chars) - empty string clears, suffix: string (optional, max 50 chars) - empty string clears, jobTitle: string (optional), company: string (optional) - empty string clears, companyWebsite: string (optional) - empty string clears, identityLink: string (optional) - LinkedIn URL, queued for review, profileBlob: file (optional) - JPEG/PNG/WebP, max 5MB, square, min 200x200px, optOut: string (optional) - 'true' or 'false', clientEmail: string (optional) - email format, empty string clears }
Returns: success, message, profileId, and pendingReview (fields awaiting staff approval)
- POST /deactivate-profile — Deactivate a profile
Request body (application/json): { profileId: integer (required, min 1) }
### Questions
- GET /premium-question-list — Get premium questions
Returns: array with featuredQuestionId, question, closeDate, openDate, domainAuthority, domainRating, agenticScore, attribution, sourceUrl, publicLink, mayCloseEarly, isHaroQuery, rejectsAi, categories
- POST /answer-question — Submit answer to a question
Request body (application/json): { answer: string (required, 100-2500 chars), featuredQuestionId: integer (required, min 1), profileId: integer (required, min 1) }
Returns: 'Success' as plain text
### Answers
- GET /submitted — Get submitted answers
Query params: page (integer, optional, default 1) — Page number. 100 items per page; a page with fewer than 100 items is the last one.
Returns: { submitted: [...] } — a plain array of up to 100 items for the requested page. No pagination metadata is returned.
- GET /selected — Get selected answers
Query params: page (integer, optional, default 1) — Page number. 100 items per page; a page with fewer than 100 items is the last one.
Returns: { note, selected: [...] } — a plain array of up to 100 items for the requested page. No pagination metadata is returned.
- GET /published — Get published answers
Query params: page (integer, optional, default 1) — Page number. 100 items per page; a page with fewer than 100 items is the last one.
Returns: { published: [...] } — a plain array of up to 100 items (each with publishDate, articleTitle, publishedLink) for the requested page. No pagination metadata is returned.
- GET /not-selected — Get not selected answers
Query params: page (integer, optional, default 1) — Page number. 100 items per page; a page with fewer than 100 items is the last one.
Returns: { notSelected: [...] } — a plain array of up to 100 items (each with discardedOn) for the requested page. No pagination metadata is returned.
### Opportunities
- GET /opportunities-list — Get media opportunities
Returns: array with opportunity, mediaOutlet, source, createdAt, deadline, pitchUrl
### Subscription Management
- POST /add-seats — Add seats to subscription
Request body (application/json): { addSeats: integer (required, min 1) }
Returns: 'Success' as plain text
## Error handling
Handle these status codes:
- 400: validation error — surface the message
- 401: token missing/expired — re-authenticate and retry once
- 402: payment required — prompt to add seats
- 403: insufficient permissions
- 404: not found
- 409: conflict (e.g. duplicate)
- 429: rate limited — respect Retry-After / retryAfterSeconds and back off
- 500: server error — retry with backoff
## Requirements
- Use my project's language and HTTP conventions (default to TypeScript with fetch if unspecified).
- Expose one typed method per endpoint, with typed request and response models matching the fields above.
- Read credentials from environment variables; never hard-code them.
- Centralize auth: obtain and cache the token, attach the `x-access-token` header automatically, and transparently re-login on a 401.
- Add a small usage example showing login followed by one authenticated call.All endpoints (except /login) require authentication via JWT token
Authenticate using the /login endpoint with your username and password. The token is returned in the x-access-token response header.
Include the token in the x-access-token header for all authenticated requests:
x-access-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Tokens are valid for 24 hours. It's recommended to call the login endpoint for each request.
/loginAuthenticate an external user with username and password, returning a JWT token
Content-Type: application/json
{
"username": "string (required)",
"password": "string (required, min 8 chars)"
}Response Body:
{
"responseHeaders": {
"x-access-token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImpvaG4uZG9lIiwicm9sZSI6ImV4dGVybmFsIn0.example"
}
}/add-profileCreate a new expert profile with image upload
x-access-token(string, required)JWT token for API authentication. Obtain this token from the /login endpoint.
x-access-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImpvaG4uZG9lIiwicm9sZSI6ImV4dGVybmFsIn0.exampleContent-Type: multipart/form-data
{
"firstName": "string (required)",
"lastName": "string (required)",
"prefix": "string (optional, max 20 chars)",
"suffix": "string (optional, max 50 chars)",
"jobTitle": "string (required)",
"company": "string (optional)",
"companyWebsite": "string (optional)",
"identityLink": "string (required) - LinkedIn URL",
"profileBlob": "file (required) - JPEG/PNG/WebP, max 5MB, square, min 200x200px",
"optOut": "string (required) - 'true' or 'false'",
"clientEmail": "string (optional) - email format"
}Response Body:
{
"success": true,
"message": "Profile created successfully",
"profileId": 12345
}Response Body:
{
"error": "Validation failed",
"details": {}
}Response Body:
{
"error": "Profile already exists",
"profileId": 12345
}Response Body:
{
"error": "Internal server error",
"details": "An unexpected error occurred while processing your request"
}/profilesRetrieve all active profiles for the authenticated user
x-access-token(string, required)JWT token for API authentication. Obtain this token from the /login endpoint.
x-access-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImpvaG4uZG9lIiwicm9sZSI6ImV4dGVybmFsIn0.exampleResponse Body:
{
"profileList": [
{
"profileId": 12345,
"firstName": "John",
"lastName": "Doe",
"linkedinUrl": "https://linkedin.com/in/johndoe",
"isActive": true,
"isFlagged": false,
"isUsed": false
}
]
}/edit-profileEdit an active expert profile. Only the fields sent are changed. Name and LinkedIn changes are queued for staff review; everything else applies immediately.
x-access-token(string, required)JWT token for API authentication. Obtain this token from the /login endpoint.
x-access-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImpvaG4uZG9lIiwicm9sZSI6ImV4dGVybmFsIn0.exampleContent-Type: multipart/form-data
{
"profileId": "integer (required, min 1)",
"firstName": "string (optional) - queued for review",
"lastName": "string (optional) - queued for review",
"prefix": "string (optional, max 20 chars) - empty string clears",
"suffix": "string (optional, max 50 chars) - empty string clears",
"jobTitle": "string (optional)",
"company": "string (optional) - empty string clears",
"companyWebsite": "string (optional) - empty string clears",
"identityLink": "string (optional) - LinkedIn URL, queued for review",
"profileBlob": "file (optional) - JPEG/PNG/WebP, max 5MB, square, min 200x200px",
"optOut": "string (optional) - 'true' or 'false'",
"clientEmail": "string (optional) - email format, empty string clears"
}Response Body:
{
"success": true,
"message": "Profile updated successfully",
"profileId": 12345,
"pendingReview": []
}Response Body:
{
"error": "Validation failed",
"details": {}
}Response Body:
{
"error": "A change to firstName is already under review",
"field": "firstName"
}Response Body:
{
"error": "Internal server error",
"details": "An unexpected error occurred while processing your request"
}/deactivate-profileDeactivate an active profile for the authenticated user
x-access-token(string, required)JWT token for API authentication. Obtain this token from the /login endpoint.
x-access-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImpvaG4uZG9lIiwicm9sZSI6ImV4dGVybmFsIn0.exampleContent-Type: application/json
{
"profileId": "integer (required, min 1)"
}/premium-question-listRetrieve active questions available for premium users
x-access-token(string, required)JWT token for API authentication. Obtain this token from the /login endpoint.
x-access-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImpvaG4uZG9lIiwicm9sZSI6ImV4dGVybmFsIn0.exampleResponse Body:
[
{
"featuredQuestionId": 12345,
"question": "What are the key trends in AI for 2024?",
"closeDate": "2024-01-30T23:59:59Z",
"openDate": "2024-01-15T00:00:00Z",
"domainAuthority": 85,
"domainRating": 82,
"agenticScore": 64,
"attribution": "dofollow",
"sourceUrl": "https://techcrunch.com/article",
"publicLink": "https://connectively.us/public-link/source/question-slug",
"mayCloseEarly": false,
"isHaroQuery": true,
"rejectsAi": false,
"categories": [
"Technology",
"AI"
]
}
]/answer-questionSubmit an answer to a featured question. Rate limited to 100 requests per hour per account (sliding window).
x-access-token(string, required)JWT token for API authentication. Obtain this token from the /login endpoint.
x-access-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImpvaG4uZG9lIiwicm9sZSI6ImV4dGVybmFsIn0.exampleContent-Type: application/json
{
"answer": "string (required, 100-2500 chars)",
"featuredQuestionId": "integer (required, min 1)",
"profileId": "integer (required, min 1)"
}Response Body:
SuccessResponse Body:
Bad Request - error: {"issues":[{"code":"too_small","minimum":100,"type":"string","inclusive":true,"exact":false,"message":"Answer must be at least 100 characters","path":["answer"]}]}Response Body:
UnauthorizedResponse Body:
Business subscription requiredResponse Body:
Question not foundResponse Body:
Add More SeatsResponse Body:
{
"error": "Rate limit exceeded. Maximum 100 attempts per hour.",
"retryAfterSeconds": 3600
}Response Body:
Something went wrong/submittedRetrieve paginated list of all submitted answers with their status
x-access-token(string, required)JWT token for API authentication. Obtain this token from the /login endpoint.
x-access-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImpvaG4uZG9lIiwicm9sZSI6ImV4dGVybmFsIn0.examplepage(integer, optional, default: 1)Page number. 100 items per page; a page with fewer than 100 items is the last one.
Response Body:
{
"submitted": {
"data": [
{
"publicationSource": "TechCrunch",
"submissionDate": "2024-01-10T14:20:00Z",
"domainAuthority": 85,
"domainRating": 82,
"agenticScore": 64,
"attribution": "dofollow",
"expertName": "John Doe",
"jobTitle": "Senior Software Engineer",
"company": "Tech Corp",
"profileId": 12345,
"featuredQuestionId": 67890,
"status": "Submitted"
}
],
"pagination": {
"page": 1,
"totalPages": 10,
"totalItems": 500
}
}
}/selectedRetrieve paginated list of answers that have been selected for publication
x-access-token(string, required)JWT token for API authentication. Obtain this token from the /login endpoint.
x-access-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImpvaG4uZG9lIiwicm9sZSI6ImV4dGVybmFsIn0.examplepage(integer, optional, default: 1)Page number. 100 items per page; a page with fewer than 100 items is the last one.
Response Body:
{
"note": "Your answer has been selected by Connectively or by the publisher, showing their interest in publishing it. This does not guarantee publication.",
"selected": {
"data": [
{
"publicationSource": "TechCrunch",
"submissionDate": "2024-01-10T14:20:00Z",
"domainAuthority": 85,
"domainRating": 82,
"agenticScore": 64,
"attribution": "dofollow",
"expertName": "John Doe",
"jobTitle": "Senior Software Engineer",
"company": "Tech Corp",
"profileId": 12345,
"featuredQuestionId": 67890,
"selectedOn": "2024-01-15T10:30:00Z"
}
],
"pagination": {
"page": 1,
"totalPages": 3,
"totalItems": 25
}
}
}/publishedRetrieve paginated list of published answers
x-access-token(string, required)JWT token for API authentication. Obtain this token from the /login endpoint.
x-access-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImpvaG4uZG9lIiwicm9sZSI6ImV4dGVybmFsIn0.examplepage(integer, optional, default: 1)Page number. 100 items per page; a page with fewer than 100 items is the last one.
Response Body:
{
"published": {
"data": [
{
"publicationSource": "TechCrunch",
"submissionDate": "2024-01-10T14:20:00Z",
"domainAuthority": 85,
"domainRating": 82,
"agenticScore": 64,
"attribution": "dofollow",
"expertName": "John Doe",
"jobTitle": "Senior Software Engineer",
"company": "Tech Corp",
"profileId": 12345,
"featuredQuestionId": 67890,
"publishDate": "2024-01-15T10:30:00Z",
"articleTitle": "AI Trends in 2024",
"publishedLink": "https://techcrunch.com/ai-trends-2024"
}
],
"pagination": {
"page": 1,
"totalPages": 5,
"totalItems": 150
}
}
}/not-selectedRetrieve paginated list of answers that were not selected for publication
x-access-token(string, required)JWT token for API authentication. Obtain this token from the /login endpoint.
x-access-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImpvaG4uZG9lIiwicm9sZSI6ImV4dGVybmFsIn0.examplepage(integer, optional, default: 1)Page number. 100 items per page; a page with fewer than 100 items is the last one.
Response Body:
{
"notSelected": {
"data": [
{
"publicationSource": "TechCrunch",
"submissionDate": "2024-01-10T14:20:00Z",
"domainAuthority": 85,
"domainRating": 82,
"agenticScore": 64,
"attribution": "dofollow",
"expertName": "John Doe",
"jobTitle": "Senior Software Engineer",
"company": "Tech Corp",
"profileId": 12345,
"featuredQuestionId": 67890,
"discardedOn": "2024-01-15T10:30:00Z"
}
],
"pagination": {
"page": 1,
"totalPages": 8,
"totalItems": 400
}
}
}/opportunities-listRetrieve a list of active media opportunities with deadlines
x-access-token(string, required)JWT token for API authentication. Obtain this token from the /login endpoint.
x-access-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImpvaG4uZG9lIiwicm9sZSI6ImV4dGVybmFsIn0.exampleResponse Body:
[
{
"opportunity": "What are the latest trends in AI?",
"mediaOutlet": "TechCrunch",
"source": "HARO",
"createdAt": "Jan 15th, 2024 10:30 AM UTC",
"deadline": "Jan 29th, 2024 11:59 PM UTC",
"pitchUrl": "https://example.com/pitch"
}
]/add-seatsAdd additional seats to an active Business subscription
x-access-token(string, required)JWT token for API authentication. Obtain this token from the /login endpoint.
x-access-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImpvaG4uZG9lIiwicm9sZSI6ImV4dGVybmFsIn0.exampleContent-Type: application/json
{
"addSeats": "integer (required, min 1)"
}Response Body:
SuccessMost endpoints return JSON objects with the requested data.
The answer-history endpoints (/submitted, /selected, /published, /not-selected) accept a page parameter and return 100 items per page. The value under the response key is a plain array — there is no data wrapper and no pagination metadata. Request successive pages until one comes back with fewer than 100 items.
GET /published?page=1
{ "published": [ { "articleTitle": "...", "publishedLink": "..." } ] }Errors are typically returned as plain text or JSON with error details:
The /answer-question endpoint is rate-limited to 100 requests per hour per accountusing a sliding window. When the rate limit is exceeded, a 429 status code is returned with a Retry-After header indicating when you can retry.
{
"error": "Rate limit exceeded. Maximum 100 attempts per hour.",
"retryAfterSeconds": 3600
}For API support, please contact:
Email: support@connectively.us