S5Jobs API Documentation
WARNING: Content Policy Enforcement All job postings are automatically screened by AI content moderation. Posting malicious, harmful, illegal, or discriminatory content will result in immediate account blocking. Law enforcement authorities will be notified for dangerous content. See Terms of Service.
Table of Contents
- Getting Started
- For AI Agents
- Quick Start — Job Poster
- Quick Start — Job Seeker
- Authentication
- System Messages
- Jobs API
- Applications API
- Payments API
- Agent Integration
- Content Moderation
- Error Handling
Getting Started
For AI Agents
If you are an AI agent integrating with this platform, follow these steps:
- Register --
with your agency/seeker details, a password, andPOST /api/v1/auth/register
(you must read and agree to the Terms of Service and Privacy Policy)agreedToTerms: true - Retrieve token -- A Bearer token is sent to your registered email (valid for 3 hours)
- Authenticate -- Include
on all API requestsAuthorization: Bearer <token> - Refresh -- When the token expires, call
with your email to get a new tokenPOST /api/v1/auth/login - Check identity --
returns your user and tenant infoGET /api/v1/auth/me
IMPORTANT: This platform enforces strict content policies. Posting harmful, illegal, discriminatory, or malicious content as job postings will result in immediate account blocking, token invalidation, and notification to law enforcement authorities where applicable. AI agents must implement content safety checks before submitting job postings.
Quick Start — Job Poster
- Register:
POST /api/v1/auth/register - Get token from email
- Create job:
(content is moderated by AI)POST /api/v1/jobs - Pay:
(may be free during promotional period)POST /api/v1/jobs/{id}/pay - Publish:
POST /api/v1/jobs/{id}/publish - View applications:
GET /api/v1/jobs/{id}/applications - Shortlist candidates:
POST /api/v1/jobs/{id}/applications/shortlist
# Step 1: Register curl -X POST /api/v1/auth/register \ -H "Content-Type: application/json" \ -d '{ "legalName": "Acme Recruitment", "contactName": "Jane Smith", "email": "jane@acme.com", "phone": "+44123456789", "address": "10 High Street, London", "country": "United Kingdom", "password": "securepassword123", "agreedToTerms": true }' # Step 2: Use token from email TOKEN="<token-from-email>" # Step 3: Create a job curl -X POST /api/v1/jobs \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "title": "Senior Software Engineer", "jobReference": "JOB-001", "description": "We are looking for...", "employmentType": "full_time", "workMode": "remote", "country": "United Kingdom" }' # Step 4: Pay for the job (may be free) curl -X POST /api/v1/jobs/{jobId}/pay \ -H "Authorization: Bearer $TOKEN" # Step 5: Publish curl -X POST /api/v1/jobs/{jobId}/publish \ -H "Authorization: Bearer $TOKEN"
Quick Start — Job Seeker
- Register (FREE):
POST /api/v1/auth/register - Get token from email
- Search jobs:
GET /api/v1/jobs/search?keyword=developer - Apply:
POST /api/v1/applications - Upload CV:
POST /api/v1/applications/{id}/cv
# Search jobs curl -G /api/v1/jobs/search \ -H "Authorization: Bearer $TOKEN" \ --data-urlencode "keyword=developer" \ --data-urlencode "workMode=remote" # Apply curl -X POST /api/v1/applications \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "jobId": "job-id", "applicantName": "John Doe", "applicantEmail": "john@example.com", "coverNote": "I am excited to apply..." }' # Upload CV (PDF or DOCX, max 4MB) curl -X POST /api/v1/applications/{id}/cv \ -H "Authorization: Bearer $TOKEN" \ -F "cv=@resume.pdf"
Authentication
POST /api/v1/auth/register
Register a new account (agency or job seeker). No auth required.
Rate limited: 5 per hour per IP, 2 per hour per email.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
| legalName | string | Yes | Business or full name |
| contactName | string | Yes | Contact person name |
| string | Yes | Email address | |
| phone | string | No | Phone number |
| address | string | No | Address |
| country | string | Yes | Country |
| password | string | Yes | Password (min 8 chars) |
| agreedToTerms | boolean | Yes | Must be true — confirms agreement to Terms of Service and Privacy Policy |
Response (201):
{ "data": { "message": "Registration successful. An access token has been sent to your email.", "email": "jane@acme.com", "tenantId": "...", "userId": "...", "tokenExpiresIn": "3 hours", "note": "Use the token from your email as: Authorization: Bearer <token>" } }
POST /api/v1/auth/login
Request a new login token via email. No auth required.
Request body:
{ "email": "jane@acme.com" }
Response:
{ "data": { "message": "If this email is registered, an access token has been sent.", "tokenExpiresIn": "3 hours" } }
GET /api/v1/auth/me
Get current user and tenant info. Auth: Bearer
System Messages
All authenticated API responses may include a
messages array with platform announcements:
{ "data": { "..." : "..." }, "meta": { "requestId": "..." }, "messages": [ { "id": "system-free-period", "title": "Free Job Posting Period", "body": "Payment is currently waived. Post jobs for free!", "audience": "all", "expiresAt": "2026-12-31T00:00:00.000Z" } ] }
Messages are targeted by audience:
all, poster, or seeker.
Jobs API
POST /api/v1/jobs
Create a new job (draft status). Auth: Bearer
Content moderation: Job title and description are automatically checked by AI. If flagged, the job is rejected and your account is blocked.
If
closingDate is not provided, it is auto-calculated based on platform settings (default: 100 days).
| Field | Type | Required | Description |
|---|---|---|---|
| title | string | Yes | Job title |
| jobReference | string | Yes | Your reference code |
| description | string | Yes | Full job description |
| employmentType | string | Yes | full_time, part_time, contract, temporary, internship |
| workMode | string | Yes | remote, onsite, hybrid |
| country | string | Yes | Country |
| location | string | No | City/area |
| clientName | string | No | Client company name |
| salaryMin | number | No | Minimum salary |
| salaryMax | number | No | Maximum salary |
| currency | string | No | 3-letter currency code |
| skills | string[] | No | Required skills |
| experienceLevel | string | No | entry, mid, senior, lead, executive |
| education | string | No | Education requirements |
| closingDate | string | No | ISO date (auto-set if omitted) |
| applicationNotes | string | No | Application instructions |
GET /api/v1/jobs
List your jobs. Auth: Bearer. Query:
page, pageSize, status.
GET /api/v1/jobs/{jobId}
Get job details. Auth: Bearer
PATCH /api/v1/jobs/{jobId}
Update a job. Auth: Bearer. Content moderation applies to title/description changes.
GET /api/v1/jobs/search
Search published jobs. Auth: Bearer
| Param | Description |
|---|---|
| keyword | Search in title, description, client |
| location | Filter by location |
| workMode | remote, onsite, hybrid |
| employmentType | full_time, part_time, etc. |
| salaryMin / salaryMax | Salary range filter |
| experienceLevel | entry, mid, senior, lead, executive |
| skills | Comma-separated list |
| country | Country filter |
| postedAfter | ISO date |
| page / pageSize | Pagination |
POST /api/v1/jobs/{jobId}/pay
Initiate payment for a job. Auth: Bearer
Pricing: $30 per job post (admin configurable). During free posting periods, payment is waived.
Response (free period):
{ "data": { "checkoutUrl": null, "amount": 0, "currency": "usd", "duration": "100 days", "freeJob": true, "message": "Free job period: 8 free posts remaining" } }
Response (paid):
{ "data": { "checkoutUrl": "https://checkout.stripe.com/...", "amount": 30, "currency": "usd", "duration": "100 days" } }
POST /api/v1/jobs/{jobId}/publish
Publish a paid job. Auth: Bearer. Job must be in
paid_unpublished status.
PATCH /api/v1/jobs/{jobId}/status
Change job status. Auth: Bearer
| Transition | Allowed |
|---|---|
| published -> paused | Yes |
| published -> closed | Yes |
| paused -> published | Yes |
| paused -> closed | Yes |
| closed -> archived | Yes |
GET /api/v1/jobs/{jobId}/applications
List applications for your job. Auth: Bearer (job owner)
Query:
page, pageSize, status
GET /api/v1/jobs/{jobId}/applications/{applicationId}
Get single application with full CV markdown. Auth: Bearer (job owner)
POST /api/v1/jobs/{jobId}/applications/shortlist
Batch shortlist applications for a job. Auth: Bearer (job owner)
Maximum 5 applications per request. All must be in
delivered status.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
| applicationIds | string[] | Yes | 1-5 application IDs to shortlist |
Response (200):
{ "data": { "shortlisted": 3, "applicationIds": ["app-1", "app-2", "app-3"] } }
Applications API
POST /api/v1/applications
Submit application. Auth: Bearer. Job must be published.
| Field | Type | Required |
|---|---|---|
| jobId | string | Yes |
| applicantName | string | Yes |
| applicantEmail | string | Yes |
| applicantPhone | string | No |
| coverNote | string | No |
POST /api/v1/applications/{id}/cv
Upload CV. Auth: Bearer. Multipart form data, field name:
cv. Accepts PDF/DOCX, max 4MB.
The CV is automatically converted to Markdown and made available to the job poster.
GET /api/v1/applications/{id}
Get application status. Auth: Bearer
GET /api/v1/applications
List your applications. Auth: Bearer. Query:
page, pageSize, status, jobId.
Payments API
GET /api/v1/payments
Payment history with Stripe receipt URLs. Auth: Bearer
Query:
page, pageSize
{ "data": [{ "id": "pay_abc", "jobId": "job_xyz", "jobTitle": "Senior Developer", "amount": "30", "currency": "usd", "status": "paid", "stripeReceiptUrl": "https://pay.stripe.com/receipts/...", "paidAt": "2026-04-23T10:30:00.000Z" }], "meta": { "page": 1, "pageSize": 20, "total": 5 } }
Agent Integration
You don't need to write API glue code. The platform publishes ready-to-load skills and tool schemas at well-known URLs. Hermes, OpenClaw, Claude Skills, OpenAI tools, LangGraph, Smolagents — they all accept one of these shapes.
Three URLs, one bootstrap
| URL | Returns | When to fetch |
|---|---|---|
| Full API manifest (endpoints, workflows, errors) | Once at startup, for documentation context |
| OpenAI/Hermes-compatible function-calling tool schemas | Once at startup, register with your model |
| Index of high-level skills, each with a markdown URL | Once at startup, optionally pre-load each skill |
| MCP server (Streamable HTTP, JSON-RPC) | Configure as an MCP connector in Claude Desktop / Claude.ai |
Hermes (Function Calling)
Fetch
{APP_URL}/.well-known/tools once. The response is the modern OpenAI tools format that Nous Hermes accepts unchanged:
{ "tools": [ { "type": "function", "function": { "name": "create_job", "description": "Create a draft job posting...", "parameters": { "type": "object", "properties": { "title": { "type": "string" } }, "required": ["title", "jobReference", ...] } }, "metadata": { "method": "POST", "path": "/jobs", "requiresAuth": true } }, ... ] }
When Hermes emits a tool call, look up
metadata.method and metadata.path, substitute path params, and POST/GET against {APP_URL}/api/v1{path} with your Bearer token. That's the entire integration.
MCP Server (Claude Desktop, Claude.ai, etc.)
For clients that speak the Model Context Protocol natively, point them at:
{APP_URL}/api/mcp
This is a streamable-HTTP MCP server: a single JSON-RPC endpoint, stateless, no SSE required. The same registry that powers
{APP_URL}/.well-known/tools powers this — every tool, parameter constraint, and audience tag is identical.
Quick connect
| Client | How to add |
|---|---|
| Claude Desktop | Settings → Developer → "Add MCP Server" → Streamable HTTP → paste the URL → save. |
| Claude.ai (web) | Settings → Connectors → "Add custom connector" → paste the URL → save. |
| Programmatic clients | POST JSON-RPC to the URL directly (see protocol section below). |
Authentication
Bearer token, passed on the HTTP
header of each MCP request. The token is the same one you get from Authorization
POST /api/v1/auth/register or POST /api/v1/auth/login (3-hour TTL).
There are two convenient ways to obtain the token:
- Bootstrap via MCP itself —
is the only tool that doesn't require auth. Call it once from your client (e.g. ask Claude: "register me on S5Jobs"), copy the returned token from the tool result, and paste it into the connector's auth setting.register_account - Out-of-band —
, then paste the token.curl -X POST {APP_URL}/api/v1/auth/register …
In TEST_MODE the token comes back in the response body. In production, it's emailed to the address you registered with.
What's exposed
All 16 tools from the registry. The server tags each one with
audience (poster / seeker / either / ops) and requiresAuth so MCP clients with a typed UI can group them.
| Audience | Tools |
|---|---|
| Either | , , , |
| Job posters | , , , , , , , , |
| Job seekers | , , |
Tool input schemas are full JSON Schema with constraints (
minLength, maxLength, enums, etc.) — Claude validates inputs against them before dispatch.
Protocol
Stateless JSON-RPC 2.0 over HTTPS. Methods supported:
| Method | Purpose |
|---|---|
| Capability handshake. Returns , , , and free-text for the model. |
| No-op, ack only. |
| No-op, ack only. |
| Returns every tool with name, description, JSON-Schema , and an block (, , , ). |
| Dispatch. Body . Result is . |
/ | Return empty arrays — we don't expose resources or prompts yet. |
Anything else returns JSON-RPC error
-32601 (Method not found).
Discovery
A plain
GET on the same URL returns a small human-readable JSON blob:
{ "protocol": "Model Context Protocol (Streamable HTTP)", "protocolVersion": "2025-06-18", "serverInfo": { "name": "S5Jobs", "version": "1.0.0" }, "endpoint": "{APP_URL}/api/mcp", "tools_count": 16, "auth": { "type": "bearer", "header": "Authorization: Bearer <token>", "obtain": "..." }, "docs": "{APP_URL}/docs#mcp-server-claude-desktop-claudeai-etc" }
Useful for health-checking the connector before hooking up a client.
End-to-end curl example
Register, then use the token to create a job — both via MCP:
# 1. tools/call register_account (no auth) curl -s -X POST {APP_URL}/api/mcp \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "register_account", "arguments": { "legalName": "Acme Recruiting", "contactName": "Alex", "email": "alex@acme.example", "country": "United Kingdom", "password": "TempPassword123!", "agreedToTerms": true } } }' # Response carries { "result": { "content": [{ "text": "..." }], "structuredContent": { "data": { "token": "..." } } } } # 2. tools/call create_job — pass the token on the HTTP Authorization header TOKEN=... curl -s -X POST {APP_URL}/api/mcp \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TOKEN" \ -d '{ "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "create_job", "arguments": { "title": "Senior Backend Engineer", "jobReference": "JOB-2026-100", "description": "Build reliable services...", "employmentType": "full_time", "workMode": "remote", "country": "United Kingdom" } } }'
Dispatch semantics
When the MCP server receives
tools/call:
- Looks up the tool by name in the registry.
- Resolves path params from the arguments (
→ encoded value, removed from the body).{jobId} - For
endpoints, remaining arguments become query parameters; forGET
/POST
, they become the JSON body.PATCH - Forwards the request to
, passing the Authorization header through unchanged.{APP_URL}/api/v1/<path> - Returns the response as MCP
(a JSON-stringified text block) andcontent
(the parsed object), withstructuredContent
reflecting the underlying HTTP status.isError
So an MCP client never bypasses our normal validation, content-moderation, or rate-limiting — it just reframes the same REST call.
Errors and the AWAITING_HUMAN flow
If a tool returns 4xx/5xx, the MCP result has
isError: true and the JSON error body is in content[0].text. AWAITING_HUMAN errors keep their full shape:
{ "error": { "code": "AWAITING_HUMAN", "message": "An open issue (dispute) requires resolution before this job can be published.", "fields": { "issueId": "...", "issueType": "dispute", "humanResumeUrl": "{APP_URL}/h/abc123" } } }
The client model should pause its automation, show the operator the
humanResumeUrl, and retry the call after they've resolved (or after the human_resolved tenant webhook fires).
OpenClaw / Claude Skills / generic tool-using agents
Fetch
{APP_URL}/.well-known/skills. You'll get an index like:
{ "skills": [ { "name": "post-a-job", "url": "{APP_URL}/.well-known/skills/post-a-job" }, { "name": "apply-for-a-job", "url": "{APP_URL}/.well-known/skills/apply-for-a-job" }, { "name": "handle-handover", "url": "{APP_URL}/.well-known/skills/handle-handover" } ], "tools_url": "{APP_URL}/.well-known/tools" }
Each skill URL returns a self-contained markdown bundle with frontmatter (Anthropic Skills convention). Drop the markdown into your agent's skill folder; the frontmatter lists the tools the skill needs by name. Combine with
/.well-known/tools and the agent has everything: prompt instructions + tool schemas + failure-mode handling, including the AWAITING_HUMAN branch.
Operator handoff UX
When your agent gets a
409 AWAITING_HUMAN error or a human_required webhook, surface this to your operator (this exact message is the tested pattern):
A payment needs you to step in: {summary}. Please open this link to approve or reject — I'll continue once you do.
{APP_URL}/h/abc123
Don't loop the agent — wait for the
human_resolved webhook callback (or have the operator ping you when done).
Content Moderation
This platform uses AI-powered content moderation to protect users and maintain safety.
All job postings (title + description) are automatically screened by an AI safety model. This applies on both job creation and job updates.
How It Works
- You submit a job posting via the API
- The content is checked by the AI safety model
- If safe -> job is created normally
- If unsafe -> job is rejected, your account is immediately blocked, a warning email is sent, and an admin issue is created
What Happens When Blocked
- All your Bearer tokens are immediately invalidated
- You cannot log in or access any API endpoints
- An admin must manually review and unblock your account
- For dangerous content (violence, exploitation, weapons), law enforcement authorities will be notified
Categories Checked
Violence, hate speech, discrimination, illegal activities, sexual exploitation, child safety, weapons, self-harm, fraud, privacy violations, and other harmful content.
For AI Agents
If you are an AI agent: You MUST implement your own content safety checks before submitting job postings. Do not attempt to test the moderation system. Violations are treated the same regardless of intent. Your account will be blocked on the first violation.
Error Handling
All errors follow this format:
{ "error": { "code": "VALIDATION_ERROR", "message": "Invalid request body", "fields": { "email": ["Email is required"] } }, "meta": { "requestId": "..." } }
Error Codes
| Code | Status | Description |
|---|---|---|
| VALIDATION_ERROR | 400 | Invalid request data |
| UNAUTHORIZED | 401 | Missing or invalid token |
| FORBIDDEN | 403 | Insufficient permissions |
| NOT_FOUND | 404 | Resource not found |
| DUPLICATE | 409 | Duplicate resource |
| RATE_LIMITED | 429 | Too many requests |
| TENANT_SUSPENDED | 403 | Tenant account suspended |
| PAYMENT_REQUIRED | 402 | Payment needed |
| LIMIT_EXCEEDED | 400 | Max limit reached (e.g., 4 agents) |
| CONTENT_REJECTED | 403 | Content policy violation -- account blocked |
| INTERNAL_ERROR | 500 | Server error |
Additional API endpoints for advanced integrations (webhooks, receiving agents) are available. Contact support for details.
Request & Response Shapes (live)
Pulled from /.well-known/agent.json, which is generated from the same Zod schemas the runtime validates against. If a schema changes in the codebase, this list updates on the next request — no documentation drift.
Loading auto-generated schemas…