OR
Developer API · v1

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

GET/api/v1/me· read scope

Returns the authenticated user and the key's scopes.

{ "id":"…","name":"Ouael","role":"ADMIN","can_post_projects":true,"scopes":["read","write"] }

Projects

GET/api/v1/projects· read scope

Lists 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_…"
POST/api/v1/projects· write scope

Creates 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[].

GET/api/v1/projects/{id_or_slug}· read scope

Fetch one project by id or slug.

PATCH/api/v1/projects/{id_or_slug}· write scope

Update a project you manage. Accepts title, summary, about, stage, tags[].

Tasks

GET/api/v1/projects/{id}/tasks· read scope

The project's task board. You must be a member of the project.

POST/api/v1/projects/{id}/tasks· write scope

Add 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 }'
PATCH/api/v1/tasks/{id}· write scope

Move 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

GET/api/v1/workshops· read scope

List 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.