Skip to main content

Opportunity Details API

Overview​

The Opportunity Details API allows users to retrieve comprehensive information about specific opportunities in the Ring Platform. This endpoint supports role-based access control and respects confidential opportunity visibility settings.

Endpoint Details​

  • URL: /api/opportunities/[id]
  • Methods: GET, PUT, DELETE
  • Authentication: Required (JWT Token)
  • Rate Limit: 120 requests per minute for GET, 30 for PUT/DELETE

Ring Platform Concepts​

What are Opportunities?​

Opportunities are the core value exchange mechanism in Ring Platform's professional networking ecosystem:

Dual Nature System​

  • Offers: Job postings, service offerings, collaboration proposals
  • Requests: Seeking services, talent acquisition, partnership requests

Confidential Access Tiers​

  • Public: Open access for basic opportunities
  • Subscriber: Registered user access to standard opportunities
  • Member: Verified member access to premium opportunities
  • Confidential: Exclusive access to sensitive, high-value opportunities

Rich Context Framework​

  • Budget Ranges: Transparent pricing and compensation
  • Required Skills: Detailed technical and soft skill requirements
  • Documents: Supporting materials and attachments
  • Expiration Management: Time-bound opportunities with status tracking
  • Entity Linking: Direct connection to posting organizations

Professional Networking Focus​

Unlike general job boards, Ring Platform opportunities are designed for:

  • B2B Collaborations: Company-to-company partnerships
  • Specialized Talent: High-skill technical positions
  • Industry Connections: Sector-specific networking
  • Confidential Deals: Sensitive business opportunities

GET - Retrieve Opportunity Details​

URL Parameters​

  • id (string, required): The unique identifier of the opportunity

Authentication Requirements​

  • All Users: Can access public opportunities
  • MEMBER+: Can access member-level opportunities
  • CONFIDENTIAL+: Can access confidential opportunities
  • ADMIN: Full access to all opportunities

Request Format​

GET /api/opportunities/{opportunity_id}
Authorization: Bearer <jwt_token>

Response Format​

Success Response (200 OK)​

{
"id": "opp_123456789",
"type": "offer",
"title": "Senior Blockchain Developer - DeFi Platform",
"isConfidential": false,
"briefDescription": "Join our team to build next-generation DeFi solutions",
"fullDescription": "We are seeking an experienced blockchain developer to lead the development of our innovative DeFi platform. You will work with cutting-edge technologies including Ethereum, Solidity, and Web3.js to create secure and scalable financial applications.",
"createdBy": "user_987654321",
"organizationId": "entity_456789123",
"dateCreated": "2025-01-14T10:00:00Z",
"dateUpdated": "2025-01-14T15:30:00Z",
"expirationDate": "2025-02-14T23:59:59Z",
"status": "active",
"category": "Software Development",
"tags": ["blockchain", "defi", "solidity", "ethereum", "web3"],
"location": "Remote / San Francisco, CA",
"budget": {
"min": 120000,
"max": 180000,
"currency": "USD"
},
"requiredSkills": [
"Solidity programming",
"Smart contract development",
"Web3.js/Ethers.js",
"DeFi protocols",
"Security best practices"
],
"requiredDocuments": [
"Resume/CV",
"Portfolio of blockchain projects",
"Code samples"
],
"attachments": [
{
"url": "https://storage.ring.ck.ua/opportunities/job-description.pdf",
"name": "Detailed Job Description.pdf"
}
],
"visibility": "member",
"contactInfo": {
"linkedEntity": "entity_456789123",
"contactAccount": "user_987654321"
}
}

Error Responses​

// 401 Unauthorized
{
"error": "Unauthorized"
}

// 403 Forbidden - Insufficient access level
{
"error": "Access denied"
}

// 404 Not Found
{
"error": "Opportunity not found"
}

// 500 Internal Server Error
{
"error": "Internal Server Error"
}

Code Examples​

JavaScript/TypeScript​

interface OpportunityDetails {
id: string;
type: 'offer' | 'request';
title: string;
isConfidential: boolean;
briefDescription: string;
fullDescription?: string;
createdBy: string;
organizationId: string;
dateCreated: string;
dateUpdated: string;
expirationDate: string;
status: 'active' | 'closed' | 'expired';
category: string;
tags: string[];
location: string;
budget?: {
min: number;
max: number;
currency: string;
};
requiredSkills: string[];
requiredDocuments: string[];
attachments: Array<{
url: string;
name: string;
}>;
visibility: 'public' | 'subscriber' | 'member' | 'confidential';
contactInfo: {
linkedEntity: string;
contactAccount: string;
};
}

