Automate everything, programmatically.
A simple, RESTful HTTP API lets you drive Open Research Tunisia from your own code or an AI agent — list and create projects, manage tasks, and more. Every request authenticates with a personal API key and acts as you, with your exact permissions. Create a key on the API keys page.
Authentication
Send your key as a Bearer token in the Authorization header. The base URL is your site's origin; all endpoints live under /api/v1. Bodies and responses are JSON.
curl https://openresearchtunisia.org/api/v1/me \
-H "Authorization: Bearer ort_your_key_here"A successful response wraps data in a data field:
{ "data": { "id": "…", "title": "…", … } }Scopes
Each key has scopes. A read key can list and fetch; a read + write key can also create and update. Give an agent a read-only key unless it needs to make changes. You choose the scope when creating the key.
Errors
Errors use standard HTTP status codes and a consistent JSON shape:
{ "error": { "code": "forbidden", "message": "You can't manage this project." } }401 missing/invalid key · 403 insufficient scope or permission · 404 not found · 422 validation · 500 server error.
Identity
/api/v1/me· read scopeReturns the authenticated user and the key's scopes.
{ "id":"…","name":"Ouael","role":"ADMIN","can_post_projects":true,"scopes":["read","write"] }Projects
/api/v1/projects· read scopeLists approved public projects. Query params: mine=true (projects you lead or belong to), recruiting=true, q= (search), limit= (max 100).
curl "https://openresearchtunisia.org/api/v1/projects?recruiting=true" \
-H "Authorization: Bearer ort_…"/api/v1/projects· write scopeCreates a project (requires posting rights). Like the web app, non-admin projects start as pending until an admin approves them. You become the lead.
curl -X POST https://openresearchtunisia.org/api/v1/projects \
-H "Authorization: Bearer ort_…" \
-H "Content-Type: application/json" \
-d '{
"title": "Forecasting Water Stress in the Medjerda Basin",
"summary": "An open, reproducible model of seasonal water stress in the basin.",
"area": "Environmental data science",
"stage": "Recruiting",
"tags": ["Climate", "GIS"],
"openings": [{ "role": "Data analyst", "skills": "Python, pandas", "seats": 2 }]
}'Required: title (≥8 chars), summary (≥30), area. Optional: about, stage, tags[], language, commitment, ethics_status, license, data_statement, openings[].
/api/v1/projects/{id_or_slug}· read scopeFetch one project by id or slug.
/api/v1/projects/{id_or_slug}· write scopeUpdate a project you manage. Accepts title, summary, about, stage, tags[].
Tasks
/api/v1/projects/{id}/tasks· read scopeThe project's task board. You must be a member of the project.
/api/v1/projects/{id}/tasks· write scopeAdd a task. Members may add a task and self-assign with "assign_self": true. Managers may also set assignee_id, credit_role, and good_first_task.
curl -X POST https://openresearchtunisia.org/api/v1/projects/PROJECT_ID/tasks \
-H "Authorization: Bearer ort_…" -H "Content-Type: application/json" \
-d '{ "title": "Summarize the drought-index papers", "effort": "M", "assign_self": true }'/api/v1/tasks/{id}· write scopeMove a task through OPEN → IN_PROGRESS → IN_REVIEW → DONE, or reassign it (managers). Only a manager may confirm DONE, which records the contribution on the assignee's public ledger.
curl -X PATCH https://openresearchtunisia.org/api/v1/tasks/TASK_ID \
-H "Authorization: Bearer ort_…" -H "Content-Type: application/json" \
-d '{ "status": "IN_REVIEW" }'Workshops
/api/v1/workshops· read scopeList public workshops. Query: limit= (max 100).
Connecting an agent
The API is designed to be agent-friendly: predictable JSON, one auth header, and stable field names. A minimal Node example:
const BASE = "https://openresearchtunisia.org/api/v1";
const KEY = process.env.ORT_API_KEY;
async function api(path, method = "GET", body) {
const res = await fetch(BASE + path, {
method,
headers: {
"Authorization": "Bearer " + KEY,
"Content-Type": "application/json",
},
body: body ? JSON.stringify(body) : undefined,
});
const json = await res.json();
if (!res.ok) throw new Error(json.error?.message ?? res.statusText);
return json.data;
}
// Create a project, then add a task to it.
const project = await api("/projects", "POST", {
title: "Automated literature triage",
summary: "An agent that triages new preprints against our inclusion criteria.",
area: "Computer science",
});
await api("/projects/" + project.id + "/tasks", "POST", {
title: "Draft the inclusion checklist",
assign_self: true,
});Keep your key secret — treat it like a password. If it leaks, revoke it on the API keys page and issue a new one. Keys never expire but can be revoked at any time.