async function getOpportunityDetails(opportunityId: string): Promise<OpportunityDetails> {
const response = await fetch(`/api/opportunities/${opportunityId}`, {
headers: {
'Authorization': `Bearer ${getAuthToken()}`
}
});

if (!response.ok) {
const error = await response.json();
throw new Error(error.error || 'Failed to fetch opportunity');
}

return response.json();
}

// Usage
try {
const opportunity = await getOpportunityDetails('opp_123456789');
console.log('Opportunity:', opportunity.title);
console.log('Budget:', `${opportunity.budget?.min}-${opportunity.budget?.max} ${opportunity.budget?.currency}`);
} catch (error) {
console.error('Error:', error.message);
}

React Component Example​

import { useState, useEffect } from 'react';

interface OpportunityDetailsProps {
opportunityId: string;
}

function OpportunityDetailsPage({ opportunityId }: OpportunityDetailsProps) {
const [opportunity, setOpportunity] = useState<OpportunityDetails | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
async function fetchOpportunity() {
try {
setLoading(true);
const data = await getOpportunityDetails(opportunityId);
setOpportunity(data);
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
} finally {
setLoading(false);
}
}

fetchOpportunity();
}, [opportunityId]);

if (loading) return <div>Loading opportunity details...</div>;
if (error) return <div>Error: {error}</div>;
if (!opportunity) return <div>Opportunity not found</div>;

return (
<div className="opportunity-details">
<header>
<h1>{opportunity.title}</h1>
<div className="opportunity-meta">
<span className={`type ${opportunity.type}`}>
{opportunity.type.toUpperCase()}
</span>
{opportunity.isConfidential && (
<span className="confidential">CONFIDENTIAL</span>
)}
<span className="status">{opportunity.status}</span>
</div>
</header>

<section className="description">
<h2>Description</h2>
<p>{opportunity.briefDescription}</p>
{opportunity.fullDescription && (
<div className="full-description">
{opportunity.fullDescription}
</div>
)}
</section>

<section className="details">
<div className="detail-grid">
<div>
<h3>Location</h3>
<p>{opportunity.location}</p>
</div>

<div>
<h3>Category</h3>
<p>{opportunity.category}</p>
</div>

{opportunity.budget && (
<div>
<h3>Budget</h3>
<p>
{opportunity.budget.min.toLocaleString()} - {opportunity.budget.max.toLocaleString()} {opportunity.budget.currency}
</p>
</div>
)}

<div>
<h3>Expires</h3>
<p>{new Date(opportunity.expirationDate).toLocaleDateString()}</p>
</div>
</div>
</section>

<section className="skills">
<h3>Required Skills</h3>
<div className="skill-tags">
{opportunity.requiredSkills.map((skill, index) => (
<span key={index} className="skill-tag">{skill}</span>
))}
</div>
</section>

<section className="tags">
<h3>Tags</h3>
<div className="tags-list">
{opportunity.tags.map((tag, index) => (
<span key={index} className="tag">#{tag}</span>
))}
</div>
</section>

{opportunity.attachments.length > 0 && (
<section className="attachments">
<h3>Attachments</h3>
<ul>
{opportunity.attachments.map((attachment, index) => (
<li key={index}>
<a href={attachment.url} target="_blank" rel="noopener noreferrer">
{attachment.name}
</a>
</li>
))}
</ul>
</section>
)}

<section className="actions">
<button className="apply-button">Apply Now</button>
<button className="contact-button">Contact Poster</button>
<button className="save-button">Save Opportunity</button>
</section>
</div>
);
}

cURL Example​

# Get opportunity details
curl -X GET https://ring.ck.ua/api/opportunities/opp_123456789 \
-H "Authorization: Bearer YOUR_JWT_TOKEN"

PUT - Update Opportunity​

Authentication Requirements​

  • Opportunity Creator: User who created the opportunity
  • ADMIN: Full platform administration access
  • CONFIDENTIAL: Can update confidential opportunities

Request Format​

PUT /api/opportunities/{opportunity_id}
Content-Type: application/json
Authorization: Bearer <jwt_token>

Request Body Schema​

interface UpdateOpportunityRequest {
title?: string;
briefDescription?: string;
fullDescription?: string;
category?: string;
tags?: string[];
location?: string;
budget?: {
min: number;
max: number;
currency: string;
};
requiredSkills?: string[];
requiredDocuments?: string[];
expirationDate?: string;
visibility?: 'public' | 'subscriber' | 'member' | 'confidential';
status?: 'active' | 'closed' | 'expired';
}

Example Request Body​

{
"title": "Senior Blockchain Developer - DeFi Platform (Updated)",
"briefDescription": "Join our growing team to build next-generation DeFi solutions",
"budget": {
"min": 130000,
"max": 190000,
"currency": "USD"
},
"requiredSkills": [
"Solidity programming",
"Smart contract development",
"Web3.js/Ethers.js",
"DeFi protocols",
"Security best practices",
"Layer 2 solutions"
],
"tags": ["blockchain", "defi", "solidity", "ethereum", "web3", "layer2"]
}

Response Format​

Success Response (200 OK)​

{
"id": "opp_123456789",
"title": "Senior Blockchain Developer - DeFi Platform (Updated)",
"briefDescription": "Join our growing team to build next-generation DeFi solutions",
"budget": {
"min": 130000,
"max": 190000,
"currency": "USD"
},
"dateUpdated": "2025-01-14T16:45:00Z",
// ... other fields
}

Code Example​

async function updateOpportunity(
opportunityId: string,
updates: UpdateOpportunityRequest
): Promise<OpportunityDetails> {
const response = await fetch(`/api/opportunities/${opportunityId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${getAuthToken()}`
},
body: JSON.stringify(updates)
});

if (!response.ok) {
const error = await response.json();
throw new Error(error.error || 'Failed to update opportunity');
}

return response.json();
}

// Usage
try {
const updatedOpportunity = await updateOpportunity('opp_123456789', {
title: 'Updated Opportunity Title',
budget: { min: 130000, max: 190000, currency: 'USD' }
});
console.log('Updated:', updatedOpportunity.title);
} catch (error) {
console.error('Update failed:', error.message);
}

DELETE - Delete Opportunity​

Authentication Requirements​

  • Opportunity Creator: User who created the opportunity
  • ADMIN: Full platform administration access
  • CONFIDENTIAL: Can delete confidential opportunities

Request Format​

DELETE /api/opportunities/{opportunity_id}
Authorization: Bearer <jwt_token>

Response Format​

Success Response (200 OK)​

{
"message": "Opportunity deleted successfully"
}

Code Example​

async function deleteOpportunity(opportunityId: string): Promise<void> {
const response = await fetch(`/api/opportunities/${opportunityId}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${getAuthToken()}`
}
});

if (!response.ok) {
const error = await response.json();
throw new Error(error.error || 'Failed to delete opportunity');
}

const result = await response.json();
console.log(result.message);
}

// Usage with confirmation
async function handleDeleteOpportunity(opportunityId: string) {
if (confirm('Are you sure you want to delete this opportunity? This action cannot be undone.')) {
try {
await deleteOpportunity(opportunityId);
// Redirect or refresh list
window.location.href = '/opportunities';
} catch (error) {
alert(`Failed to delete opportunity: ${error.message}`);
}
}
}

Access Control Logic​

Confidential Access Check​

function hasConfidentialAccess(userRole: string): boolean {
return ['admin', 'confidential'].includes(userRole);
}

function canAccessOpportunity(user: User, opportunity: Opportunity): boolean {
// Public opportunities - everyone can access
if (opportunity.visibility === 'public') return true;

// Subscriber level - registered users
if (opportunity.visibility === 'subscriber' && user.role !== 'visitor') return true;

// Member level - verified members and above
if (opportunity.visibility === 'member' && ['member', 'confidential', 'admin'].includes(user.role)) return true;

// Confidential level - premium access only
if (opportunity.visibility === 'confidential' && hasConfidentialAccess(user.role)) return true;

return false;
}

Validation Rules​

Opportunity Data Validation​

  • title: 1-200 characters
  • briefDescription: 1-500 characters
  • fullDescription: Optional, max 5000 characters
  • budget.min: Must be positive number
  • budget.max: Must be greater than min
  • expirationDate: Must be future date
  • requiredSkills: Max 20 skills, each 1-100 characters
  • tags: Max 10 tags, each 1-50 characters

Security Considerations​

  1. Role-Based Access: Strict visibility controls based on user roles
  2. Input Validation: All input is validated and sanitized
  3. Authorization Checks: Verify user permissions before operations
  4. Audit Logging: All modifications are logged
  5. Rate Limiting: Prevents abuse with appropriate limits

Changelog​

  • v1.0.0 - Initial implementation with basic CRUD operations
  • v1.1.0 - Added confidential access controls
  • v1.2.0 - Enhanced validation and error handling
  • v1.3.0 - Added budget and skills management
  • v1.4.0 - Improved security and audit logging